@nuanu-ai/agentify-contracts 0.7.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/receipt.js CHANGED
@@ -41,16 +41,23 @@ import { IdentifierSchema, SalePriceSchema, TimestampSchema } from "./primitives
41
41
  * if it did not, there was never anything to record. That is the same promise
42
42
  * the portal makes to the buyer in words: we say so when we learn.
43
43
  *
44
- * That leaves four: paid and still running, paid and delivered, paid and owed
45
- * back, paid and paid back.
44
+ * That leaves five: paid and still running, paid and delivered, a paid parcel
45
+ * shipped (ADR-0033), paid and owed back, paid and paid back.
46
46
  *
47
47
  * What this list does not say is when a receipt is written at all, and a reader
48
48
  * should not infer it from here. That is the gateway's, and a gateway that
49
- * writes one only as goods are released will only ever produce `delivered` —
50
- * so a consumer must not read the presence of these four as a promise that a
51
- * receipt exists for every payment that executed.
49
+ * writes one only as goods are released or a parcel ships will only ever
50
+ * produce `delivered` and `shipped` — so a consumer must not read the presence
51
+ * of these five as a promise that a receipt exists for every payment that
52
+ * executed.
52
53
  */
53
- export const ReceiptOutcomeSchema = z.enum(["in_progress", "delivered", "refund_due", "refunded"]);
54
+ export const ReceiptOutcomeSchema = z.enum([
55
+ "in_progress",
56
+ "delivered",
57
+ "shipped",
58
+ "refund_due",
59
+ "refunded",
60
+ ]);
54
61
  export const ReceiptSchema = z.strictObject({
55
62
  /**
56
63
  * The receipt's own identifier. In the modes where the payment goes first,
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 every
76
- * new word moves `CONTRACT_VERSION` before the gateway sends it. An old worker
77
- * then stops at its version handshake rather than calling a successful answer
78
- * unreadable after the merchant has already acted on it (ADR-0006).
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
@@ -110,6 +111,10 @@ export declare const OrderCallResultSchema: z.ZodEnum<{
110
111
  * this order declares it delivers, so nothing was written down; the message
111
112
  * says whether the order still stands or has already ended, and the fields
112
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.
113
118
  *
114
119
  * The fourth is the only one of them a merchant fixes rather than records, and
115
120
  * it is the reason it is promised rather than left to the open set. It is
@@ -132,7 +137,7 @@ export declare const OrderCallResultSchema: z.ZodEnum<{
132
137
  * one and reads it as the failure it is. What it loses is the meaning, which is
133
138
  * what this list and the dictionary below carry.
134
139
  */
135
- 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"];
136
141
  /**
137
142
  * One thing wrong with what was sent, in a place, in a code and in words.
138
143
  *
@@ -202,7 +207,9 @@ export declare const CARD_REJECTED = "card_rejected";
202
207
  * Each arrives in the refusal's `problems` with an empty path, beside whatever
203
208
  * is wrong with the card, and none of them is cleared by editing the card: the
204
209
  * merchant has no name set for buyers to read, no wallet set for their sales to
205
- * be paid into, or no approval from the operator for the live catalog. The
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
206
213
  * name is set with a call of the merchant's own (`POST /v0/seller-name`) or in
207
214
  * the dashboard; the wallet in the dashboard's Settings alone, since no key of the
208
215
  * merchant's code may say where the money goes; and the approval is the
@@ -216,6 +223,7 @@ export declare const MERCHANT_FINDINGS: Readonly<{
216
223
  readonly NO_SELLER_NAME: "no_seller_name";
217
224
  readonly NO_PAYOUT_WALLET: "no_payout_wallet";
218
225
  readonly NO_OPERATOR_APPROVAL: "no_operator_approval";
226
+ readonly NO_SELLER_SITE: "no_seller_site";
219
227
  }>;
220
228
  /** One of the findings about the merchant, as its code travels on the wire. */
221
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 every
77
- * new word moves `CONTRACT_VERSION` before the gateway sends it. An old worker
78
- * then stops at its version handshake rather than calling a successful answer
79
- * unreadable after the merchant has already acted on it (ADR-0006).
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
@@ -111,6 +112,10 @@ export const OrderCallResultSchema = z.enum(ORDER_CALL_RESULTS);
111
112
  * this order declares it delivers, so nothing was written down; the message
112
113
  * says whether the order still stands or has already ended, and the fields
113
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.
114
119
  *
115
120
  * The fourth is the only one of them a merchant fixes rather than records, and
116
121
  * it is the reason it is promised rather than left to the open set. It is
@@ -138,6 +143,7 @@ export const ORDER_CALL_ERROR_CODES = Object.freeze([
138
143
  "order_already_closed",
139
144
  "not_applicable_in_mode",
140
145
  "delivery_does_not_match_card",
146
+ "shipment_already_recorded",
141
147
  ]);
142
148
  /**
143
149
  * One thing wrong with what was sent, in a place, in a code and in words.
@@ -176,7 +182,7 @@ export const ProblemSchema = z
176
182
  * are described for the export for the reason the error's codes are.
177
183
  */
178
184
  code: z.string().regex(/\S/, "a finding carries a code").meta({
179
- 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. Three 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) and "no_operator_approval" (the operator has not admitted this merchant to the live catalog, which only the operator can change).',
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).',
180
186
  }),
181
187
  /** The same finding in words, for the person who has to fix the card. */
182
188
  message: z.string().regex(/\S/, "a finding carries an explanation a person can read"),
@@ -212,7 +218,7 @@ export const CallErrorSchema = z.strictObject({
212
218
  code: z.string().regex(/\S/, "an error carries a code").meta({
213
219
  // Same reason as the refusal code: the dictionary travels with the field
214
220
  // or it does not reach the reader the export exists for.
215
- description: 'Why the call did not go through. The set is open, and five 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; 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.',
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).',
216
222
  }),
217
223
  message: z.string().regex(/\S/, "an error carries an explanation a person can read"),
218
224
  retryable: z.boolean(),
@@ -246,7 +252,9 @@ export const CARD_REJECTED = "card_rejected";
246
252
  * Each arrives in the refusal's `problems` with an empty path, beside whatever
247
253
  * is wrong with the card, and none of them is cleared by editing the card: the
248
254
  * merchant has no name set for buyers to read, no wallet set for their sales to
249
- * be paid into, or no approval from the operator for the live catalog. The
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
250
258
  * name is set with a call of the merchant's own (`POST /v0/seller-name`) or in
251
259
  * the dashboard; the wallet in the dashboard's Settings alone, since no key of the
252
260
  * merchant's code may say where the money goes; and the approval is the
@@ -260,6 +268,7 @@ export const MERCHANT_FINDINGS = Object.freeze({
260
268
  NO_SELLER_NAME: "no_seller_name",
261
269
  NO_PAYOUT_WALLET: "no_payout_wallet",
262
270
  NO_OPERATOR_APPROVAL: "no_operator_approval",
271
+ NO_SELLER_SITE: "no_seller_site",
263
272
  });
264
273
  /**
265
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;
@@ -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>;
@@ -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.7.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": [