@nuanu-ai/agentify-contracts 0.3.2
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/LICENSE +201 -0
- package/NOTICE +2 -0
- package/README.md +32 -0
- package/dist/api.d.ts +1948 -0
- package/dist/api.js +956 -0
- package/dist/card.d.ts +423 -0
- package/dist/card.js +700 -0
- package/dist/envelope.d.ts +174 -0
- package/dist/envelope.js +101 -0
- package/dist/events.d.ts +56 -0
- package/dist/events.js +54 -0
- package/dist/evm-address.d.ts +77 -0
- package/dist/evm-address.js +265 -0
- package/dist/handler.d.ts +90 -0
- package/dist/handler.js +102 -0
- package/dist/index.d.ts +1008 -0
- package/dist/index.js +167 -0
- package/dist/merchant.d.ts +281 -0
- package/dist/merchant.js +433 -0
- package/dist/order-status.d.ts +47 -0
- package/dist/order-status.js +102 -0
- package/dist/order.d.ts +50 -0
- package/dist/order.js +78 -0
- package/dist/param-spec.d.ts +145 -0
- package/dist/param-spec.js +147 -0
- package/dist/primitives.d.ts +103 -0
- package/dist/primitives.js +130 -0
- package/dist/quote.d.ts +79 -0
- package/dist/quote.js +92 -0
- package/dist/receipt.d.ts +79 -0
- package/dist/receipt.js +83 -0
- package/dist/results.d.ts +230 -0
- package/dist/results.js +269 -0
- package/dist/selling.d.ts +24 -0
- package/dist/selling.js +35 -0
- package/package.json +46 -0
package/dist/card.js
ADDED
|
@@ -0,0 +1,700 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One product, in the shapes it has: the card the merchant publishes, the card
|
|
3
|
+
* an agent reads in a catalog, and the card its own merchant reads back.
|
|
4
|
+
*
|
|
5
|
+
* The published card comes first and everything the agent sees is drawn from
|
|
6
|
+
* it. The requirement that runs through every field follows from that: an
|
|
7
|
+
* agent holding only this card has to be able to assemble a correct purchase
|
|
8
|
+
* and know what it will get back. A field that fails that test is not a
|
|
9
|
+
* detail, it is a sale that ends in a refusal.
|
|
10
|
+
*
|
|
11
|
+
* The catalog id is not part of the published schema. We issue it when the
|
|
12
|
+
* card is published and hand it back in the result, so a card arriving with
|
|
13
|
+
* one is a card whose author is guessing at our numbering.
|
|
14
|
+
*
|
|
15
|
+
* The first two shapes share this file rather than being separated by
|
|
16
|
+
* audience, because what they have to be is in step with each other. Anything a
|
|
17
|
+
* merchant may publish has to project into something an agent can read, and
|
|
18
|
+
* anything the projection copies is a claim we make to whoever spends money on
|
|
19
|
+
* it. Kept apart, the rule that ties them would live in neither file.
|
|
20
|
+
*
|
|
21
|
+
* The third is not a projection at all and is here for the opposite reason: a
|
|
22
|
+
* merchant reading their own catalog is reading what they wrote, so the card
|
|
23
|
+
* travels whole, wrapped in the two facts they cannot have — the identifier we
|
|
24
|
+
* issued and the word it is selling under. A second projection would be one
|
|
25
|
+
* more shape to keep in step for no gain to anybody.
|
|
26
|
+
*/
|
|
27
|
+
import { z } from "zod";
|
|
28
|
+
import { ParamSpecSchema, paramSpecToValidator } from "./param-spec.js";
|
|
29
|
+
import { IdentifierSchema, MoneySchema, TimestampSchema } from "./primitives.js";
|
|
30
|
+
import { SellingStateSchema } from "./selling.js";
|
|
31
|
+
/**
|
|
32
|
+
* When the product reaches the agent, and therefore when the money moves.
|
|
33
|
+
*
|
|
34
|
+
* `sync` — in the answer to the purchase, and the payment executes last, after
|
|
35
|
+
* the merchant has delivered. `async` — later, by a separate call, and the
|
|
36
|
+
* payment executes at the moment of purchase. `confirm` — the merchant is
|
|
37
|
+
* asked first, and the payment executes right after they say yes.
|
|
38
|
+
*/
|
|
39
|
+
export const FulfillmentSchema = z.enum(["sync", "async", "confirm"]).meta({
|
|
40
|
+
description: 'When the product reaches the agent, and so when the money moves. "sync" — in the answer to the purchase, payment last. "async" — later, by a separate call, payment at the moment of purchase. "confirm" — the merchant is asked first and the payment follows their yes. A card cannot be published as "confirm" during the pilot: the confirmation request has no shape on the wire yet, so a handler could not tell one from a paid order.',
|
|
41
|
+
});
|
|
42
|
+
/**
|
|
43
|
+
* How the price and availability of this card are asked for, if they are.
|
|
44
|
+
*
|
|
45
|
+
* `"handler"` is the default path: the question travels the same outgoing
|
|
46
|
+
* channel as the orders, and the merchant hosts nothing. The other form names
|
|
47
|
+
* an address, for merchants whose price is computed by a separate service that
|
|
48
|
+
* the order handler cannot reach.
|
|
49
|
+
*
|
|
50
|
+
* The address has to be https. The question and the answer carry a merchant's
|
|
51
|
+
* prices; over plain http they are readable and rewritable by anyone on the
|
|
52
|
+
* path, and a sale that went through at someone else's price would be
|
|
53
|
+
* indistinguishable from an honest one.
|
|
54
|
+
*/
|
|
55
|
+
export const PriceCheckSchema = z.union([
|
|
56
|
+
z.literal("handler"),
|
|
57
|
+
z.strictObject({
|
|
58
|
+
// Two checks, and each earns its place. `z.url()` says this is a URL at
|
|
59
|
+
// all; the pattern says the scheme is https. The scheme is a pattern
|
|
60
|
+
// rather than zod's own `protocol` option because a pattern is what
|
|
61
|
+
// survives into the JSON Schema export — zod renders a url as
|
|
62
|
+
// `format: "uri"` and drops the rest, so a generated client would
|
|
63
|
+
// otherwise happily post a merchant's prices over http. Written both
|
|
64
|
+
// ways, the protocol option was doing nothing the pattern did not, and
|
|
65
|
+
// no test could tell the difference.
|
|
66
|
+
//
|
|
67
|
+
// Case-insensitive because a URL scheme is (RFC 3986 §3.1). Written
|
|
68
|
+
// case-sensitively alongside zod's own check, the two disagreed:
|
|
69
|
+
// `HTTPS://` passed one and failed the other, with a message saying an
|
|
70
|
+
// address was not https about an address that was.
|
|
71
|
+
url: z.url().regex(/^https:\/\//i, "a price hook is https"),
|
|
72
|
+
}),
|
|
73
|
+
], { error: 'a price check is either "handler" or { url } naming an https address' });
|
|
74
|
+
/** The short line a catalog shows; how the card differs from its neighbours. */
|
|
75
|
+
const TitleSchema = z.string().regex(/\S/, "a title must not be empty or blank");
|
|
76
|
+
/**
|
|
77
|
+
* The longest a word may be before the discovery channel stops carrying it, and
|
|
78
|
+
* the most words it carries.
|
|
79
|
+
*
|
|
80
|
+
* Both numbers belong to the channel rather than to us, and both are read off
|
|
81
|
+
* its own implementation rather than guessed: a name is at most thirty-two
|
|
82
|
+
* characters of printable ASCII, and at most five tags survive. What the
|
|
83
|
+
* channel does past those limits is drop the value without telling anybody,
|
|
84
|
+
* which is why they are enforced here instead — a merchant whose tag or whose
|
|
85
|
+
* name silently disappeared would have nothing to look at and no reason to
|
|
86
|
+
* suspect anything went wrong.
|
|
87
|
+
*/
|
|
88
|
+
const LISTED_TEXT_MAX = 32;
|
|
89
|
+
const LISTED_TAGS_MAX = 5;
|
|
90
|
+
/**
|
|
91
|
+
* A word the discovery channel can render, held to the channel's own rule.
|
|
92
|
+
*
|
|
93
|
+
* Printable ASCII and nothing else, because that is what the channel's own
|
|
94
|
+
* check tests for before it drops a value: a pattern over the printable range
|
|
95
|
+
* and a length counted in the units a JavaScript string counts in, which is
|
|
96
|
+
* why the same word measures the same on every side that implements this. The
|
|
97
|
+
* restriction is real and it costs something worth saying out loud: a seller
|
|
98
|
+
* whose name is written in Cyrillic, Greek or Arabic cannot be listed under
|
|
99
|
+
* it, and this refuses the name rather than listing them under a mangled one.
|
|
100
|
+
*/
|
|
101
|
+
const listedText = (what) => z
|
|
102
|
+
.string()
|
|
103
|
+
.min(1, `a ${what} must not be empty`)
|
|
104
|
+
.max(LISTED_TEXT_MAX, `a ${what} is at most ${LISTED_TEXT_MAX} characters`)
|
|
105
|
+
.regex(/^[\x20-\x7e]*$/, `a ${what} is printable ASCII, which is all the listing carries`)
|
|
106
|
+
// Blank and padded in one rule. A space at either end survives the listing
|
|
107
|
+
// untouched, which is worse than being dropped: it makes two spellings of
|
|
108
|
+
// one word, and a merchant comparing what they typed with what they see
|
|
109
|
+
// finds them identical.
|
|
110
|
+
.regex(/^\S(?:[\s\S]*\S)?$/, `a ${what} must not be blank or padded with spaces`);
|
|
111
|
+
/**
|
|
112
|
+
* The name the seller of a card is listed under, wherever a catalog names a
|
|
113
|
+
* seller rather than a product.
|
|
114
|
+
*
|
|
115
|
+
* It belongs to the merchant and not to any one card: a merchant sells under
|
|
116
|
+
* one name, and a per-card name would be one seller appearing as several. What
|
|
117
|
+
* holds it is the merchants table, so this schema is the rule alone, applied
|
|
118
|
+
* wherever a merchant's listing name is written down.
|
|
119
|
+
*/
|
|
120
|
+
export const ServiceNameSchema = listedText("service name").meta({
|
|
121
|
+
description: "The name a seller is listed under in a discovery catalog. At most 32 characters of printable ASCII, because that is what the catalog carries; a name outside that is refused here rather than truncated there, where nobody would be told.",
|
|
122
|
+
});
|
|
123
|
+
/**
|
|
124
|
+
* The words a merchant puts on one product so an agent searching a catalog can
|
|
125
|
+
* find it.
|
|
126
|
+
*
|
|
127
|
+
* They describe this product and not the seller, which is why they sit on the
|
|
128
|
+
* card. At most five, because the catalog keeps the first five and drops the
|
|
129
|
+
* rest without a word.
|
|
130
|
+
*/
|
|
131
|
+
export const TagsSchema = z
|
|
132
|
+
.array(listedText("tag"))
|
|
133
|
+
// Not an empty list. To the catalog an empty list and no tags at all are the
|
|
134
|
+
// same thing — it drops the field either way — so accepting both would give
|
|
135
|
+
// one meaning two spellings, and the merchant who wrote the brackets could
|
|
136
|
+
// reasonably think they had said something. One way to say it, and it is
|
|
137
|
+
// leaving the field out.
|
|
138
|
+
.min(1, "a card that names no tags leaves the field out rather than sending an empty list")
|
|
139
|
+
.max(LISTED_TAGS_MAX, `a card carries at most ${LISTED_TAGS_MAX} tags`)
|
|
140
|
+
.refine(
|
|
141
|
+
// The catalog folds tags together without regard to case and keeps the
|
|
142
|
+
// first of each, so "Access" and "access" published together become one
|
|
143
|
+
// tag and the merchant is never told which of the two survived. Refusing
|
|
144
|
+
// the pair is the version of that a merchant can act on.
|
|
145
|
+
(tags) => new Set(tags.map((tag) => tag.toLowerCase())).size === tags.length, "two tags that differ only in case are one tag to the listing, so both cannot be published")
|
|
146
|
+
.meta({
|
|
147
|
+
description: "Words describing this product for an agent searching a discovery catalog. Between 1 and 5 of them, each 1 to 32 characters of printable ASCII with no space at either end, and no two the same without regard to case — because that is what the catalog keeps, and it drops the rest in silence. A card with no tags leaves the field out.",
|
|
148
|
+
// The case rule is a refinement and zod drops those when it renders a
|
|
149
|
+
// document. This much of it does cross, so a generated client refuses at
|
|
150
|
+
// least the identical pair.
|
|
151
|
+
uniqueItems: true,
|
|
152
|
+
});
|
|
153
|
+
/**
|
|
154
|
+
* The longest a description may be before a discovery catalog stops carrying
|
|
155
|
+
* all of it.
|
|
156
|
+
*
|
|
157
|
+
* This number is weaker evidence than the other two on this page and the
|
|
158
|
+
* difference matters. The length and the alphabet a listing name and a tag are
|
|
159
|
+
* held to were read out of the catalog's own code, which we run here; this one
|
|
160
|
+
* is read out of the catalog's written documentation, recorded in
|
|
161
|
+
* `docs/research/04-spike-bazaar-listing.md`, and no code of theirs that we can
|
|
162
|
+
* run enforces it — their sanitiser touches the listing name, the tags and the
|
|
163
|
+
* icon, and never the description. A hundred entries walked out of the live
|
|
164
|
+
* catalog are consistent with it and prove nothing: the longest was 468
|
|
165
|
+
* characters and none sat at the boundary, which is what a hard cut would have
|
|
166
|
+
* left behind. So the ceiling is honoured rather than verified.
|
|
167
|
+
*/
|
|
168
|
+
const LISTED_DESCRIPTION_MAX = 500;
|
|
169
|
+
/**
|
|
170
|
+
* What the buyer gets, what it is good for and what it does not include. Read
|
|
171
|
+
* by a program, so distinguishing facts do the work here.
|
|
172
|
+
*
|
|
173
|
+
* It is also the one field of prose that goes out to a discovery catalog, and
|
|
174
|
+
* the only one of the three merchant-written fields that reaches a listing with
|
|
175
|
+
* nothing of the catalog's own checking it on arrival. Whatever a catalog does
|
|
176
|
+
* with a description past its limit — cut it, refuse the record, keep it whole
|
|
177
|
+
* — a merchant would learn of it from a listing rather than from us. So the
|
|
178
|
+
* limit is here, at the publish, where somebody is reading the answer.
|
|
179
|
+
*
|
|
180
|
+
* The refusal is written as a function of what arrived rather than as a fixed
|
|
181
|
+
* sentence, and it says two things a fixed one could not. It says how long the
|
|
182
|
+
* text actually is, because the merchant is the only party who cannot see that
|
|
183
|
+
* number and the only one who has to act on it — a shop's editor does not count
|
|
184
|
+
* characters, and "at most 500" with nothing beside it is an instruction to go
|
|
185
|
+
* and count by hand. And it says whose ceiling this is: read on its own, a
|
|
186
|
+
* sentence about five hundred characters could be our rule, the payment
|
|
187
|
+
* protocol's, or something about an alphabet, and a merchant deciding whether
|
|
188
|
+
* to argue with us cannot tell which. It is the discovery catalog's, it is
|
|
189
|
+
* documented rather than enforced by anything of theirs we can run (see the
|
|
190
|
+
* comment on the number above), and the word "documents" in the sentence is
|
|
191
|
+
* carrying that and not decoration.
|
|
192
|
+
*
|
|
193
|
+
* The count is of characters and not of bytes, which is what a merchant writing
|
|
194
|
+
* in Cyrillic would otherwise have to work out from a number twice the size of
|
|
195
|
+
* their text. It is UTF-16 units, precisely: a character outside the basic
|
|
196
|
+
* plane — an emoji, a musical symbol — counts as two. That is not what a person
|
|
197
|
+
* sees on their screen, and it is deliberately the same unit the ceiling is
|
|
198
|
+
* measured in, so the number a merchant is told is always the number they were
|
|
199
|
+
* refused for. Reporting glyphs and refusing on units would be a message that
|
|
200
|
+
* disagrees with the rule it is explaining.
|
|
201
|
+
*/
|
|
202
|
+
const DescriptionSchema = z
|
|
203
|
+
.string()
|
|
204
|
+
.regex(/\S/, "a description must not be empty or blank")
|
|
205
|
+
.max(LISTED_DESCRIPTION_MAX, {
|
|
206
|
+
// `input` is the string this check ran against: zod reaches a length check
|
|
207
|
+
// only once the value is known to be a string, so there is no other case
|
|
208
|
+
// here to write a branch for.
|
|
209
|
+
error: (issue) => `this description is ${String(issue.input).length} characters and a listing carries at` +
|
|
210
|
+
` most ${LISTED_DESCRIPTION_MAX} — the ceiling is the one the discovery catalog documents,` +
|
|
211
|
+
" not ours and not the payment protocol's",
|
|
212
|
+
});
|
|
213
|
+
/**
|
|
214
|
+
* What the agent receives when the delivery goes through. Never empty: a
|
|
215
|
+
* declaration with no fields satisfies the letter of "the card declares its
|
|
216
|
+
* result" while telling the agent nothing about what it is paying for.
|
|
217
|
+
*
|
|
218
|
+
* The rule is written twice again. zod drops refinements when it renders
|
|
219
|
+
* JSON Schema, so the same constraint goes into the metadata as
|
|
220
|
+
* `minProperties`, where a generator can still see it.
|
|
221
|
+
*
|
|
222
|
+
* One schema, used by the published card and by the projection below, because
|
|
223
|
+
* the promise is the same one: what the agent reads before paying is what the
|
|
224
|
+
* merchant is held to afterwards. Two copies of a refinement would be two
|
|
225
|
+
* promises, and the export would carry whichever was edited last.
|
|
226
|
+
*/
|
|
227
|
+
const DeclaredResultSchema = ParamSpecSchema.refine(
|
|
228
|
+
// Not merely non-empty: at least one field that actually arrives. A
|
|
229
|
+
// declaration of one field marked `required: false` satisfies "at least
|
|
230
|
+
// one" and promises the agent exactly as much as an empty one does.
|
|
231
|
+
(spec) => Object.values(spec).some((field) => field.required !== false), "a card declares at least one field of what the agent receives, and at least one of them arrives every time").meta({
|
|
232
|
+
description: "What the agent receives on delivery. At least one field, and at least one of the fields is not marked required: false — a result that might be entirely absent promises nothing.",
|
|
233
|
+
minProperties: 1,
|
|
234
|
+
});
|
|
235
|
+
/**
|
|
236
|
+
* The short forms a card may be written in, and the one form they all become.
|
|
237
|
+
*
|
|
238
|
+
* A merchant selling one thing has three facts to give us: what they sell, at
|
|
239
|
+
* what price, and what the buyer receives. Everything else a card can carry is
|
|
240
|
+
* either optional already or has one answer that fits almost every product. The
|
|
241
|
+
* three forms below let such a merchant write only the three facts, and they
|
|
242
|
+
* exist for the publish call and for nothing else.
|
|
243
|
+
*
|
|
244
|
+
* What keeps them from becoming a second contract is that they open out into
|
|
245
|
+
* the canonical card while it is parsed. What is stored, what an agent reads in
|
|
246
|
+
* a catalog, what a delivery is held to and what a discovery catalog is told
|
|
247
|
+
* are all built from the one shape they were always built from — the
|
|
248
|
+
* projections further down this file never see a short form and need no case
|
|
249
|
+
* for one. The mechanism does not fork; only the writing does, in the writer's
|
|
250
|
+
* favour.
|
|
251
|
+
*
|
|
252
|
+
* They are private to this file on purpose. A card is the only document written
|
|
253
|
+
* by a person rather than by a running program, so a short spelling buys
|
|
254
|
+
* nothing anywhere else and would put a second form on the wire if it were
|
|
255
|
+
* offered there. The price of a quote answer and the price on an order stay as
|
|
256
|
+
* they are.
|
|
257
|
+
*
|
|
258
|
+
* Two properties are worth stating, because a reader would otherwise have to
|
|
259
|
+
* test for them.
|
|
260
|
+
*
|
|
261
|
+
* The short forms belong to fields rather than to cards. A card may write its
|
|
262
|
+
* price short and its purchase parameters long, or put a title on one delivered
|
|
263
|
+
* field and leave its neighbours as bare type words. There is no short mode to
|
|
264
|
+
* turn on, so adding one option to one field does not mean rewriting the rest.
|
|
265
|
+
*
|
|
266
|
+
* And a short form neither widens nor narrows what a card may say. The set of
|
|
267
|
+
* types a field may declare is `ParamTypeSchema`'s and is read from there: the
|
|
268
|
+
* expansion writes the word it was given into `type` without judging it, so a
|
|
269
|
+
* word outside the set is refused by the same check and in the same words as
|
|
270
|
+
* `{ type: 'strin' }` always was.
|
|
271
|
+
*/
|
|
272
|
+
/** A price written as one string: the amount, one space, the currency code. */
|
|
273
|
+
const PRICE_WRITTEN_SHORT = /^(\S+) (\S+)$/;
|
|
274
|
+
/**
|
|
275
|
+
* What a price that is neither of the two forms is told.
|
|
276
|
+
*
|
|
277
|
+
* It names both forms, because the mistake it catches does not say which one
|
|
278
|
+
* was meant: `"5.00"` is the short form with the currency left off and `"USD"`
|
|
279
|
+
* is the short form with the amount left off, and what helps in either case is
|
|
280
|
+
* seeing a whole price written each way.
|
|
281
|
+
*/
|
|
282
|
+
const PRICE_EXPECTED = 'a price is either the amount and the currency as one string, "5.00 USD", or the two written out, { amount: "5.00", currency: "USD" }';
|
|
283
|
+
/**
|
|
284
|
+
* A card's price: the two fields, or the one string that becomes them.
|
|
285
|
+
*
|
|
286
|
+
* The string is opened out before `MoneySchema` runs rather than beside it, and
|
|
287
|
+
* that ordering is why this is a preprocess and not a union of the two shapes.
|
|
288
|
+
* Written as a union, a merchant who gave the canonical form and got one half
|
|
289
|
+
* of it wrong is told "invalid input" — zod has two failed branches and no way
|
|
290
|
+
* to choose between them — where today they are told which half and why. Opened
|
|
291
|
+
* out first, the canonical check is the only one that ever speaks, so
|
|
292
|
+
* `{ amount: 'x', currency: 'USD' }` still names the amount, and `"5 dollars"`
|
|
293
|
+
* names the currency, which is the half of it that is wrong.
|
|
294
|
+
*/
|
|
295
|
+
const CardPriceSchema = z.preprocess((value, ctx) => {
|
|
296
|
+
if (typeof value !== "string")
|
|
297
|
+
return value;
|
|
298
|
+
const written = PRICE_WRITTEN_SHORT.exec(value);
|
|
299
|
+
if (written === null) {
|
|
300
|
+
ctx.addIssue({ code: "custom", message: PRICE_EXPECTED });
|
|
301
|
+
// Nothing further to say about this field: the canonical check would only
|
|
302
|
+
// add "expected object, received string" underneath a sentence that has
|
|
303
|
+
// already said what was expected. The rest of the card is still checked,
|
|
304
|
+
// because this sits on the field rather than on the card.
|
|
305
|
+
return z.NEVER;
|
|
306
|
+
}
|
|
307
|
+
return { amount: written[1], currency: written[2] };
|
|
308
|
+
}, MoneySchema);
|
|
309
|
+
/** A record with names on it, as against an array, a null or a bare value. */
|
|
310
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
311
|
+
/**
|
|
312
|
+
* A declaration with every field written out, from one where some of them are
|
|
313
|
+
* the type word alone.
|
|
314
|
+
*
|
|
315
|
+
* `access_url: 'string'` becomes `access_url: { type: 'string' }`; a field that
|
|
316
|
+
* is anything else is passed through for the canonical schema to judge, and
|
|
317
|
+
* that includes a word which is not a type.
|
|
318
|
+
*
|
|
319
|
+
* The declaration is rebuilt rather than edited, so nothing here changes the
|
|
320
|
+
* object the caller handed in. The rebuild is also where a key named
|
|
321
|
+
* `__proto__` could have started being kept, or setting a prototype;
|
|
322
|
+
* `Object.fromEntries` defines a property rather than assigning one, so such a
|
|
323
|
+
* key stays an ordinary key and is dropped where it always was — see
|
|
324
|
+
* `PROTOTYPE_KEY_IS_DROPPED` in `param-spec.ts`.
|
|
325
|
+
*/
|
|
326
|
+
const declarationWrittenOut = (value) => isRecord(value)
|
|
327
|
+
? Object.fromEntries(Object.entries(value).map(([name, field]) => [
|
|
328
|
+
name,
|
|
329
|
+
typeof field === "string" ? { type: field } : field,
|
|
330
|
+
]))
|
|
331
|
+
: value;
|
|
332
|
+
/**
|
|
333
|
+
* The same declaration, also accepting a field written as its type alone.
|
|
334
|
+
*
|
|
335
|
+
* Takes the schema rather than being one, because a card has two declarations
|
|
336
|
+
* held to different rules — the purchase parameters may be empty and the
|
|
337
|
+
* delivery result may not — and both take the short form.
|
|
338
|
+
*/
|
|
339
|
+
const writtenShort = (declaration) => z.preprocess(declarationWrittenOut, declaration);
|
|
340
|
+
const CardFieldsSchema = z.strictObject({
|
|
341
|
+
/** The merchant's own key for this product, the one their database uses. */
|
|
342
|
+
merchant_item_id: IdentifierSchema,
|
|
343
|
+
title: TitleSchema,
|
|
344
|
+
description: DescriptionSchema,
|
|
345
|
+
/**
|
|
346
|
+
* The price in the catalog. Always required, even for a card with a price
|
|
347
|
+
* check: this is what the agent compares when it is choosing.
|
|
348
|
+
*
|
|
349
|
+
* Written as the two fields, or as the one string that becomes them. What is
|
|
350
|
+
* stored and what an agent reads are the two fields either way.
|
|
351
|
+
*/
|
|
352
|
+
price: CardPriceSchema,
|
|
353
|
+
/** What the agent has to supply to buy. Absent when the purchase needs no input. */
|
|
354
|
+
params: writtenShort(ParamSpecSchema).optional(),
|
|
355
|
+
result: writtenShort(DeclaredResultSchema),
|
|
356
|
+
/**
|
|
357
|
+
* Words for an agent searching a discovery catalog. Absent on a card whose
|
|
358
|
+
* merchant named none, and never invented for them.
|
|
359
|
+
*/
|
|
360
|
+
tags: TagsSchema.optional(),
|
|
361
|
+
/**
|
|
362
|
+
* When the product reaches the agent, and so when the money moves.
|
|
363
|
+
*
|
|
364
|
+
* A card that names no mode is synchronous, which is what almost every
|
|
365
|
+
* product a person writes a card for by hand is: the goods go back in the
|
|
366
|
+
* answer to the purchase. The default is filled in here rather than read as
|
|
367
|
+
* absence downstream, so the stored card, the public card and the discovery
|
|
368
|
+
* declaration all carry the word — the alternative is three readers each
|
|
369
|
+
* deciding for themselves what silence meant.
|
|
370
|
+
*/
|
|
371
|
+
fulfillment: FulfillmentSchema.default("sync"),
|
|
372
|
+
price_check: PriceCheckSchema.optional(),
|
|
373
|
+
/**
|
|
374
|
+
* How long the merchant may take to answer a confirmation request.
|
|
375
|
+
*
|
|
376
|
+
* The seconds themselves belong to the gateway. What a card that names none
|
|
377
|
+
* is held to is set there and published on the portal — an hour — while the
|
|
378
|
+
* ceiling on what a card may name is genuinely undecided. So this schema
|
|
379
|
+
* takes any whole positive number of seconds rather than inventing a limit
|
|
380
|
+
* that would read as a decision nobody took.
|
|
381
|
+
*/
|
|
382
|
+
confirm_deadline_seconds: z.int().positive().optional(),
|
|
383
|
+
/** How long the merchant may take to deliver an order it has accepted. */
|
|
384
|
+
fulfill_deadline_seconds: z.int().positive().optional(),
|
|
385
|
+
});
|
|
386
|
+
/**
|
|
387
|
+
* Both deadlines are shown to the agent before it pays, which is why a card
|
|
388
|
+
* may only carry the ones its own mode uses. A synchronous card advertising a
|
|
389
|
+
* delivery deadline would be advertising a wait that never happens — and the
|
|
390
|
+
* wait for a synchronous answer is our system-wide budget, the same for every
|
|
391
|
+
* product, so it has no field here at all.
|
|
392
|
+
*/
|
|
393
|
+
export const CardSchema = CardFieldsSchema.superRefine((card, ctx) => {
|
|
394
|
+
if (card.fulfillment === "confirm") {
|
|
395
|
+
// The gate, and the reason it is a refusal rather than a note somewhere.
|
|
396
|
+
// A confirmation request reaches the merchant before any money moves and
|
|
397
|
+
// must be answered without delivering — but nothing on the wire marks it
|
|
398
|
+
// as one, so a handler could not tell it from a paid order. Publishing
|
|
399
|
+
// such a card would sell the merchant a mode that cannot be served, and
|
|
400
|
+
// the first they heard of it would be a request they mishandled.
|
|
401
|
+
//
|
|
402
|
+
// The value stays in the enumeration: the mode exists in the model and the
|
|
403
|
+
// state machine knows it. What is missing is its shape on the wire, and
|
|
404
|
+
// that is a "not yet", not a "no".
|
|
405
|
+
ctx.addIssue({
|
|
406
|
+
code: "custom",
|
|
407
|
+
path: ["fulfillment"],
|
|
408
|
+
message: 'a card cannot be published as "confirm" during the pilot: the confirmation request has no shape on the wire yet, so a handler could not tell one from a paid order',
|
|
409
|
+
});
|
|
410
|
+
// The deadline rules below are unchanged and still right for this mode.
|
|
411
|
+
// Reporting them alongside the gate would stack a second complaint on a
|
|
412
|
+
// card that has one problem, and lifting the gate later would uncover it.
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
// Past the gate only "sync" and "async" remain, and neither is ever asked to
|
|
416
|
+
// confirm — which is why this needs no test on the mode of its own.
|
|
417
|
+
if (card.confirm_deadline_seconds !== undefined) {
|
|
418
|
+
ctx.addIssue({
|
|
419
|
+
code: "custom",
|
|
420
|
+
path: ["confirm_deadline_seconds"],
|
|
421
|
+
message: `only a card with fulfillment "confirm" is asked to confirm; this one is "${card.fulfillment}"`,
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
if (card.fulfillment === "sync" && card.fulfill_deadline_seconds !== undefined) {
|
|
425
|
+
ctx.addIssue({
|
|
426
|
+
code: "custom",
|
|
427
|
+
path: ["fulfill_deadline_seconds"],
|
|
428
|
+
message: 'a synchronous card delivers inside the system-wide response budget and sets no delivery deadline; "async" and "confirm" do',
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
}).meta({
|
|
432
|
+
// JSON Schema has no way to say "this field only when that one has this
|
|
433
|
+
// value", and zod drops the rules above when it renders a document. Left at
|
|
434
|
+
// that, an engineer generating a client from the export would build one that
|
|
435
|
+
// sends deadlines a card cannot carry and only find out on the first publish.
|
|
436
|
+
// Saying it in words is weaker than checking it, and better than silence.
|
|
437
|
+
description: 'A product in the catalog, as the merchant publishes it. Three rules are enforced beyond the shape below. A card cannot be published as fulfillment "confirm" during the pilot: the confirmation request has no shape on the wire yet, so a handler could not tell one from a paid order. confirm_deadline_seconds is only allowed when fulfillment is "confirm", and fulfill_deadline_seconds only when fulfillment is "async" or "confirm" — a synchronous card delivers inside the system-wide response budget and names no deadline of its own. This document describes the card as it is stored and read back; three fields also take a shorter spelling at publication, which JSON Schema has no way to show alongside the canonical one. price may be written as one string, the amount and the currency code with a single space between them ("5.00 USD"). A field of params or result may be written as its type word alone (access_url: "string") where it carries no title and no required flag. fulfillment may be left out, and a card that leaves it out is "sync". Each of those is opened out into the form below as the card is accepted, so a card generated from this document is accepted unchanged and a card read back is always in this form.',
|
|
438
|
+
});
|
|
439
|
+
/**
|
|
440
|
+
* The check an agent's purchase parameters are held to, for this card.
|
|
441
|
+
*
|
|
442
|
+
* The compiler underneath takes a direction, and a direction is a string a
|
|
443
|
+
* caller can get backwards: compiling a card's result as a purchase silently
|
|
444
|
+
* restores the hole where a delivery promises nothing. These two take the card
|
|
445
|
+
* instead, so the only place that knows which declaration is which is the
|
|
446
|
+
* place that picks.
|
|
447
|
+
*/
|
|
448
|
+
export const purchaseCheckFor = (card) => paramSpecToValidator(card.params ?? {}, "purchase");
|
|
449
|
+
/** The check this card's delivery is held to. */
|
|
450
|
+
export const deliveryCheckFor = (card) => paramSpecToValidator(card.result, "delivery");
|
|
451
|
+
/**
|
|
452
|
+
* The fields of a card as an agent reads it in a catalog.
|
|
453
|
+
*
|
|
454
|
+
* The published card is what a merchant writes; this is what we say about it
|
|
455
|
+
* to somebody who is about to spend money. Every difference between the two is
|
|
456
|
+
* a decision, so each one is written down.
|
|
457
|
+
*
|
|
458
|
+
* Our catalog identifier replaces the merchant's own key. `id` is what a
|
|
459
|
+
* purchase, a receipt and a status all use, while `merchant_item_id` is unique
|
|
460
|
+
* only inside one merchant's catalog and means nothing outside it. An agent
|
|
461
|
+
* handed both would use the wrong one some of the time, for no gain.
|
|
462
|
+
*
|
|
463
|
+
* `price_check` is gone and one fact out of it stays. The address of a
|
|
464
|
+
* merchant's pricing service is infrastructure of theirs, no agent ever calls
|
|
465
|
+
* it, and publishing it would put it in front of everyone. What an agent does
|
|
466
|
+
* act on is that the price will be asked again: the number in the catalog is
|
|
467
|
+
* what it compares when choosing, and the sale can go through at another. So
|
|
468
|
+
* the projection carries `price_checked_at_purchase` and nothing else about
|
|
469
|
+
* how the asking is done. The flag says we ask, not that we get an answer —
|
|
470
|
+
* what happens when a merchant is silent depends on the mode and is the
|
|
471
|
+
* gateway's, and this document does not claim to know it.
|
|
472
|
+
*
|
|
473
|
+
* Both deadlines stay, because they are the merchant's promise to the agent
|
|
474
|
+
* about how long it may wait, and the agent is told them before it pays. They
|
|
475
|
+
* sit on the modes that have them, as branches of a union rather than as a
|
|
476
|
+
* rule attached to one object: JSON Schema cannot say "this field only when
|
|
477
|
+
* that one has this value", and zod drops such a rule without a word, so as
|
|
478
|
+
* branches the constraint crosses whole into the export.
|
|
479
|
+
*
|
|
480
|
+
* `as_of` is added: the moment the price shown here was published. A price
|
|
481
|
+
* with no moment behind it cannot be judged stale, and this is the only
|
|
482
|
+
* freshness claim a catalog makes.
|
|
483
|
+
*
|
|
484
|
+
* `tags` are gone as well, and that one is a decision rather than an omission.
|
|
485
|
+
* They are words a merchant chose so that a search in a discovery catalog finds
|
|
486
|
+
* this product, held to that catalog's own rule about length and alphabet. Our
|
|
487
|
+
* own catalog is not searched that way — an agent reading it has the whole
|
|
488
|
+
* page — so carrying them here would put a foreign catalog's constraint in
|
|
489
|
+
* front of a reader who has no use for it.
|
|
490
|
+
*
|
|
491
|
+
* One thing an agent might reasonably want is not here, and saying so is
|
|
492
|
+
* better than leaving it to be discovered: nothing in this document names who
|
|
493
|
+
* is selling. There is a shape in this contract for one name a seller carries —
|
|
494
|
+
* `ServiceNameSchema`, the name a discovery catalog lists them under — and it
|
|
495
|
+
* is deliberately not this. That name exists to satisfy one channel's rules,
|
|
496
|
+
* a merchant may have none, and standing it in for a public identity would
|
|
497
|
+
* answer a question — who "we" are to the buyer, and who the merchant is —
|
|
498
|
+
* that is still open.
|
|
499
|
+
*/
|
|
500
|
+
const PublicCardFieldsSchema = z.strictObject({
|
|
501
|
+
/** Our catalog identifier, the one a purchase, a receipt and a status use. */
|
|
502
|
+
id: IdentifierSchema,
|
|
503
|
+
title: TitleSchema,
|
|
504
|
+
description: DescriptionSchema,
|
|
505
|
+
/** The price in the catalog: what an agent compares when it is choosing. */
|
|
506
|
+
price: MoneySchema,
|
|
507
|
+
/** When the price above was published. */
|
|
508
|
+
as_of: TimestampSchema,
|
|
509
|
+
/** What the agent has to supply to buy. Absent when the purchase needs no input. */
|
|
510
|
+
params: ParamSpecSchema.optional(),
|
|
511
|
+
result: DeclaredResultSchema,
|
|
512
|
+
/**
|
|
513
|
+
* Whether the merchant is asked for this product's price and availability at
|
|
514
|
+
* the moment of purchase, so the sale may go through at a price other than
|
|
515
|
+
* the one above.
|
|
516
|
+
*
|
|
517
|
+
* Required rather than defaulted, because both readings of a missing flag
|
|
518
|
+
* cost the agent something: read as false it budgets against a price that is
|
|
519
|
+
* about to move, read as true it distrusts a price that never moves.
|
|
520
|
+
*/
|
|
521
|
+
price_checked_at_purchase: z.boolean(),
|
|
522
|
+
});
|
|
523
|
+
export const PublicCardSchema = z
|
|
524
|
+
.discriminatedUnion("fulfillment", [
|
|
525
|
+
// Synchronous: the product arrives in the answer to the purchase, inside our
|
|
526
|
+
// own response budget — one number for every product on the platform, which
|
|
527
|
+
// is why no card names it and no card may name a delivery deadline instead.
|
|
528
|
+
PublicCardFieldsSchema.extend({ fulfillment: z.literal("sync") }),
|
|
529
|
+
// Asynchronous: the money moves at the purchase and the product comes later,
|
|
530
|
+
// within the merchant's own delivery deadline where they set one.
|
|
531
|
+
PublicCardFieldsSchema.extend({
|
|
532
|
+
fulfillment: z.literal("async"),
|
|
533
|
+
fulfill_deadline_seconds: z.int().positive().optional(),
|
|
534
|
+
}),
|
|
535
|
+
// With confirmation: the merchant is asked first and the payment follows
|
|
536
|
+
// their yes, so both waits exist. No card can be published in this mode
|
|
537
|
+
// during the pilot; the branch is here because the mode is in the
|
|
538
|
+
// vocabulary, and a branch missing from a projection would be a second gate
|
|
539
|
+
// in a second place for whoever lifts the first one.
|
|
540
|
+
PublicCardFieldsSchema.extend({
|
|
541
|
+
fulfillment: z.literal("confirm"),
|
|
542
|
+
confirm_deadline_seconds: z.int().positive().optional(),
|
|
543
|
+
fulfill_deadline_seconds: z.int().positive().optional(),
|
|
544
|
+
}),
|
|
545
|
+
])
|
|
546
|
+
.meta({
|
|
547
|
+
// Everything below is written in prose above as well, and it has to be
|
|
548
|
+
// written twice: the reader this matters most to is the one holding the
|
|
549
|
+
// exported document and no TypeScript, and that reader is about to spend
|
|
550
|
+
// money on what this card claims. `as_of` in particular means something
|
|
551
|
+
// narrower here than the same name means elsewhere in this contract, and a
|
|
552
|
+
// reader who assumed otherwise would trust a stale number.
|
|
553
|
+
description: "A product an agent can buy, projected from the card its merchant published. as_of is when the price shown here was published, and nothing more: on a card whose price is checked at purchase it says nothing about how fresh that check will be — elsewhere in this contract the same name means the moment a live answer was true. price_checked_at_purchase says the merchant is asked for a price at the moment of purchase, not that they answer; what happens when they are silent depends on the mode and belongs to the gateway. The number above is what an agent compares when choosing and may not be what the sale goes through at. Two rules hold beyond the shape: a synchronous product names no delivery deadline, because it is delivered inside a response budget that is the same for every product on the platform, and only a product whose merchant is asked to confirm names a confirmation deadline. No field here names who is selling: this contract has no shape for a merchant's public identity.",
|
|
554
|
+
});
|
|
555
|
+
/**
|
|
556
|
+
* The card as an agent reads it, built from the card the merchant published.
|
|
557
|
+
*
|
|
558
|
+
* It exists so there is one projection rather than one per caller. The
|
|
559
|
+
* decision about what an agent may see is made once, here, and it is made by
|
|
560
|
+
* naming each field that is copied. Built the other way — copy the card, then
|
|
561
|
+
* remove what is internal — a field added to the published card would reach
|
|
562
|
+
* every agent by default, and the first anybody heard of it would be a
|
|
563
|
+
* merchant's pricing address in a public catalog.
|
|
564
|
+
*
|
|
565
|
+
* The two things the card cannot know are passed in: the catalog identifier we
|
|
566
|
+
* issued when it was published, and the moment its price was published.
|
|
567
|
+
*/
|
|
568
|
+
export const publicCardOf = (card, issued) => {
|
|
569
|
+
const common = {
|
|
570
|
+
id: issued.id,
|
|
571
|
+
title: card.title,
|
|
572
|
+
description: card.description,
|
|
573
|
+
price: card.price,
|
|
574
|
+
as_of: issued.as_of,
|
|
575
|
+
...(card.params === undefined ? {} : { params: card.params }),
|
|
576
|
+
result: card.result,
|
|
577
|
+
price_checked_at_purchase: card.price_check !== undefined,
|
|
578
|
+
};
|
|
579
|
+
switch (card.fulfillment) {
|
|
580
|
+
case "sync":
|
|
581
|
+
return { ...common, fulfillment: "sync" };
|
|
582
|
+
case "async":
|
|
583
|
+
return {
|
|
584
|
+
...common,
|
|
585
|
+
fulfillment: "async",
|
|
586
|
+
...(card.fulfill_deadline_seconds === undefined
|
|
587
|
+
? {}
|
|
588
|
+
: { fulfill_deadline_seconds: card.fulfill_deadline_seconds }),
|
|
589
|
+
};
|
|
590
|
+
case "confirm":
|
|
591
|
+
return {
|
|
592
|
+
...common,
|
|
593
|
+
fulfillment: "confirm",
|
|
594
|
+
...(card.confirm_deadline_seconds === undefined
|
|
595
|
+
? {}
|
|
596
|
+
: { confirm_deadline_seconds: card.confirm_deadline_seconds }),
|
|
597
|
+
...(card.fulfill_deadline_seconds === undefined
|
|
598
|
+
? {}
|
|
599
|
+
: { fulfill_deadline_seconds: card.fulfill_deadline_seconds }),
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
/**
|
|
604
|
+
* One declared field as a value standing for its own type.
|
|
605
|
+
*
|
|
606
|
+
* A string is the word `string` rather than an empty one, and that is not a
|
|
607
|
+
* flourish: a delivered string has to carry something, so an example built out
|
|
608
|
+
* of empty strings would be an example this system's own delivery check
|
|
609
|
+
* refuses — published to strangers as what they will receive. The word says
|
|
610
|
+
* what goes there, which is more than an empty string said anyway.
|
|
611
|
+
*/
|
|
612
|
+
const standInFor = (type) => {
|
|
613
|
+
switch (type) {
|
|
614
|
+
case "string":
|
|
615
|
+
return "string";
|
|
616
|
+
case "number":
|
|
617
|
+
case "integer":
|
|
618
|
+
return 0;
|
|
619
|
+
case "boolean":
|
|
620
|
+
return false;
|
|
621
|
+
}
|
|
622
|
+
};
|
|
623
|
+
/** A whole declaration as an example: every field, each one standing for a type. */
|
|
624
|
+
const exampleOf = (spec) => Object.fromEntries(Object.entries(spec).map(([name, field]) => [name, standInFor(field.type)]));
|
|
625
|
+
/**
|
|
626
|
+
* The JSON Schema a purchase body is held to.
|
|
627
|
+
*
|
|
628
|
+
* It is rendered from the very check the gateway runs against an agent's
|
|
629
|
+
* parameters, so the document a stranger reads and the door they meet cannot
|
|
630
|
+
* disagree. The rendered document names its own dialect at the top, which is
|
|
631
|
+
* right for a document standing alone and wrong for one about to be nested
|
|
632
|
+
* inside another schema, so that one line is dropped here.
|
|
633
|
+
*/
|
|
634
|
+
const purchaseBodySchemaOf = (card) => {
|
|
635
|
+
const { $schema, ...body } = z.toJSONSchema(z.strictObject({ params: purchaseCheckFor(card) }));
|
|
636
|
+
return body;
|
|
637
|
+
};
|
|
638
|
+
/**
|
|
639
|
+
* The card as a discovery catalog reads it.
|
|
640
|
+
*
|
|
641
|
+
* The two things a card cannot know are passed in: the address this resource
|
|
642
|
+
* answers at, which is pinned by whoever runs the gateway rather than worked
|
|
643
|
+
* out from a request, and the name its seller is listed under, which belongs to
|
|
644
|
+
* the merchant. A seller with no listing name and a card with no tags leave
|
|
645
|
+
* those fields out altogether — an empty string and an empty list are values a
|
|
646
|
+
* catalog would render, and the absence is the only way to say that nobody
|
|
647
|
+
* named one.
|
|
648
|
+
*/
|
|
649
|
+
export const bazaarDeclarationOf = (card, listed) => ({
|
|
650
|
+
resource: {
|
|
651
|
+
url: listed.url,
|
|
652
|
+
description: card.description,
|
|
653
|
+
mimeType: "application/json",
|
|
654
|
+
...(listed.serviceName === null ? {} : { serviceName: listed.serviceName }),
|
|
655
|
+
...(card.tags === undefined ? {} : { tags: card.tags }),
|
|
656
|
+
},
|
|
657
|
+
input: { params: exampleOf(card.params ?? {}) },
|
|
658
|
+
inputSchema: purchaseBodySchemaOf(card),
|
|
659
|
+
output: { example: exampleOf(card.result) },
|
|
660
|
+
});
|
|
661
|
+
/**
|
|
662
|
+
* One card as the merchant who published it reads it back.
|
|
663
|
+
*
|
|
664
|
+
* The public catalog answers none of what a merchant needs from their own
|
|
665
|
+
* catalog. It is unscoped, so nothing in it says which entries are theirs; it
|
|
666
|
+
* carries our identifier in place of their key; and a card that is off sale is
|
|
667
|
+
* not in it at all, which is precisely the card a merchant is looking for when
|
|
668
|
+
* they go to put it back on sale.
|
|
669
|
+
*
|
|
670
|
+
* So the card travels whole rather than as a third projection. Everything the
|
|
671
|
+
* merchant wrote is theirs already, and copying a subset of it here would be
|
|
672
|
+
* one more shape to hold in step with the published card — the drift the
|
|
673
|
+
* projection above exists to prevent once, repeated for no gain.
|
|
674
|
+
*
|
|
675
|
+
* Two fields say where the card stands, and they are two rather than one for a
|
|
676
|
+
* reason that is invisible from the shape. `selling` is what a purchase of this
|
|
677
|
+
* card meets right now, and it is the same word the order machine is given at
|
|
678
|
+
* the birth of an order, so a card reading "paused" here is a card that refuses
|
|
679
|
+
* new orders there. `paused` says whether the pause is this card's own. They
|
|
680
|
+
* differ exactly when the merchant has stopped all selling: every card then
|
|
681
|
+
* reads `selling: "paused"`, and only the ones with `paused: true` are still
|
|
682
|
+
* paused after the merchant starts selling again. A merchant given only the
|
|
683
|
+
* first would press resume on a card and watch nothing change.
|
|
684
|
+
*/
|
|
685
|
+
export const MerchantCardSchema = z
|
|
686
|
+
.strictObject({
|
|
687
|
+
/** Our catalog identifier, the one a purchase, a receipt and a status use. */
|
|
688
|
+
id: IdentifierSchema,
|
|
689
|
+
/** When this version of the card was published. */
|
|
690
|
+
as_of: TimestampSchema,
|
|
691
|
+
/** The card exactly as its merchant published it. */
|
|
692
|
+
card: CardSchema,
|
|
693
|
+
/** What a purchase of this card meets right now. */
|
|
694
|
+
selling: SellingStateSchema,
|
|
695
|
+
/** Whether the pause is this card's own, rather than the whole catalog's. */
|
|
696
|
+
paused: z.boolean(),
|
|
697
|
+
})
|
|
698
|
+
.meta({
|
|
699
|
+
description: 'One card as the merchant who published it reads it back: the card whole, the catalog identifier we issued for it, and where it stands. selling is what a purchase of this card meets right now and is the same word the order machine is given, so a card reading "paused" refuses new orders and lets the orders already accepted play out. paused says whether the pause is this card\'s own. The two differ when the merchant has stopped all selling: every card then reads selling "paused", and only the cards with paused true are still paused once the merchant starts selling again — so a resume on a card whose paused is already false changes nothing until all selling is resumed.',
|
|
700
|
+
});
|