@stellar-agent-services/protocol 0.0.0-stage → 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.
Files changed (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +14 -2
  3. package/dist/canonical-json.d.ts +18 -0
  4. package/dist/canonical-json.d.ts.map +1 -0
  5. package/dist/canonical-json.js +86 -0
  6. package/dist/canonical-json.js.map +1 -0
  7. package/dist/errors.d.ts +85 -0
  8. package/dist/errors.d.ts.map +1 -0
  9. package/dist/errors.js +46 -0
  10. package/dist/errors.js.map +1 -0
  11. package/dist/headers.d.ts +15 -0
  12. package/dist/headers.d.ts.map +1 -0
  13. package/dist/headers.js +29 -0
  14. package/dist/headers.js.map +1 -0
  15. package/dist/index.d.ts +11 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +18 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/jobs.d.ts +62 -0
  20. package/dist/jobs.d.ts.map +1 -0
  21. package/dist/jobs.js +66 -0
  22. package/dist/jobs.js.map +1 -0
  23. package/dist/manifest.d.ts +95 -0
  24. package/dist/manifest.d.ts.map +1 -0
  25. package/dist/manifest.js +144 -0
  26. package/dist/manifest.js.map +1 -0
  27. package/dist/networks.d.ts +19 -0
  28. package/dist/networks.d.ts.map +1 -0
  29. package/dist/networks.js +33 -0
  30. package/dist/networks.js.map +1 -0
  31. package/dist/receipts.d.ts +112 -0
  32. package/dist/receipts.d.ts.map +1 -0
  33. package/dist/receipts.js +35 -0
  34. package/dist/receipts.js.map +1 -0
  35. package/dist/standard.d.ts +25 -0
  36. package/dist/standard.d.ts.map +1 -0
  37. package/dist/standard.js +29 -0
  38. package/dist/standard.js.map +1 -0
  39. package/dist/strkey.d.ts +7 -0
  40. package/dist/strkey.d.ts.map +1 -0
  41. package/dist/strkey.js +56 -0
  42. package/dist/strkey.js.map +1 -0
  43. package/dist/validation-request.d.ts +32 -0
  44. package/dist/validation-request.d.ts.map +1 -0
  45. package/dist/validation-request.js +29 -0
  46. package/dist/validation-request.js.map +1 -0
  47. package/package.json +37 -4
  48. package/schemas/manifest.schema.json +272 -0
  49. package/schemas/validation-request.schema.json +43 -0
  50. package/schemas/verification-receipt.schema.json +149 -0
  51. package/test-vectors/README.md +45 -0
  52. package/test-vectors/binding-hash.json +64 -0
  53. package/test-vectors/canonical-json.json +94 -0
  54. package/test-vectors/challenges.json +245 -0
  55. package/test-vectors/headers.json +37 -0
  56. package/test-vectors/job-states.json +39 -0
  57. package/test-vectors/manifests.json +1322 -0
  58. package/test-vectors/problems.json +15 -0
  59. package/test-vectors/validation-requests.json +98 -0
package/package.json CHANGED
@@ -1,6 +1,39 @@
1
1
  {
2
2
  "name": "@stellar-agent-services/protocol",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "Provider Standard v1 as code: manifest, job, receipt and header definitions shared by every package.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/AlgoriaLabs/stellar-agent-services.git",
10
+ "directory": "packages/protocol"
11
+ },
12
+ "engines": {
13
+ "node": ">=22.13"
14
+ },
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "default": "./dist/index.js"
19
+ },
20
+ "./manifest.schema.json": "./schemas/manifest.schema.json",
21
+ "./verification-receipt.schema.json": "./schemas/verification-receipt.schema.json",
22
+ "./validation-request.schema.json": "./schemas/validation-request.schema.json",
23
+ "./test-vectors/*": "./test-vectors/*"
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "schemas",
28
+ "test-vectors",
29
+ "!dist/.tsbuildinfo"
30
+ ],
31
+ "scripts": {
32
+ "build": "tsc -b"
33
+ },
34
+ "devDependencies": {
35
+ "@stellar/stellar-sdk": "16.4.1",
36
+ "@x402/stellar": "2.28.0",
37
+ "ajv": "8.20.0"
38
+ }
39
+ }
@@ -0,0 +1,272 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://algorialabs.github.io/stellar-agent-services/schemas/provider/1/manifest.schema.json",
4
+ "$comment": "The $id is provisional until the v1 freeze. Rule ids such as [M5] refer to docs/provider-standard.md.",
5
+ "title": "Provider Standard v1 manifest",
6
+ "description": "Describes a paid AI service on Stellar: its services, prices, payment option and job endpoints. Unknown fields are allowed and ignored [M8]. Some rules need code as well (§2.4): M1, M3, M5 (same origin), M7, S6, S9 and P11.",
7
+ "type": "object",
8
+ "required": ["standard", "provider", "baseUrl", "payment", "services"],
9
+ "properties": {
10
+ "standard": {
11
+ "description": "[M8] A manifest with a different major version is rejected.",
12
+ "const": "stellar-agent-services/provider@1"
13
+ },
14
+ "provider": { "$ref": "#/$defs/provider" },
15
+ "baseUrl": {
16
+ "description": "[M5] Job endpoints live under it. HTTPS, no user info, query, fragment, empty path segment or trailing slash. Same origin as the manifest URL, which is checked in code.",
17
+ "type": "string",
18
+ "maxLength": 300,
19
+ "pattern": "^https://[^\\s/?#@]+(/[^\\s/?#]+)*$"
20
+ },
21
+ "payment": { "$ref": "#/$defs/payment" },
22
+ "jobs": { "$ref": "#/$defs/jobs" },
23
+ "services": {
24
+ "type": "array",
25
+ "minItems": 1,
26
+ "maxItems": 25,
27
+ "items": { "$ref": "#/$defs/service" }
28
+ },
29
+ "extensions": { "$ref": "#/$defs/extensions" }
30
+ },
31
+ "$defs": {
32
+ "provider": {
33
+ "type": "object",
34
+ "required": ["name"],
35
+ "properties": {
36
+ "name": { "$ref": "#/$defs/line", "type": "string", "minLength": 1, "maxLength": 120 },
37
+ "description": { "$ref": "#/$defs/text", "type": "string", "maxLength": 500 },
38
+ "homepage": { "$ref": "#/$defs/httpsUrl" },
39
+ "agentId": {
40
+ "description": "Stellar 8004 agent id (u32), needed for the identity check (§7).",
41
+ "type": "integer",
42
+ "minimum": 0,
43
+ "maximum": 4294967295
44
+ },
45
+ "termsUrl": { "$ref": "#/$defs/httpsUrl" },
46
+ "dataUse": {
47
+ "type": "object",
48
+ "properties": {
49
+ "storesInputs": {
50
+ "description": "Whether inputs are kept after the job finishes.",
51
+ "type": "boolean"
52
+ },
53
+ "trainsOnInputs": {
54
+ "description": "Whether inputs are used to train models.",
55
+ "type": "boolean"
56
+ }
57
+ }
58
+ }
59
+ }
60
+ },
61
+ "payment": {
62
+ "type": "object",
63
+ "required": ["protocol", "x402Version", "accepts"],
64
+ "properties": {
65
+ "protocol": { "const": "x402" },
66
+ "x402Version": { "const": 2 },
67
+ "accepts": {
68
+ "description": "[P1] Exactly one payment option in v1.",
69
+ "type": "array",
70
+ "minItems": 1,
71
+ "maxItems": 1,
72
+ "items": { "$ref": "#/$defs/paymentOption" }
73
+ }
74
+ }
75
+ },
76
+ "paymentOption": {
77
+ "description": "x402 v2 PaymentRequirements field names, without amount, which comes from each service's price [P1].",
78
+ "type": "object",
79
+ "required": ["scheme", "network", "asset", "payTo", "maxTimeoutSeconds", "extra"],
80
+ "properties": {
81
+ "scheme": { "const": "exact" },
82
+ "network": { "enum": ["stellar:testnet", "stellar:pubnet"] },
83
+ "asset": {
84
+ "description": "The USDC Stellar Asset Contract for the network.",
85
+ "type": "string"
86
+ },
87
+ "payTo": {
88
+ "description": "A G... account or C... contract address. Its checksum is checked in code [P11].",
89
+ "type": "string",
90
+ "pattern": "^[GC][A-D][A-Z2-7]{54}$"
91
+ },
92
+ "amount": false,
93
+ "maxTimeoutSeconds": { "type": "integer", "minimum": 30, "maximum": 300 },
94
+ "extra": {
95
+ "type": "object",
96
+ "required": ["areFeesSponsored"],
97
+ "properties": {
98
+ "areFeesSponsored": { "const": true }
99
+ }
100
+ }
101
+ },
102
+ "allOf": [
103
+ {
104
+ "if": {
105
+ "type": "object",
106
+ "required": ["network"],
107
+ "properties": { "network": { "const": "stellar:testnet" } }
108
+ },
109
+ "then": {
110
+ "type": "object",
111
+ "properties": { "asset": { "const": "CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA" } }
112
+ }
113
+ },
114
+ {
115
+ "if": {
116
+ "type": "object",
117
+ "required": ["network"],
118
+ "properties": { "network": { "const": "stellar:pubnet" } }
119
+ },
120
+ "then": {
121
+ "type": "object",
122
+ "properties": { "asset": { "const": "CCW67TSZV3SSS2HXMBQ5JFGCKJNXKZM7UQUWUZPUTHXSTZLEO7SJMI75" } }
123
+ }
124
+ }
125
+ ]
126
+ },
127
+ "jobs": {
128
+ "type": "object",
129
+ "properties": {
130
+ "retentionSeconds": {
131
+ "description": "How long results stay retrievable.",
132
+ "type": "integer",
133
+ "minimum": 3600,
134
+ "default": 86400
135
+ }
136
+ }
137
+ },
138
+ "service": {
139
+ "type": "object",
140
+ "required": ["name", "description", "price", "inputSchema", "outputSchema"],
141
+ "properties": {
142
+ "name": {
143
+ "description": "Unique in the manifest [M7]. A URL path segment, an MCP tool name and an LLM function name.",
144
+ "type": "string",
145
+ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
146
+ },
147
+ "title": { "$ref": "#/$defs/line", "type": "string", "minLength": 1, "maxLength": 120 },
148
+ "description": { "$ref": "#/$defs/text", "type": "string", "minLength": 1, "maxLength": 1000 },
149
+ "price": {
150
+ "type": "object",
151
+ "required": ["amount"],
152
+ "properties": {
153
+ "amount": {
154
+ "description": "A positive integer in the asset's base units. USDC has 7 decimals, so \"100000\" is 0.01 USDC.",
155
+ "type": "string",
156
+ "pattern": "^[1-9][0-9]{0,37}$"
157
+ }
158
+ }
159
+ },
160
+ "charge": {
161
+ "description": "When the user is charged (§3.2).",
162
+ "enum": ["on-success", "upfront"],
163
+ "default": "upfront"
164
+ },
165
+ "expectedDurationSeconds": { "type": "integer", "minimum": 1 },
166
+ "inputSchema": { "$ref": "#/$defs/objectSchema" },
167
+ "outputSchema": { "$ref": "#/$defs/objectSchema" },
168
+ "examples": {
169
+ "description": "Each input and output must match the service's schemas, which is checked in code [S9].",
170
+ "type": "array",
171
+ "maxItems": 5,
172
+ "items": {
173
+ "type": "object",
174
+ "required": ["input", "output"],
175
+ "properties": {
176
+ "input": { "type": "object" },
177
+ "output": { "type": "object" }
178
+ }
179
+ }
180
+ },
181
+ "maxInputBytes": { "type": "integer", "minimum": 1, "maximum": 10485760, "default": 1048576 },
182
+ "tags": {
183
+ "type": "array",
184
+ "maxItems": 10,
185
+ "uniqueItems": true,
186
+ "items": { "type": "string", "pattern": "^[a-z0-9-]{1,32}$" }
187
+ },
188
+ "capacity": {
189
+ "description": "How much load the service takes (§5.8). Hints only: the provider's 429, 503 and queue answers are what count.",
190
+ "type": "object",
191
+ "properties": {
192
+ "concurrency": {
193
+ "description": "Jobs the service runs at the same time.",
194
+ "type": "integer",
195
+ "minimum": 1
196
+ },
197
+ "maxQueueWaitSeconds": {
198
+ "description": "Longest a new job may expect to wait in the queue; beyond it, create-job answers 503. 0 means no queue.",
199
+ "type": "integer",
200
+ "minimum": 0
201
+ }
202
+ }
203
+ },
204
+ "extensions": { "$ref": "#/$defs/extensions" }
205
+ },
206
+ "if": {
207
+ "type": "object",
208
+ "required": ["charge"],
209
+ "properties": { "charge": { "const": "on-success" } }
210
+ },
211
+ "then": { "type": "object", "required": ["expectedDurationSeconds"] }
212
+ },
213
+ "objectSchema": {
214
+ "description": "[S4] A JSON Schema 2020-12 schema whose root is \"type\": \"object\". [S5] It references nothing outside itself. Its size is checked in code [S6].",
215
+ "allOf": [
216
+ { "$ref": "https://json-schema.org/draft/2020-12/schema" },
217
+ { "$ref": "#/$defs/localReferencesOnly" },
218
+ {
219
+ "type": "object",
220
+ "required": ["type"],
221
+ "properties": { "type": { "const": "object" } }
222
+ }
223
+ ]
224
+ },
225
+ "localReferencesOnly": {
226
+ "description": "[S5] Wherever they appear, $ref and $dynamicRef start with \"#\", and $schema is JSON Schema 2020-12. Deliberately conservative: it also applies inside const, enum, default and examples.",
227
+ "if": { "type": "object" },
228
+ "then": {
229
+ "type": "object",
230
+ "properties": {
231
+ "$ref": { "$ref": "#/$defs/localReference" },
232
+ "$dynamicRef": { "$ref": "#/$defs/localReference" },
233
+ "$schema": {
234
+ "if": { "type": "string" },
235
+ "then": { "type": "string", "const": "https://json-schema.org/draft/2020-12/schema" },
236
+ "else": { "$ref": "#/$defs/localReferencesOnly" }
237
+ }
238
+ },
239
+ "additionalProperties": { "$ref": "#/$defs/localReferencesOnly" }
240
+ },
241
+ "else": {
242
+ "if": { "type": "array" },
243
+ "then": { "type": "array", "items": { "$ref": "#/$defs/localReferencesOnly" } }
244
+ }
245
+ },
246
+ "localReference": {
247
+ "if": { "type": "string" },
248
+ "then": { "type": "string", "pattern": "^#" },
249
+ "else": { "$ref": "#/$defs/localReferencesOnly" }
250
+ },
251
+ "httpsUrl": {
252
+ "description": "[M6] An HTTPS URL of at most 300 characters, without user info.",
253
+ "type": "string",
254
+ "maxLength": 300,
255
+ "pattern": "^https://[^\\s/?#@]+([/?#]\\S*)?$"
256
+ },
257
+ "line": {
258
+ "description": "Text without control characters.",
259
+ "type": "string",
260
+ "pattern": "^[^\u0000-\u001f\u007f]*$"
261
+ },
262
+ "text": {
263
+ "description": "Text that may contain tabs and newlines but no other control characters.",
264
+ "type": "string",
265
+ "pattern": "^[^\u0000-\u0008\u000b-\u001f\u007f]*$"
266
+ },
267
+ "extensions": {
268
+ "description": "[X1] Fields outside this standard. Keys SHOULD be reverse-DNS names such as com.example.sla.",
269
+ "type": "object"
270
+ }
271
+ }
272
+ }
@@ -0,0 +1,43 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://algorialabs.github.io/stellar-agent-services/schemas/provider/1/validation-request.schema.json",
4
+ "$comment": "Rule ids such as [I1] refer to docs/provider-standard.md; the request itself is described in docs/verification.md.",
5
+ "title": "Provider Standard v1 validation request",
6
+ "description": "What a provider's owner asks a verifier to check, in a Stellar 8004 validation_request. It is published as RFC 8785 canonical JSON: the SHA-256 of those bytes is the request_hash, and the request_uri holds the same bytes. Unknown fields are allowed and ignored, but they are hashed with the rest.",
7
+ "type": "object",
8
+ "required": ["standard", "type", "agentId", "network", "manifest", "validator", "requestedAt"],
9
+ "properties": {
10
+ "standard": { "const": "stellar-agent-services/provider@1" },
11
+ "type": { "const": "validation-request" },
12
+ "agentId": {
13
+ "description": "The provider's Stellar 8004 agent id (u32).",
14
+ "type": "integer",
15
+ "minimum": 0,
16
+ "maximum": 4294967295
17
+ },
18
+ "network": { "enum": ["stellar:testnet", "stellar:pubnet"] },
19
+ "manifest": {
20
+ "description": "The manifest to check.",
21
+ "type": "object",
22
+ "required": ["url"],
23
+ "properties": {
24
+ "url": {
25
+ "description": "The HTTPS URL the agent's registration names [I1].",
26
+ "type": "string",
27
+ "maxLength": 2048,
28
+ "pattern": "^https://[^\\s]+$"
29
+ }
30
+ }
31
+ },
32
+ "validator": {
33
+ "description": "The verifier's account: the only one that may answer the request.",
34
+ "type": "string",
35
+ "pattern": "^G[A-D][A-Z2-7]{54}$"
36
+ },
37
+ "requestedAt": {
38
+ "description": "RFC 3339. It makes each request unique: the registry refuses a request_hash it already has.",
39
+ "type": "string",
40
+ "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]+)?(Z|[+-][0-9]{2}:[0-9]{2})$"
41
+ }
42
+ }
43
+ }
@@ -0,0 +1,149 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://algorialabs.github.io/stellar-agent-services/schemas/provider/1/verification-receipt.schema.json",
4
+ "$comment": "Rule ids such as [P4] refer to docs/provider-standard.md; the receipt itself is described in docs/verification.md.",
5
+ "title": "Provider Standard v1 verification receipt",
6
+ "description": "What a verifier issues after checking a provider. It is published as RFC 8785 canonical JSON, and the SHA-256 of those bytes is the Stellar 8004 Validation Registry response_hash. Unknown fields are allowed and ignored. Two rules need code as well: level is the highest conformance level whose checks all passed, and score is that level's score.",
7
+ "type": "object",
8
+ "required": ["standard", "network", "validator", "startedAt", "checkedAt", "score", "scoreDirection", "level", "manifest", "results"],
9
+ "properties": {
10
+ "standard": { "const": "stellar-agent-services/provider@1" },
11
+ "agentId": {
12
+ "description": "The provider's Stellar 8004 agent id (u32). Required to publish the receipt to the Validation Registry.",
13
+ "type": "integer",
14
+ "minimum": 0,
15
+ "maximum": 4294967295
16
+ },
17
+ "network": { "enum": ["stellar:testnet", "stellar:pubnet"] },
18
+ "validator": {
19
+ "description": "The verifier's account, which signs validation_response.",
20
+ "type": "string",
21
+ "pattern": "^G[A-D][A-Z2-7]{54}$"
22
+ },
23
+ "startedAt": { "$ref": "#/$defs/timestamp" },
24
+ "checkedAt": { "$ref": "#/$defs/timestamp" },
25
+ "score": {
26
+ "description": "The level's score, and the on-chain response: 0, 33, 67 or 100 for levels 0 to 3.",
27
+ "enum": [0, 33, 67, 100]
28
+ },
29
+ "scoreDirection": { "const": "higher-is-better" },
30
+ "level": { "enum": [0, 1, 2, 3] },
31
+ "manifest": {
32
+ "type": "object",
33
+ "required": ["url"],
34
+ "properties": {
35
+ "url": { "type": "string", "minLength": 1, "maxLength": 2048 },
36
+ "sha256": {
37
+ "description": "SHA-256 of the parsed manifest as canonical JSON. Absent when the manifest could not be read as JSON.",
38
+ "$ref": "#/$defs/sha256"
39
+ }
40
+ }
41
+ },
42
+ "results": {
43
+ "description": "One result per check, in order from C1 to C7.",
44
+ "type": "array",
45
+ "prefixItems": [
46
+ { "$ref": "#/$defs/checkResult", "type": "object", "properties": { "check": { "const": "C1" } } },
47
+ { "$ref": "#/$defs/checkResult", "type": "object", "properties": { "check": { "const": "C2" } } },
48
+ { "$ref": "#/$defs/checkResult", "type": "object", "properties": { "check": { "const": "C3" } } },
49
+ { "$ref": "#/$defs/checkResult", "type": "object", "properties": { "check": { "const": "C4" } } },
50
+ { "$ref": "#/$defs/checkResult", "type": "object", "properties": { "check": { "const": "C5" } } },
51
+ { "$ref": "#/$defs/checkResult", "type": "object", "properties": { "check": { "const": "C6" } } },
52
+ { "$ref": "#/$defs/checkResult", "type": "object", "properties": { "check": { "const": "C7" } } }
53
+ ],
54
+ "minItems": 7,
55
+ "items": false
56
+ },
57
+ "payment": { "$ref": "#/$defs/paymentReceipt" },
58
+ "tool": {
59
+ "description": "The software that issued the receipt.",
60
+ "type": "object",
61
+ "required": ["name", "version"],
62
+ "properties": {
63
+ "name": { "type": "string", "minLength": 1, "maxLength": 100 },
64
+ "version": { "type": "string", "minLength": 1, "maxLength": 50 }
65
+ }
66
+ }
67
+ },
68
+ "allOf": [
69
+ {
70
+ "if": { "type": "object", "properties": { "level": { "const": 0 } } },
71
+ "then": { "type": "object", "properties": { "score": { "const": 0 } } }
72
+ },
73
+ {
74
+ "if": { "type": "object", "properties": { "level": { "const": 1 } } },
75
+ "then": { "type": "object", "properties": { "score": { "const": 33 } } }
76
+ },
77
+ {
78
+ "if": { "type": "object", "properties": { "level": { "const": 2 } } },
79
+ "then": { "type": "object", "properties": { "score": { "const": 67 } } }
80
+ },
81
+ {
82
+ "if": { "type": "object", "properties": { "level": { "const": 3 } } },
83
+ "then": { "type": "object", "properties": { "score": { "const": 100 } } }
84
+ }
85
+ ],
86
+ "$defs": {
87
+ "timestamp": {
88
+ "description": "RFC 3339.",
89
+ "type": "string",
90
+ "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]+)?(Z|[+-][0-9]{2}:[0-9]{2})$"
91
+ },
92
+ "sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
93
+ "rule": {
94
+ "description": "A rule id such as P4, or a section such as §5.5 for a requirement without an id.",
95
+ "type": "string",
96
+ "pattern": "^(?:[A-Z][0-9]{1,2}|§[0-9]{1,2}(?:\\.[0-9]{1,2})?)$"
97
+ },
98
+ "checkResult": {
99
+ "type": "object",
100
+ "required": ["check", "status", "failedRules"],
101
+ "properties": {
102
+ "check": { "enum": ["C1", "C2", "C3", "C4", "C5", "C6", "C7"] },
103
+ "status": { "enum": ["passed", "failed", "skipped"] },
104
+ "failedRules": { "type": "array", "items": { "$ref": "#/$defs/rule" }, "uniqueItems": true },
105
+ "warnings": { "type": "array", "items": { "$ref": "#/$defs/rule" }, "uniqueItems": true },
106
+ "reason": { "type": "string", "minLength": 1, "maxLength": 500 },
107
+ "findings": {
108
+ "type": "array",
109
+ "maxItems": 50,
110
+ "items": {
111
+ "type": "object",
112
+ "required": ["rule", "severity", "message"],
113
+ "properties": {
114
+ "rule": { "$ref": "#/$defs/rule" },
115
+ "severity": { "enum": ["fail", "warn"] },
116
+ "message": { "type": "string", "minLength": 1, "maxLength": 500 }
117
+ }
118
+ }
119
+ },
120
+ "evidence": { "type": "object" }
121
+ },
122
+ "allOf": [
123
+ {
124
+ "if": { "type": "object", "properties": { "status": { "const": "failed" } } },
125
+ "then": { "type": "object", "properties": { "failedRules": { "type": "array", "minItems": 1 } } },
126
+ "else": { "type": "object", "properties": { "failedRules": { "type": "array", "maxItems": 0 } } }
127
+ },
128
+ {
129
+ "if": { "type": "object", "properties": { "status": { "const": "skipped" } } },
130
+ "then": { "type": "object", "required": ["reason"] }
131
+ }
132
+ ]
133
+ },
134
+ "paymentReceipt": {
135
+ "description": "The C4 payment, as the job view reported it (provider-standard.md §5.7), after the verifier confirmed it on chain.",
136
+ "type": "object",
137
+ "required": ["network", "asset", "amount", "payTo", "payer", "transaction", "settledAt"],
138
+ "properties": {
139
+ "network": { "enum": ["stellar:testnet", "stellar:pubnet"] },
140
+ "asset": { "type": "string", "pattern": "^C[A-D][A-Z2-7]{54}$" },
141
+ "amount": { "type": "string", "pattern": "^[1-9][0-9]{0,37}$" },
142
+ "payTo": { "type": "string", "pattern": "^[GC][A-D][A-Z2-7]{54}$" },
143
+ "payer": { "type": "string", "pattern": "^[GC][A-D][A-Z2-7]{54}$" },
144
+ "transaction": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
145
+ "settledAt": { "$ref": "#/$defs/timestamp" }
146
+ }
147
+ }
148
+ }
149
+ }
@@ -0,0 +1,45 @@
1
+ # Provider Standard v1 test vectors
2
+
3
+ Known-answer tests for [Provider Standard v1](../../../docs/provider-standard.md). An implementation in any
4
+ language can check itself against these files. `packages/protocol` runs all of them in its own tests.
5
+
6
+ | File | What it pins | Spec |
7
+ | --- | --- | --- |
8
+ | `canonical-json.json` | RFC 8785 canonical JSON and its SHA-256, including the RFC's own examples | §5.3, verification receipts |
9
+ | `binding-hash.json` | The job binding hash | §5.3 |
10
+ | `headers.json` | `Idempotency-Key` and `Job-Token` formats | §5.2 |
11
+ | `manifests.json` | Manifest validation: the JSON Schema and the rules it can't express | §2.4 |
12
+ | `challenges.json` | Checking an x402 challenge against the approved manifest | §4.2 |
13
+ | `job-states.json` | Allowed job state transitions and the `charged` value of each state | §5.6 |
14
+ | `problems.json` | Error codes, their HTTP status and Retry-After, and job failure codes | §6 |
15
+ | `validation-requests.json` | Validation request documents: their canonical bytes, `request_hash` and `data:` URI, and the JSON Schema | [verification.md](../../../docs/verification.md#stellar-8004-validation-registry) |
16
+
17
+ ## Conventions
18
+
19
+ - **Patches.** `manifests.json` and `challenges.json` describe each case as a small change to a base document,
20
+ using the `add`, `replace` and `remove` operations of JSON Patch (RFC 6902). A last path segment of `-`
21
+ appends to an array.
22
+ - **Violations.** Each `{ "rule", "pointer" }` is a rule id from the spec and a JSON Pointer (RFC 6901) to the
23
+ offending value, or to where a missing field belongs. Compare them as a set. Messages are not part of the
24
+ vectors.
25
+ - **`checkedBy`.** In `manifests.json`, `"schema"` cases fail the
26
+ [manifest JSON Schema](../schemas/manifest.schema.json). `"code"` cases pass it and fail only the rules JSON
27
+ Schema can't express. If you validate in code only, every invalid case must still fail.
28
+ - **Numbers** are IEEE 754 doubles, as I-JSON requires. Integers beyond ±(2^53 − 1) aren't interoperable, so the
29
+ vectors avoid them. Services should send such values as strings.
30
+ - **Duplicate keys** make a document invalid I-JSON, but many parsers can't detect them, so no vector covers
31
+ them.
32
+
33
+ ## How they were made
34
+
35
+ The expected values were generated once and checked before they were written:
36
+ - canonical JSON against the independent [`canonicalize`](https://github.com/erdtman/canonicalize) package by an
37
+ RFC 8785 author;
38
+ - the RFC 8785 examples (§3.2.2, §3.2.3, Appendix B) against the text of the RFC;
39
+ - hashes against a second SHA-256 implementation;
40
+ - every manifest and challenge case against Ajv with the JSON Schema, and against `packages/protocol`;
41
+ - validation requests with Python's `json`, `hashlib` and `base64` modules, apart from `packages/protocol`, and the
42
+ hashes again with `shasum`;
43
+ - job states and error codes by hand from the tables in §5.6 and §6.
44
+
45
+ When you change a vector, run `npm test` and review the diff by hand.
@@ -0,0 +1,64 @@
1
+ {
2
+ "description": "The job binding hash of provider-standard.md §5.3: bindingHash = lowercase hex SHA-256 of the RFC 8785 canonical JSON of {\"service\": service, \"input\": input}. `canonical` shows the bytes that are hashed.",
3
+ "cases": [
4
+ {
5
+ "name": "the text-stats example",
6
+ "service": "text-stats",
7
+ "input": { "text": "Hello world." },
8
+ "canonical": "{\"input\":{\"text\":\"Hello world.\"},\"service\":\"text-stats\"}",
9
+ "bindingHash": "3d3b3dbdf90621535bd77d1f1d2a1ed0334bf875c7dc7c023caa48cad949ce87"
10
+ },
11
+ {
12
+ "name": "key order does not matter (1 of 2)",
13
+ "service": "translate",
14
+ "input": { "text": "Good morning", "target": "tr", "source": "en" },
15
+ "canonical": "{\"input\":{\"source\":\"en\",\"target\":\"tr\",\"text\":\"Good morning\"},\"service\":\"translate\"}",
16
+ "bindingHash": "1727298e87acb5642a49ceed3cfca3012e3d90c8ddae8642aec5b21c5d40531d"
17
+ },
18
+ {
19
+ "name": "key order does not matter (2 of 2)",
20
+ "service": "translate",
21
+ "input": { "source": "en", "target": "tr", "text": "Good morning" },
22
+ "canonical": "{\"input\":{\"source\":\"en\",\"target\":\"tr\",\"text\":\"Good morning\"},\"service\":\"translate\"}",
23
+ "bindingHash": "1727298e87acb5642a49ceed3cfca3012e3d90c8ddae8642aec5b21c5d40531d"
24
+ },
25
+ {
26
+ "name": "the service name is part of the binding",
27
+ "service": "summarize",
28
+ "input": { "text": "Hello world." },
29
+ "canonical": "{\"input\":{\"text\":\"Hello world.\"},\"service\":\"summarize\"}",
30
+ "bindingHash": "6b84191b4fce4a19ae8e51b637df8dd5c0d349a57e00d416c729a2ee2a942882"
31
+ },
32
+ {
33
+ "name": "numbers use RFC 8785 formatting",
34
+ "service": "image-generate",
35
+ "input": { "prompt": "a lighthouse at dusk", "width": 1024, "guidance": 7.5, "ratio": 0.5625, "seed": 0 },
36
+ "canonical": "{\"input\":{\"guidance\":7.5,\"prompt\":\"a lighthouse at dusk\",\"ratio\":0.5625,\"seed\":0,\"width\":1024},\"service\":\"image-generate\"}",
37
+ "bindingHash": "197fad97c66e72f70045bcaf8433a6e11185fc76ddc4789c86d82032856e5d5a"
38
+ },
39
+ {
40
+ "name": "nested objects are sorted, arrays keep their order",
41
+ "service": "batch-classify",
42
+ "input": {
43
+ "items": [{ "text": "Second", "id": "b" }, { "text": "First", "id": "a" }],
44
+ "labels": ["spam", "ham"]
45
+ },
46
+ "canonical": "{\"input\":{\"items\":[{\"id\":\"b\",\"text\":\"Second\"},{\"id\":\"a\",\"text\":\"First\"}],\"labels\":[\"spam\",\"ham\"]},\"service\":\"batch-classify\"}",
47
+ "bindingHash": "8695964e2a63c73facb3cc46fb9ed3df5c4e8716ba3e1b909d8b15b3b4473e1b"
48
+ },
49
+ {
50
+ "name": "non-ASCII text, quotes and newlines",
51
+ "service": "text-stats",
52
+ "input": { "text": "Merhaba dünya 🌍\n\"quoted\"" },
53
+ "canonical": "{\"input\":{\"text\":\"Merhaba dünya 🌍\\n\\\"quoted\\\"\"},\"service\":\"text-stats\"}",
54
+ "bindingHash": "2ed5ae38386e9a30c98a4d38d497278d6059360ce066fb30f96fa351ebc25b51"
55
+ },
56
+ {
57
+ "name": "an empty input object",
58
+ "service": "ping",
59
+ "input": {},
60
+ "canonical": "{\"input\":{},\"service\":\"ping\"}",
61
+ "bindingHash": "76658c27819d7e93b8ed15f1a81519b33ae76c1c765185daaf380fa252176000"
62
+ }
63
+ ]
64
+ }