@authorizedretailers/spec 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/schemas.d.ts +3 -0
- package/dist/schemas.js +4 -1
- package/dist/validate.d.ts +1 -1
- package/dist/validate.js +29 -2
- package/fixtures/file/invalid/full-physical-only.json +37 -0
- package/fixtures/manifest.json +7 -0
- package/package.json +1 -1
- package/schemas/common.json +3 -2
- package/spec/spec-v0.1.md +11 -3
package/dist/schemas.d.ts
CHANGED
package/dist/schemas.js
CHANGED
|
@@ -245,11 +245,14 @@ export const common = {
|
|
|
245
245
|
"$ref": "#/$defs/party"
|
|
246
246
|
},
|
|
247
247
|
"channels": {
|
|
248
|
-
"description": "Section 6: every authorization MUST name at least one channel identifier.",
|
|
248
|
+
"description": "Section 6: every authorization MUST name at least one online channel identifier (amazon, walmart, ebay or web). A physical identifier alone does not authorize anything.",
|
|
249
249
|
"type": "array",
|
|
250
250
|
"minItems": 1,
|
|
251
251
|
"items": {
|
|
252
252
|
"$ref": "#/$defs/channel"
|
|
253
|
+
},
|
|
254
|
+
"contains": {
|
|
255
|
+
"$ref": "#/$defs/verifiableChannel"
|
|
253
256
|
}
|
|
254
257
|
},
|
|
255
258
|
"scope": {
|
package/dist/validate.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AuthorizedRetailersFile, Channel, Scope, FullFile, PointerFile, PrivateFile, VerifyRequest, VerifyResponse } from "./types.js";
|
|
2
|
-
export type ErrorCode = "schema" | "invalid_timestamp" | "expires_not_after_issued" | "file_expiry_exceeds_max" | "authorization_expires_after_file" | "duplicate_authorization_id" | "valid_until_not_after_checked" | "valid_until_exceeds_max";
|
|
2
|
+
export type ErrorCode = "schema" | "no_online_channel" | "invalid_timestamp" | "expires_not_after_issued" | "file_expiry_exceeds_max" | "authorization_expires_after_file" | "duplicate_authorization_id" | "valid_until_not_after_checked" | "valid_until_exceeds_max";
|
|
3
3
|
export interface ValidationError {
|
|
4
4
|
code: ErrorCode;
|
|
5
5
|
/** JSON Pointer into the instance, e.g. "/authorizations/0/expires". */
|
package/dist/validate.js
CHANGED
|
@@ -48,6 +48,32 @@ function ts(errors, path, value) {
|
|
|
48
48
|
errors.push({ code: "invalid_timestamp", path, message: `not a valid UTC timestamp: ${value}` });
|
|
49
49
|
return ms;
|
|
50
50
|
}
|
|
51
|
+
const ONLINE_CHANNEL_TYPES = new Set(["amazon", "walmart", "ebay", "web"]);
|
|
52
|
+
/**
|
|
53
|
+
* Section 6: every authorization MUST name at least one online channel identifier. The schema
|
|
54
|
+
* enforces this too (`contains`), but its error is buried in generic output, so name it plainly.
|
|
55
|
+
* Runs on unvalidated input, so it checks shapes defensively.
|
|
56
|
+
*/
|
|
57
|
+
function onlineChannelErrors(input) {
|
|
58
|
+
const auths = input?.authorizations;
|
|
59
|
+
if (!Array.isArray(auths))
|
|
60
|
+
return [];
|
|
61
|
+
const errors = [];
|
|
62
|
+
auths.forEach((a, i) => {
|
|
63
|
+
const channels = a?.channels;
|
|
64
|
+
if (!Array.isArray(channels) || channels.length === 0)
|
|
65
|
+
return;
|
|
66
|
+
const online = channels.some((c) => ONLINE_CHANNEL_TYPES.has(c?.type));
|
|
67
|
+
if (!online) {
|
|
68
|
+
errors.push({
|
|
69
|
+
code: "no_online_channel",
|
|
70
|
+
path: `/authorizations/${i}/channels`,
|
|
71
|
+
message: "an authorization MUST name at least one online channel identifier (amazon, walmart, ebay or web); a physical identifier alone does not authorize anything (section 6)",
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
return errors;
|
|
76
|
+
}
|
|
51
77
|
/** Rules the JSON Schema cannot express (sections 5 and 10). Assumes the file already passed the schema. */
|
|
52
78
|
function fullFileRules(f) {
|
|
53
79
|
const errors = [];
|
|
@@ -109,14 +135,15 @@ function verifyResponseRules(r) {
|
|
|
109
135
|
}
|
|
110
136
|
/** Validates an authorized-retailers.json file of any form. */
|
|
111
137
|
export function validateFile(input) {
|
|
112
|
-
const
|
|
138
|
+
const isFull = input?.form === "full";
|
|
139
|
+
const errors = [...(isFull ? onlineChannelErrors(input) : []), ...schemaErrors(file, input)];
|
|
113
140
|
if (errors.length === 0 && input.form === "full") {
|
|
114
141
|
errors.push(...fullFileRules(input));
|
|
115
142
|
}
|
|
116
143
|
return done(errors, input);
|
|
117
144
|
}
|
|
118
145
|
export function validateFullFile(input) {
|
|
119
|
-
const errors = schemaErrors(fileFull, input);
|
|
146
|
+
const errors = [...onlineChannelErrors(input), ...schemaErrors(fileFull, input)];
|
|
120
147
|
if (errors.length === 0)
|
|
121
148
|
errors.push(...fullFileRules(input));
|
|
122
149
|
return done(errors, input);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"spec": "authorized-retailers/0.1",
|
|
3
|
+
"form": "full",
|
|
4
|
+
"brand": {
|
|
5
|
+
"name": "Example Brand",
|
|
6
|
+
"domain": "brand.example"
|
|
7
|
+
},
|
|
8
|
+
"issued": "2026-09-23T00:00:00Z",
|
|
9
|
+
"expires": "2026-12-22T00:00:00Z",
|
|
10
|
+
"authorizations": [
|
|
11
|
+
{
|
|
12
|
+
"id": "auth_01J9X2",
|
|
13
|
+
"retailer": {
|
|
14
|
+
"name": "Example Retail LLC",
|
|
15
|
+
"entity_id": "ent_7Q4M"
|
|
16
|
+
},
|
|
17
|
+
"channels": [
|
|
18
|
+
{
|
|
19
|
+
"type": "physical",
|
|
20
|
+
"address": "1 High St, London",
|
|
21
|
+
"country": "GB"
|
|
22
|
+
}
|
|
23
|
+
],
|
|
24
|
+
"scope": {
|
|
25
|
+
"territories": [
|
|
26
|
+
"US",
|
|
27
|
+
"CA"
|
|
28
|
+
],
|
|
29
|
+
"product_lines": [
|
|
30
|
+
"all"
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
"proposed_by": null,
|
|
34
|
+
"expires": "2026-12-22T00:00:00Z"
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
}
|
package/fixtures/manifest.json
CHANGED
|
@@ -145,6 +145,13 @@
|
|
|
145
145
|
"expect": "schema",
|
|
146
146
|
"note": "Domains are bare hostnames"
|
|
147
147
|
},
|
|
148
|
+
{
|
|
149
|
+
"path": "file/invalid/full-physical-only.json",
|
|
150
|
+
"kind": "file",
|
|
151
|
+
"valid": false,
|
|
152
|
+
"expect": "no_online_channel",
|
|
153
|
+
"note": "Section 6: physical alone does not authorize"
|
|
154
|
+
},
|
|
148
155
|
{
|
|
149
156
|
"path": "file/invalid/full-physical-missing-country.json",
|
|
150
157
|
"kind": "file",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@authorizedretailers/spec",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Authorized Retailers v0.1: JSON Schemas, types, validator, identifier matching, RFC 8785 canonicalization and detached-JWS reference verifier",
|
|
5
5
|
"license": "Apache-2.0 AND CC-BY-4.0",
|
|
6
6
|
"author": "Authorized Retailers",
|
package/schemas/common.json
CHANGED
|
@@ -140,10 +140,11 @@
|
|
|
140
140
|
"id": { "$ref": "#/$defs/id" },
|
|
141
141
|
"retailer": { "$ref": "#/$defs/party" },
|
|
142
142
|
"channels": {
|
|
143
|
-
"description": "Section 6: every authorization MUST name at least one channel identifier.",
|
|
143
|
+
"description": "Section 6: every authorization MUST name at least one online channel identifier (amazon, walmart, ebay or web). A physical identifier alone does not authorize anything.",
|
|
144
144
|
"type": "array",
|
|
145
145
|
"minItems": 1,
|
|
146
|
-
"items": { "$ref": "#/$defs/channel" }
|
|
146
|
+
"items": { "$ref": "#/$defs/channel" },
|
|
147
|
+
"contains": { "$ref": "#/$defs/verifiableChannel" }
|
|
147
148
|
},
|
|
148
149
|
"scope": { "$ref": "#/$defs/scope" },
|
|
149
150
|
"proposed_by": {
|
package/spec/spec-v0.1.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Authorized Retailers Specification v0.1
|
|
2
2
|
|
|
3
|
-
Draft v0.1 · 23 September 2026, updated
|
|
3
|
+
Draft v0.1 · 23 September 2026, updated 26 September 2026
|
|
4
4
|
Editor: Ed Jacobs
|
|
5
5
|
|
|
6
6
|
Licensed under [CC BY 4.0](https://github.com/authorizedretailers/spec/blob/main/LICENSE-SPEC). JSON Schemas and reference code: Apache-2.0.
|
|
@@ -13,6 +13,8 @@ Status: draft v0.1, for discussion with brands, retailers and agent developers.
|
|
|
13
13
|
|
|
14
14
|
The spec asserts one thing: whether a brand has authorized a given seller, on a given channel, in a given territory, for a given product scope, as of a given date.
|
|
15
15
|
|
|
16
|
+
Scope: the spec covers consumer-facing online channels: marketplaces and web stores, including omnichannel retailers, which are identified by their web domain. Trade, wholesale and practitioner-only channels are out of scope. Sellers there are not listed, so their resale on consumer channels verifies as unlisted.
|
|
17
|
+
|
|
16
18
|
It does not assert:
|
|
17
19
|
|
|
18
20
|
- **Authenticity.** An authorized seller can still sell counterfeit goods or have its account compromised. Authorization and authenticity are separate questions.
|
|
@@ -123,7 +125,7 @@ An individual authorization's `expires` MUST NOT be later than the file's `expir
|
|
|
123
125
|
|
|
124
126
|
## 6. Seller identity and channel identifiers
|
|
125
127
|
|
|
126
|
-
Agents see sellers as channel identifiers, not company names, so every authorization MUST name at least one channel identifier. A name alone is not verifiable.
|
|
128
|
+
Agents see sellers as channel identifiers, not company names, so every authorization MUST name at least one online channel identifier (`amazon`, `walmart`, `ebay` or `web`). A name alone is not verifiable, and a `physical` identifier alone does not authorize anything.
|
|
127
129
|
|
|
128
130
|
| Channel type | Required fields | Identifier |
|
|
129
131
|
| --- | --- | --- |
|
|
@@ -131,7 +133,7 @@ Agents see sellers as channel identifiers, not company names, so every authoriza
|
|
|
131
133
|
| `walmart` | `marketplace`, `seller_id` | Walmart Marketplace partner ID |
|
|
132
134
|
| `ebay` | `marketplace`, `seller_id` | eBay user ID |
|
|
133
135
|
| `web` | `domain` | The retailer's store domain |
|
|
134
|
-
| `physical` | `address`, `country` |
|
|
136
|
+
| `physical` | `address`, `country` | Optional storefront detail. Not a channel agents can verify, and never sufficient on its own |
|
|
135
137
|
|
|
136
138
|
New channel types are added by registry proposal and published in the spec changelog.
|
|
137
139
|
|
|
@@ -260,6 +262,8 @@ The registry's liability position, dispute timelines and data handling are set o
|
|
|
260
262
|
- [ ] Observation on channels beyond Amazon: Walmart and retailer web domains first?
|
|
261
263
|
- [ ] Alignment with UCP and ACP: an extension field that points agents to a brand's authorized-retailers file.
|
|
262
264
|
- [ ] Brand-declared `unauthorized`: a status for sellers the brand has explicitly said are not authorized, as distinct from `unlisted`, with a dispute path for the seller.
|
|
265
|
+
- [ ] Dispensary and multi-seller platforms (e.g. practitioner dispensaries hosted on one platform domain): how to authorize individual sellers when agents only see the platform domain.
|
|
266
|
+
- [ ] Channel audience: whether a channel needs an `audience` field (consumer, trade, practitioner) or whether consumer-only scope is enough.
|
|
263
267
|
|
|
264
268
|
## 14. Changelog
|
|
265
269
|
|
|
@@ -280,3 +284,7 @@ The registry's liability position, dispute timelines and data handling are set o
|
|
|
280
284
|
- A registry MAY warn brands about sellers at PO boxes or commercial mailboxes. Verify answers don't change (section 12).
|
|
281
285
|
- Corrected: the reference registry doesn't observe sellers yet (section 7).
|
|
282
286
|
- Examples use reserved `.example` domains.
|
|
287
|
+
- **2026-09-26**
|
|
288
|
+
- Scope: the spec covers consumer-facing online channels, including omnichannel retailers identified by their web domain. Trade, wholesale and practitioner-only channels are out of scope (section 1).
|
|
289
|
+
- Every authorization MUST name at least one online channel identifier (`amazon`, `walmart`, `ebay` or `web`). A `physical` identifier alone no longer authorizes anything (section 6).
|
|
290
|
+
- Two open questions: dispensary and multi-seller platforms, and a channel `audience` field (section 13).
|