@nuanu-ai/agentify-contracts 0.6.0 → 0.8.0
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/api.d.ts +259 -327
- package/dist/api.js +108 -68
- package/dist/card.d.ts +127 -81
- package/dist/card.js +271 -66
- package/dist/envelope.d.ts +46 -0
- package/dist/index.d.ts +231 -203
- package/dist/index.js +21 -12
- package/dist/merchant.d.ts +21 -120
- package/dist/merchant.js +60 -185
- package/dist/order-status.d.ts +2 -1
- package/dist/order-status.js +13 -1
- package/dist/order.d.ts +17 -0
- package/dist/order.js +13 -0
- package/dist/primitives.d.ts +13 -0
- package/dist/primitives.js +14 -0
- package/dist/quote.d.ts +6 -0
- package/dist/quote.js +10 -0
- package/dist/receipt.d.ts +8 -5
- package/dist/receipt.js +13 -6
- package/dist/results.d.ts +19 -10
- package/dist/results.js +21 -11
- package/dist/seller-site.d.ts +30 -0
- package/dist/seller-site.js +43 -0
- package/dist/ship-to.d.ts +50 -0
- package/dist/ship-to.js +76 -0
- package/dist/shipment.d.ts +42 -0
- package/dist/shipment.js +112 -0
- package/package.json +1 -1
package/dist/results.d.ts
CHANGED
|
@@ -72,10 +72,11 @@ import { z } from "zod";
|
|
|
72
72
|
* it cannot parse rather than as a success it cannot name, and reports the
|
|
73
73
|
* call as having gone wrong. So a word added here has to reach those clients
|
|
74
74
|
* before the gateway starts sending it. The version handshake would be the
|
|
75
|
-
* place to catch that. The published SDK keeps this schema strict, and
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
75
|
+
* place to catch that. The published SDK keeps this schema strict, and once a
|
|
76
|
+
* merchant we do not control runs it, every new word moves `CONTRACT_VERSION`
|
|
77
|
+
* before the gateway sends it. An old worker then stops at its version
|
|
78
|
+
* handshake rather than calling a successful answer unreadable after the
|
|
79
|
+
* merchant has already acted on it (ADR-0006 §2).
|
|
79
80
|
*
|
|
80
81
|
* `debt_closed_by_delivery` says the delivery deadline had already passed and
|
|
81
82
|
* the goods went out anyway, closing a debt instead of completing a sale; the
|
|
@@ -102,13 +103,18 @@ export declare const OrderCallResultSchema: z.ZodEnum<{
|
|
|
102
103
|
* merchant in its own words rather than be flattened into the nearest of
|
|
103
104
|
* three. `refund_already_settled` — the debt was paid back, so there is
|
|
104
105
|
* nothing left to deliver against. `order_already_closed` — the order reached
|
|
105
|
-
* an ending that no call reopens. `not_applicable_in_mode` — the call
|
|
106
|
-
* exist for this card's mode, as
|
|
107
|
-
*
|
|
106
|
+
* an ending that no call reopens. `not_applicable_in_mode` — the call or the
|
|
107
|
+
* answer does not exist for this card's mode, as delivering or refusing
|
|
108
|
+
* separately and taking the order on do not in the synchronous one, where the
|
|
109
|
+
* handler's own answer is the delivery or the refusal.
|
|
108
110
|
* `delivery_does_not_match_card` — the goods are not the ones the card for
|
|
109
111
|
* this order declares it delivers, so nothing was written down; the message
|
|
110
112
|
* says whether the order still stands or has already ended, and the fields
|
|
111
113
|
* that did not fit are named one by one in the error's `problems`.
|
|
114
|
+
* `shipment_already_recorded` — a parcel's shipment is recorded on this order
|
|
115
|
+
* and cannot be changed (ADR-0033); the same shipment sent again is answered
|
|
116
|
+
* as already delivered, and a different one is refused with this, never
|
|
117
|
+
* retryable, because a corrected number taken in silence would vanish.
|
|
112
118
|
*
|
|
113
119
|
* The fourth is the only one of them a merchant fixes rather than records, and
|
|
114
120
|
* it is the reason it is promised rather than left to the open set. It is
|
|
@@ -131,7 +137,7 @@ export declare const OrderCallResultSchema: z.ZodEnum<{
|
|
|
131
137
|
* one and reads it as the failure it is. What it loses is the meaning, which is
|
|
132
138
|
* what this list and the dictionary below carry.
|
|
133
139
|
*/
|
|
134
|
-
export declare const ORDER_CALL_ERROR_CODES: readonly ["refund_already_settled", "order_already_closed", "not_applicable_in_mode", "delivery_does_not_match_card"];
|
|
140
|
+
export declare const ORDER_CALL_ERROR_CODES: readonly ["refund_already_settled", "order_already_closed", "not_applicable_in_mode", "delivery_does_not_match_card", "shipment_already_recorded"];
|
|
135
141
|
/**
|
|
136
142
|
* One thing wrong with what was sent, in a place, in a code and in words.
|
|
137
143
|
*
|
|
@@ -201,9 +207,11 @@ export declare const CARD_REJECTED = "card_rejected";
|
|
|
201
207
|
* Each arrives in the refusal's `problems` with an empty path, beside whatever
|
|
202
208
|
* is wrong with the card, and none of them is cleared by editing the card: the
|
|
203
209
|
* merchant has no name set for buyers to read, no wallet set for their sales to
|
|
204
|
-
* be paid into,
|
|
210
|
+
* be paid into, no approval from the operator for the live catalog, or — on a
|
|
211
|
+
* parcel's card alone (ADR-0033) — no site for their shop, where a buyer takes
|
|
212
|
+
* a parcel that did not arrive. The site is set where the name is. The
|
|
205
213
|
* name is set with a call of the merchant's own (`POST /v0/seller-name`) or in
|
|
206
|
-
* the
|
|
214
|
+
* the dashboard; the wallet in the dashboard's Settings alone, since no key of the
|
|
207
215
|
* merchant's code may say where the money goes; and the approval is the
|
|
208
216
|
* operator's decision, with no call. The name is asked for everywhere, the
|
|
209
217
|
* wallet wherever a payment settles and the approval on the live deployment
|
|
@@ -215,6 +223,7 @@ export declare const MERCHANT_FINDINGS: Readonly<{
|
|
|
215
223
|
readonly NO_SELLER_NAME: "no_seller_name";
|
|
216
224
|
readonly NO_PAYOUT_WALLET: "no_payout_wallet";
|
|
217
225
|
readonly NO_OPERATOR_APPROVAL: "no_operator_approval";
|
|
226
|
+
readonly NO_SELLER_SITE: "no_seller_site";
|
|
218
227
|
}>;
|
|
219
228
|
/** One of the findings about the merchant, as its code travels on the wire. */
|
|
220
229
|
export type MerchantFinding = (typeof MERCHANT_FINDINGS)[keyof typeof MERCHANT_FINDINGS];
|
package/dist/results.js
CHANGED
|
@@ -73,10 +73,11 @@ import { IdentifierSchema } from "./primitives.js";
|
|
|
73
73
|
* it cannot parse rather than as a success it cannot name, and reports the
|
|
74
74
|
* call as having gone wrong. So a word added here has to reach those clients
|
|
75
75
|
* before the gateway starts sending it. The version handshake would be the
|
|
76
|
-
* place to catch that. The published SDK keeps this schema strict, and
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
76
|
+
* place to catch that. The published SDK keeps this schema strict, and once a
|
|
77
|
+
* merchant we do not control runs it, every new word moves `CONTRACT_VERSION`
|
|
78
|
+
* before the gateway sends it. An old worker then stops at its version
|
|
79
|
+
* handshake rather than calling a successful answer unreadable after the
|
|
80
|
+
* merchant has already acted on it (ADR-0006 §2).
|
|
80
81
|
*
|
|
81
82
|
* `debt_closed_by_delivery` says the delivery deadline had already passed and
|
|
82
83
|
* the goods went out anyway, closing a debt instead of completing a sale; the
|
|
@@ -103,13 +104,18 @@ export const OrderCallResultSchema = z.enum(ORDER_CALL_RESULTS);
|
|
|
103
104
|
* merchant in its own words rather than be flattened into the nearest of
|
|
104
105
|
* three. `refund_already_settled` — the debt was paid back, so there is
|
|
105
106
|
* nothing left to deliver against. `order_already_closed` — the order reached
|
|
106
|
-
* an ending that no call reopens. `not_applicable_in_mode` — the call
|
|
107
|
-
* exist for this card's mode, as
|
|
108
|
-
*
|
|
107
|
+
* an ending that no call reopens. `not_applicable_in_mode` — the call or the
|
|
108
|
+
* answer does not exist for this card's mode, as delivering or refusing
|
|
109
|
+
* separately and taking the order on do not in the synchronous one, where the
|
|
110
|
+
* handler's own answer is the delivery or the refusal.
|
|
109
111
|
* `delivery_does_not_match_card` — the goods are not the ones the card for
|
|
110
112
|
* this order declares it delivers, so nothing was written down; the message
|
|
111
113
|
* says whether the order still stands or has already ended, and the fields
|
|
112
114
|
* that did not fit are named one by one in the error's `problems`.
|
|
115
|
+
* `shipment_already_recorded` — a parcel's shipment is recorded on this order
|
|
116
|
+
* and cannot be changed (ADR-0033); the same shipment sent again is answered
|
|
117
|
+
* as already delivered, and a different one is refused with this, never
|
|
118
|
+
* retryable, because a corrected number taken in silence would vanish.
|
|
113
119
|
*
|
|
114
120
|
* The fourth is the only one of them a merchant fixes rather than records, and
|
|
115
121
|
* it is the reason it is promised rather than left to the open set. It is
|
|
@@ -137,6 +143,7 @@ export const ORDER_CALL_ERROR_CODES = Object.freeze([
|
|
|
137
143
|
"order_already_closed",
|
|
138
144
|
"not_applicable_in_mode",
|
|
139
145
|
"delivery_does_not_match_card",
|
|
146
|
+
"shipment_already_recorded",
|
|
140
147
|
]);
|
|
141
148
|
/**
|
|
142
149
|
* One thing wrong with what was sent, in a place, in a code and in words.
|
|
@@ -175,7 +182,7 @@ export const ProblemSchema = z
|
|
|
175
182
|
* are described for the export for the reason the error's codes are.
|
|
176
183
|
*/
|
|
177
184
|
code: z.string().regex(/\S/, "a finding carries a code").meta({
|
|
178
|
-
description: 'What kind of finding it is, for the program that reads it. The set is open: a finding about a field of what was sent carries the name the check gave it.
|
|
185
|
+
description: 'What kind of finding it is, for the program that reads it. The set is open: a finding about a field of what was sent carries the name the check gave it. Four are promised, always with an empty path, and each says the merchant rather than the card is missing something, so no edit to the card clears it: "no_seller_name" (no name set for buyers to read), "no_payout_wallet" (no wallet set for the sales to be paid into, asked for wherever a payment settles), "no_operator_approval" (the operator has not admitted this merchant to the live catalog, which only the operator can change) and "no_seller_site" (no site set for the merchant\'s shop, asked for on a parcel\'s card alone, because a parcel that does not arrive is a question its buyer takes there).',
|
|
179
186
|
}),
|
|
180
187
|
/** The same finding in words, for the person who has to fix the card. */
|
|
181
188
|
message: z.string().regex(/\S/, "a finding carries an explanation a person can read"),
|
|
@@ -211,7 +218,7 @@ export const CallErrorSchema = z.strictObject({
|
|
|
211
218
|
code: z.string().regex(/\S/, "an error carries a code").meta({
|
|
212
219
|
// Same reason as the refusal code: the dictionary travels with the field
|
|
213
220
|
// or it does not reach the reader the export exists for.
|
|
214
|
-
description: 'Why the call did not go through. The set is open, and
|
|
221
|
+
description: 'Why the call did not go through. The set is open, and six are promised to mean one thing each — but not on the same calls, and which call a code can arrive on is part of what is promised about it. Publishing a card is refused with one word and no other: "card_rejected" (the card was not published, and every finding standing between it and the catalog is named in the error\'s problems — the fields at fault, and the merchant\'s own missing name, payout wallet or live operator approval where those are what is missing, and for a parcel\'s card their shop\'s site; it is never retryable, because the same card gets the same answer and what changes the outcome is fixing what the problems name). The calls that close an order — delivering, refusing, taking one on — are refused with the other four, and never with the first: "refund_already_settled" (the debt was paid back, so there is nothing left to deliver against). "order_already_closed" (the order reached an ending that no call reopens). "not_applicable_in_mode" (the call or the answer does not exist for this card\'s mode — in the synchronous one the handler\'s own answer is the delivery or the refusal, so delivering or refusing separately does not exist there, and neither does taking an order on while it waits for its goods, which promises them later through a call that mode does not have). "delivery_does_not_match_card" (the goods are not the ones the card for this order declares it delivers — nothing was written down, the problems name the fields that did not fit, and the message says whether the order still stands or has already ended). The last of those is retryable in a different sense from a lost connection: the call arrived and was understood, so sending the same goods again gives the same refusal, and what clears it is delivering what the card declares. It is not retryable at all where the order has already ended, because there is nothing left to deliver against. On a parcel\'s order the deliver call has one more: "shipment_already_recorded" (a shipment is recorded on this order and cannot be changed; the same one sent again succeeds as already delivered, and this refusal is never retryable).',
|
|
215
222
|
}),
|
|
216
223
|
message: z.string().regex(/\S/, "an error carries an explanation a person can read"),
|
|
217
224
|
retryable: z.boolean(),
|
|
@@ -245,9 +252,11 @@ export const CARD_REJECTED = "card_rejected";
|
|
|
245
252
|
* Each arrives in the refusal's `problems` with an empty path, beside whatever
|
|
246
253
|
* is wrong with the card, and none of them is cleared by editing the card: the
|
|
247
254
|
* merchant has no name set for buyers to read, no wallet set for their sales to
|
|
248
|
-
* be paid into,
|
|
255
|
+
* be paid into, no approval from the operator for the live catalog, or — on a
|
|
256
|
+
* parcel's card alone (ADR-0033) — no site for their shop, where a buyer takes
|
|
257
|
+
* a parcel that did not arrive. The site is set where the name is. The
|
|
249
258
|
* name is set with a call of the merchant's own (`POST /v0/seller-name`) or in
|
|
250
|
-
* the
|
|
259
|
+
* the dashboard; the wallet in the dashboard's Settings alone, since no key of the
|
|
251
260
|
* merchant's code may say where the money goes; and the approval is the
|
|
252
261
|
* operator's decision, with no call. The name is asked for everywhere, the
|
|
253
262
|
* wallet wherever a payment settles and the approval on the live deployment
|
|
@@ -259,6 +268,7 @@ export const MERCHANT_FINDINGS = Object.freeze({
|
|
|
259
268
|
NO_SELLER_NAME: "no_seller_name",
|
|
260
269
|
NO_PAYOUT_WALLET: "no_payout_wallet",
|
|
261
270
|
NO_OPERATOR_APPROVAL: "no_operator_approval",
|
|
271
|
+
NO_SELLER_SITE: "no_seller_site",
|
|
262
272
|
});
|
|
263
273
|
/**
|
|
264
274
|
* The error a refused publish carries: the shared shape, with the findings made
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The address of a seller's own shop, held where both the card that names it
|
|
3
|
+
* and a parcel's tracking page, whose host is held to the same rule, can read
|
|
4
|
+
* it (ADR-0033, ADR-0034).
|
|
5
|
+
*/
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
/**
|
|
8
|
+
* The address of a seller's own shop on the web, where an agent takes what an
|
|
9
|
+
* order cannot answer (ADR-0034).
|
|
10
|
+
*
|
|
11
|
+
* Only an https origin, because anything after the host — a path, a query, a
|
|
12
|
+
* fragment — would be text of the merchant's own reaching every agent that
|
|
13
|
+
* reads the card, which is the free text the decision refuses. The host is the
|
|
14
|
+
* one part the merchant writes, so it is held to a domain name too: labels of
|
|
15
|
+
* letters, digits and hyphens, at most 253 characters, ending in a zone of
|
|
16
|
+
* letters or a punycode one. Anything a URL parser keeps as written would
|
|
17
|
+
* otherwise pass — a sentence of instructions, a quote, a host of any length —
|
|
18
|
+
* and so would a single word or an IP address, which point other people's
|
|
19
|
+
* agents into somebody's own network. What the pattern cannot do is tell a
|
|
20
|
+
* public name from a private one: a name in a zone kept for local networks,
|
|
21
|
+
* or a public one whose address leads into one, passes, and so does a
|
|
22
|
+
* hyphenated sentence of up to 253 characters that is a valid name. That is
|
|
23
|
+
* why an agent is told, beside every site, that nobody checked it.
|
|
24
|
+
*
|
|
25
|
+
* The pattern says all that in a form the JSON Schema export keeps, and it
|
|
26
|
+
* stops the check where it fails, so an address copied with a slash at the end
|
|
27
|
+
* is told once. Asking the URL parser for the origin catches what the pattern
|
|
28
|
+
* leaves, such as a punycode label the parser would write differently.
|
|
29
|
+
*/
|
|
30
|
+
export declare const SellerSiteSchema: z.ZodString;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The address of a seller's own shop, held where both the card that names it
|
|
3
|
+
* and a parcel's tracking page, whose host is held to the same rule, can read
|
|
4
|
+
* it (ADR-0033, ADR-0034).
|
|
5
|
+
*/
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
/** What a seller's site is held to, said once for the refusal and the description. */
|
|
8
|
+
const SITE_FORM = "a seller's site is https:// and the domain name of their shop, with nothing after it — https://shop.example: in lower case, a domain name rather than an IP address or a single word, with no path, query, fragment, port, credentials or trailing slash";
|
|
9
|
+
/**
|
|
10
|
+
* The address of a seller's own shop on the web, where an agent takes what an
|
|
11
|
+
* order cannot answer (ADR-0034).
|
|
12
|
+
*
|
|
13
|
+
* Only an https origin, because anything after the host — a path, a query, a
|
|
14
|
+
* fragment — would be text of the merchant's own reaching every agent that
|
|
15
|
+
* reads the card, which is the free text the decision refuses. The host is the
|
|
16
|
+
* one part the merchant writes, so it is held to a domain name too: labels of
|
|
17
|
+
* letters, digits and hyphens, at most 253 characters, ending in a zone of
|
|
18
|
+
* letters or a punycode one. Anything a URL parser keeps as written would
|
|
19
|
+
* otherwise pass — a sentence of instructions, a quote, a host of any length —
|
|
20
|
+
* and so would a single word or an IP address, which point other people's
|
|
21
|
+
* agents into somebody's own network. What the pattern cannot do is tell a
|
|
22
|
+
* public name from a private one: a name in a zone kept for local networks,
|
|
23
|
+
* or a public one whose address leads into one, passes, and so does a
|
|
24
|
+
* hyphenated sentence of up to 253 characters that is a valid name. That is
|
|
25
|
+
* why an agent is told, beside every site, that nobody checked it.
|
|
26
|
+
*
|
|
27
|
+
* The pattern says all that in a form the JSON Schema export keeps, and it
|
|
28
|
+
* stops the check where it fails, so an address copied with a slash at the end
|
|
29
|
+
* is told once. Asking the URL parser for the origin catches what the pattern
|
|
30
|
+
* leaves, such as a punycode label the parser would write differently.
|
|
31
|
+
*/
|
|
32
|
+
export const SellerSiteSchema = z
|
|
33
|
+
.string()
|
|
34
|
+
.regex(/^https:\/\/(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+(?:[a-z]{2,63}|xn--[a-z0-9-]{1,59})$/, { message: SITE_FORM, abort: true })
|
|
35
|
+
.refine((site) => {
|
|
36
|
+
try {
|
|
37
|
+
return new URL(site).origin === site;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}, SITE_FORM)
|
|
43
|
+
.meta({ description: `The https origin of a seller's own shop: ${SITE_FORM}.` });
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The address a parcel goes to (ADR-0032).
|
|
3
|
+
*
|
|
4
|
+
* An agent buying a parcel sends it beside the purchase, in a shape of ours
|
|
5
|
+
* with the Agentic Commerce Protocol's names, because that is the address an
|
|
6
|
+
* agent already writes: `name`, `line_one`, `line_two`, `city`, `state`,
|
|
7
|
+
* `postal_code`, `country`, `phone_number`. One `name`, because a person may
|
|
8
|
+
* have one name and a split name cannot be recovered without guessing; no
|
|
9
|
+
* company, because the second line holds it; a phone, because the carriers a
|
|
10
|
+
* merchant hands a parcel to mostly ask for one and the merchant cannot ask
|
|
11
|
+
* for it themselves.
|
|
12
|
+
*
|
|
13
|
+
* The door checks the shape and no more. Whether a state or a postal code is
|
|
14
|
+
* needed in this country, and whether the merchant ships there at all, is the
|
|
15
|
+
* merchant's to answer in their price check: a per-country table of what is
|
|
16
|
+
* required is a table this contract would keep wrong.
|
|
17
|
+
*
|
|
18
|
+
* The address is the buyer's and passes through Agentify. The merchant's price
|
|
19
|
+
* question receives only the locality — where the parcel goes, not to whom —
|
|
20
|
+
* and the full address reaches the merchant only once the order is paid. Once
|
|
21
|
+
* the merchant takes the order on, or the order ends without them, Agentify
|
|
22
|
+
* erases its copy, and the order reads only when that happened.
|
|
23
|
+
*/
|
|
24
|
+
import { z } from "zod";
|
|
25
|
+
export declare const ShipToSchema: z.ZodObject<{
|
|
26
|
+
name: z.ZodString;
|
|
27
|
+
line_one: z.ZodString;
|
|
28
|
+
line_two: z.ZodOptional<z.ZodString>;
|
|
29
|
+
city: z.ZodString;
|
|
30
|
+
state: z.ZodOptional<z.ZodString>;
|
|
31
|
+
postal_code: z.ZodOptional<z.ZodString>;
|
|
32
|
+
country: z.ZodString;
|
|
33
|
+
phone_number: z.ZodString;
|
|
34
|
+
}, z.core.$strict>;
|
|
35
|
+
/** Where a parcel goes as its price is asked: the place, not the person. */
|
|
36
|
+
export declare const ShipToLocalitySchema: z.ZodObject<{
|
|
37
|
+
country: z.ZodString;
|
|
38
|
+
state: z.ZodOptional<z.ZodString>;
|
|
39
|
+
city: z.ZodString;
|
|
40
|
+
postal_code: z.ZodOptional<z.ZodString>;
|
|
41
|
+
}, z.core.$strict>;
|
|
42
|
+
/** An address Agentify no longer holds: only when it let it go. */
|
|
43
|
+
export declare const ErasedShipToSchema: z.ZodObject<{
|
|
44
|
+
erased_at: z.ZodISODateTime;
|
|
45
|
+
}, z.core.$strict>;
|
|
46
|
+
export type ShipTo = z.infer<typeof ShipToSchema>;
|
|
47
|
+
export type ShipToLocality = z.infer<typeof ShipToLocalitySchema>;
|
|
48
|
+
export type ErasedShipTo = z.infer<typeof ErasedShipToSchema>;
|
|
49
|
+
/** The locality of an address, with what it did not give left out. */
|
|
50
|
+
export declare const localityOf: (address: ShipTo) => ShipToLocality;
|
package/dist/ship-to.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The address a parcel goes to (ADR-0032).
|
|
3
|
+
*
|
|
4
|
+
* An agent buying a parcel sends it beside the purchase, in a shape of ours
|
|
5
|
+
* with the Agentic Commerce Protocol's names, because that is the address an
|
|
6
|
+
* agent already writes: `name`, `line_one`, `line_two`, `city`, `state`,
|
|
7
|
+
* `postal_code`, `country`, `phone_number`. One `name`, because a person may
|
|
8
|
+
* have one name and a split name cannot be recovered without guessing; no
|
|
9
|
+
* company, because the second line holds it; a phone, because the carriers a
|
|
10
|
+
* merchant hands a parcel to mostly ask for one and the merchant cannot ask
|
|
11
|
+
* for it themselves.
|
|
12
|
+
*
|
|
13
|
+
* The door checks the shape and no more. Whether a state or a postal code is
|
|
14
|
+
* needed in this country, and whether the merchant ships there at all, is the
|
|
15
|
+
* merchant's to answer in their price check: a per-country table of what is
|
|
16
|
+
* required is a table this contract would keep wrong.
|
|
17
|
+
*
|
|
18
|
+
* The address is the buyer's and passes through Agentify. The merchant's price
|
|
19
|
+
* question receives only the locality — where the parcel goes, not to whom —
|
|
20
|
+
* and the full address reaches the merchant only once the order is paid. Once
|
|
21
|
+
* the merchant takes the order on, or the order ends without them, Agentify
|
|
22
|
+
* erases its copy, and the order reads only when that happened.
|
|
23
|
+
*/
|
|
24
|
+
import { z } from "zod";
|
|
25
|
+
import { TimestampSchema } from "./primitives.js";
|
|
26
|
+
/** A part of an address that has to say something. */
|
|
27
|
+
const said = (what) => z.string().regex(/\S/, `${what} is not blank`);
|
|
28
|
+
const CountrySchema = z
|
|
29
|
+
.string()
|
|
30
|
+
.regex(/^[A-Z]{2}$/, "a country is two capital letters, ISO 3166-1 alpha-2, such as ID or US");
|
|
31
|
+
const StateSchema = z
|
|
32
|
+
.string()
|
|
33
|
+
.regex(/^[A-Z0-9]{1,3}$/, "a state is its subdivision code without the country in front, such as CA, NSW or BA");
|
|
34
|
+
export const ShipToSchema = z
|
|
35
|
+
.strictObject({
|
|
36
|
+
/** Who receives the parcel, as one name. */
|
|
37
|
+
name: said("a name"),
|
|
38
|
+
line_one: said("the first line of an address"),
|
|
39
|
+
/** A flat, a building, a company. Absent where there is none. */
|
|
40
|
+
line_two: said("the second line of an address").optional(),
|
|
41
|
+
city: said("a city"),
|
|
42
|
+
/** The subdivision code without the country, where the country has them. */
|
|
43
|
+
state: StateSchema.optional(),
|
|
44
|
+
postal_code: said("a postal code").optional(),
|
|
45
|
+
country: CountrySchema,
|
|
46
|
+
phone_number: said("a phone number"),
|
|
47
|
+
})
|
|
48
|
+
.meta({
|
|
49
|
+
description: "Where a parcel goes, in the Agentic Commerce Protocol's names. A name, a first line, a city, a country and a phone are required; the country is ISO 3166-1 alpha-2 and a state, where given, is its subdivision code without the country in front. Whether a state or a postal code is needed here, and whether the merchant ships to this place, is the merchant's to answer. The address passes through Agentify: the merchant's price question receives only its locality, the merchant receives the whole of it once the order is paid, and Agentify erases its copy once the merchant takes the order on, or the order ends without them.",
|
|
50
|
+
});
|
|
51
|
+
/** Where a parcel goes as its price is asked: the place, not the person. */
|
|
52
|
+
export const ShipToLocalitySchema = z
|
|
53
|
+
.strictObject({
|
|
54
|
+
country: CountrySchema,
|
|
55
|
+
state: StateSchema.optional(),
|
|
56
|
+
city: said("a city"),
|
|
57
|
+
postal_code: said("a postal code").optional(),
|
|
58
|
+
})
|
|
59
|
+
.meta({
|
|
60
|
+
description: "Where a parcel goes, as its price is asked: the country, the state, the city and the postal code of the address, and nothing about who receives it. A price question reaches a merchant for purchases that are never made, and a shipping rate needs the place alone.",
|
|
61
|
+
});
|
|
62
|
+
/** An address Agentify no longer holds: only when it let it go. */
|
|
63
|
+
export const ErasedShipToSchema = z
|
|
64
|
+
.strictObject({
|
|
65
|
+
erased_at: TimestampSchema,
|
|
66
|
+
})
|
|
67
|
+
.meta({
|
|
68
|
+
description: "An address Agentify has erased, because nothing of Agentify's needs it any more: the merchant took the order on or recorded its shipment, or the order ended or came to owe a refund without being taken on. Only when it was erased is kept, and nothing of what it was. The merchant holds the address only as they stored it from the paid order, and an order that ended before it was paid never gave it to them.",
|
|
69
|
+
});
|
|
70
|
+
/** The locality of an address, with what it did not give left out. */
|
|
71
|
+
export const localityOf = (address) => ({
|
|
72
|
+
country: address.country,
|
|
73
|
+
...(address.state === undefined ? {} : { state: address.state }),
|
|
74
|
+
city: address.city,
|
|
75
|
+
...(address.postal_code === undefined ? {} : { postal_code: address.postal_code }),
|
|
76
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A parcel's shipment (ADR-0033): what the merchant says once a carrier has the
|
|
3
|
+
* parcel, and what the agent reads afterwards.
|
|
4
|
+
*
|
|
5
|
+
* It is the body of the `deliver` call on a parcel's order, in place of goods.
|
|
6
|
+
* A carrier, required — a carrier's name, or the shop's own courier — and a
|
|
7
|
+
* tracking number, whose key is required and whose value is null for a parcel
|
|
8
|
+
* that has none, never an empty string: a shop's own courier can sell through
|
|
9
|
+
* this mode, and "no number" has to be said rather than forgotten. A tracking
|
|
10
|
+
* page and an expected delivery window are optional.
|
|
11
|
+
*
|
|
12
|
+
* All of it is the merchant's claim and none of it is checked against a
|
|
13
|
+
* carrier, so the door holds it to its shape and to plain words on one line,
|
|
14
|
+
* as it holds a seller's name (ADR-0017): every agent that bought the parcel
|
|
15
|
+
* reads it exactly as it was written. The instant the parcel shipped is the
|
|
16
|
+
* gateway's to stamp when it records the shipment, never the merchant's to
|
|
17
|
+
* send.
|
|
18
|
+
*/
|
|
19
|
+
import { z } from "zod";
|
|
20
|
+
/** What the merchant records when a carrier has the parcel. */
|
|
21
|
+
export declare const ShipmentSchema: z.ZodObject<{
|
|
22
|
+
carrier: z.ZodString;
|
|
23
|
+
tracking_number: z.ZodNullable<z.ZodString>;
|
|
24
|
+
tracking_url: z.ZodOptional<z.ZodString>;
|
|
25
|
+
estimated_delivery: z.ZodOptional<z.ZodObject<{
|
|
26
|
+
earliest: z.ZodISODateTime;
|
|
27
|
+
latest: z.ZodISODateTime;
|
|
28
|
+
}, z.core.$strict>>;
|
|
29
|
+
}, z.core.$strict>;
|
|
30
|
+
/** A shipment as the agent reads it: what the merchant said, and when it was recorded. */
|
|
31
|
+
export declare const RecordedShipmentSchema: z.ZodObject<{
|
|
32
|
+
carrier: z.ZodString;
|
|
33
|
+
tracking_number: z.ZodNullable<z.ZodString>;
|
|
34
|
+
tracking_url: z.ZodOptional<z.ZodString>;
|
|
35
|
+
estimated_delivery: z.ZodOptional<z.ZodObject<{
|
|
36
|
+
earliest: z.ZodISODateTime;
|
|
37
|
+
latest: z.ZodISODateTime;
|
|
38
|
+
}, z.core.$strict>>;
|
|
39
|
+
shipped_at: z.ZodISODateTime;
|
|
40
|
+
}, z.core.$strict>;
|
|
41
|
+
export type Shipment = z.infer<typeof ShipmentSchema>;
|
|
42
|
+
export type RecordedShipment = z.infer<typeof RecordedShipmentSchema>;
|
package/dist/shipment.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A parcel's shipment (ADR-0033): what the merchant says once a carrier has the
|
|
3
|
+
* parcel, and what the agent reads afterwards.
|
|
4
|
+
*
|
|
5
|
+
* It is the body of the `deliver` call on a parcel's order, in place of goods.
|
|
6
|
+
* A carrier, required — a carrier's name, or the shop's own courier — and a
|
|
7
|
+
* tracking number, whose key is required and whose value is null for a parcel
|
|
8
|
+
* that has none, never an empty string: a shop's own courier can sell through
|
|
9
|
+
* this mode, and "no number" has to be said rather than forgotten. A tracking
|
|
10
|
+
* page and an expected delivery window are optional.
|
|
11
|
+
*
|
|
12
|
+
* All of it is the merchant's claim and none of it is checked against a
|
|
13
|
+
* carrier, so the door holds it to its shape and to plain words on one line,
|
|
14
|
+
* as it holds a seller's name (ADR-0017): every agent that bought the parcel
|
|
15
|
+
* reads it exactly as it was written. The instant the parcel shipped is the
|
|
16
|
+
* gateway's to stamp when it records the shipment, never the merchant's to
|
|
17
|
+
* send.
|
|
18
|
+
*/
|
|
19
|
+
import { z } from "zod";
|
|
20
|
+
import { notPlainTextIn } from "./plain-text.js";
|
|
21
|
+
import { TimestampSchema } from "./primitives.js";
|
|
22
|
+
import { SellerSiteSchema } from "./seller-site.js";
|
|
23
|
+
/** How long a carrier's name or a tracking number may be. */
|
|
24
|
+
const SHIPMENT_TEXT_MAX = 100;
|
|
25
|
+
/**
|
|
26
|
+
* A short piece of the merchant's own words, as an agent will read it. The
|
|
27
|
+
* plain-text rule is a refinement, which no JSON Schema carries, so the
|
|
28
|
+
* description says it.
|
|
29
|
+
*/
|
|
30
|
+
const shortPlainText = (what, says) => z
|
|
31
|
+
.string()
|
|
32
|
+
.min(1, `${what} must not be empty`)
|
|
33
|
+
.max(SHIPMENT_TEXT_MAX, `${what} is at most ${SHIPMENT_TEXT_MAX} characters`)
|
|
34
|
+
.regex(/^\S(?:.*\S)?$/, `${what} must not be blank or padded with spaces`)
|
|
35
|
+
.superRefine((text, ctx) => {
|
|
36
|
+
for (const phrase of notPlainTextIn(text, "one line")) {
|
|
37
|
+
ctx.addIssue({
|
|
38
|
+
code: "custom",
|
|
39
|
+
message: `${what} carries ${phrase}, and it is plain text, which an agent reads exactly as it is written`,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
.meta({
|
|
44
|
+
description: `${says}: at most ${SHIPMENT_TEXT_MAX} characters of plain text on one line, neither blank nor padded with spaces. HTML markup and character references are refused rather than read as words.`,
|
|
45
|
+
});
|
|
46
|
+
/** What a tracking address is held to, said once for the refusal and the description. */
|
|
47
|
+
const TRACKING_FORM = "a tracking address is a whole https address on a domain name, written as an address parser writes it back: at most 500 characters, a host in lower case and in its ASCII form (punycode for a name in another script), with no spaces, line breaks, quotes or markup, and no credentials, port or IP address. It is an address to open, never words to act on";
|
|
48
|
+
/**
|
|
49
|
+
* Where a parcel can be followed. It is an address and nothing else, because
|
|
50
|
+
* every agent that bought the parcel reads it: a parser that would rewrite it
|
|
51
|
+
* — a space, a line break, markup in its query, capitals in its host — means
|
|
52
|
+
* there are words of the merchant's in it, and those are refused rather than
|
|
53
|
+
* cleaned, as the rest of the shipment's text is. What its path and query say
|
|
54
|
+
* is the carrier's, and it is an address to open, never words to act on.
|
|
55
|
+
*/
|
|
56
|
+
const TrackingUrlSchema = z
|
|
57
|
+
.string()
|
|
58
|
+
.max(500, TRACKING_FORM)
|
|
59
|
+
.regex(/^https:\/\//, { message: TRACKING_FORM, abort: true })
|
|
60
|
+
.refine((address) => {
|
|
61
|
+
try {
|
|
62
|
+
const parsed = new URL(address);
|
|
63
|
+
// A parser writes a slash after a bare host, before a query or a
|
|
64
|
+
// fragment as well as at the end, and that alone is not a rewrite.
|
|
65
|
+
const slashed = address.replace(/^(https:\/\/[^/?#]*)(?=[?#]|$)/, "$1/");
|
|
66
|
+
return ((parsed.href === address || parsed.href === slashed) &&
|
|
67
|
+
parsed.username === "" &&
|
|
68
|
+
parsed.password === "" &&
|
|
69
|
+
// Its origin is held as a seller's site is (ADR-0034): https, a
|
|
70
|
+
// domain name in lower case, no port.
|
|
71
|
+
SellerSiteSchema.safeParse(parsed.origin).success);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}, TRACKING_FORM)
|
|
77
|
+
.meta({ description: `Where the parcel can be followed: ${TRACKING_FORM}.` });
|
|
78
|
+
/** When the merchant expects the parcel to arrive, as a window. */
|
|
79
|
+
const EstimatedDeliverySchema = z
|
|
80
|
+
.strictObject({
|
|
81
|
+
earliest: TimestampSchema,
|
|
82
|
+
latest: TimestampSchema,
|
|
83
|
+
})
|
|
84
|
+
.refine((window) => Date.parse(window.earliest) <= Date.parse(window.latest), {
|
|
85
|
+
path: ["earliest"],
|
|
86
|
+
message: "an expected delivery's earliest instant comes no later than its latest",
|
|
87
|
+
})
|
|
88
|
+
.meta({
|
|
89
|
+
description: "When the merchant expects the parcel to arrive, as a window: the earliest instant comes no later than the latest.",
|
|
90
|
+
});
|
|
91
|
+
/** What the merchant records when a carrier has the parcel. */
|
|
92
|
+
export const ShipmentSchema = z
|
|
93
|
+
.strictObject({
|
|
94
|
+
/** Who carries it: a carrier's name, or the shop's own courier. */
|
|
95
|
+
carrier: shortPlainText("a carrier", "Who carries the parcel: a carrier's name, or the shop's own courier"),
|
|
96
|
+
/** The carrier's number for the parcel, or null where there is none. */
|
|
97
|
+
tracking_number: shortPlainText("a tracking number", "The carrier's number for the parcel, or null where there is none").nullable(),
|
|
98
|
+
/** Where the parcel can be followed, where the carrier has such a page. */
|
|
99
|
+
tracking_url: TrackingUrlSchema.optional(),
|
|
100
|
+
/** When the merchant expects it to arrive, where they know. */
|
|
101
|
+
estimated_delivery: EstimatedDeliverySchema.optional(),
|
|
102
|
+
})
|
|
103
|
+
.meta({
|
|
104
|
+
description: 'A parcel\'s shipment, as the merchant records it with the deliver call once a carrier has the parcel. "carrier" is required: a carrier\'s name, or the shop\'s own courier. "tracking_number" is required as a key and is null for a parcel that has no number, never an empty string. "tracking_url" is an https page where the parcel can be followed, written as an address and nothing else, and "estimated_delivery" the window it is expected in; both are optional. All of it is the merchant\'s claim, which Agentify does not check against any carrier, and its words are plain text on one line. The instant it shipped is not sent: Agentify records it.',
|
|
105
|
+
});
|
|
106
|
+
/** A shipment as the agent reads it: what the merchant said, and when it was recorded. */
|
|
107
|
+
export const RecordedShipmentSchema = ShipmentSchema.extend({
|
|
108
|
+
/** When Agentify recorded the shipment. */
|
|
109
|
+
shipped_at: TimestampSchema,
|
|
110
|
+
}).meta({
|
|
111
|
+
description: "A parcel's shipment as Agentify recorded it: what the merchant said — the carrier, the tracking number or null where there is none, and where they gave them a tracking page and an expected delivery window — and \"shipped_at\", the instant Agentify recorded it. All of it but that instant is the merchant's claim, which Agentify did not check against any carrier. It is the last thing Agentify knows about the parcel: whether it arrives is between the buyer and the merchant, and the seller's site is where to ask.",
|
|
112
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nuanu-ai/agentify-contracts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The Agentify wire contract as code: the schemas for cards, orders, price checks and receipts that the gateway and the merchant SDK both read.",
|
|
6
6
|
"keywords": [
|