@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.
@@ -0,0 +1,433 @@
1
+ /**
2
+ * How a merchant comes to exist, the name their products are sold under, the
3
+ * wallet their sales are paid into, and the keys they open the door with.
4
+ *
5
+ * The first two belong together because registering is the act that produces
6
+ * both: one call makes the merchant and the key its cabinet will call as them
7
+ * with, and what comes back carries that key once. Split across two files, a
8
+ * reader working out what registering leaves a merchant holding would have to
9
+ * read both to find that it is a key of a kind no list here carries. The name is here for the same reason read the other way round — it is a
10
+ * fact about the merchant and about none of their cards, and the one question a
11
+ * reader arrives with is which of the two names a merchant has is which.
12
+ *
13
+ * Two rules run through the file and are worth saying once.
14
+ *
15
+ * The secret appears in exactly three documents, and every one of them is the
16
+ * answer to a call that has just made a key. Nothing that is ever drawn again —
17
+ * the list a merchant reads, the row that comes back from disabling one — can
18
+ * carry it, and the shapes below refuse it rather than merely omit it. What is
19
+ * kept on our side is a digest, so there is nothing to put in those documents
20
+ * even if somebody wanted to.
21
+ *
22
+ * And a key that has been revoked stays in the list. `disabled_at` is a moment
23
+ * rather than a flag, and it is always present: after an incident the question
24
+ * is when a key stopped working, and a list that dropped the key answers
25
+ * nothing, while a flag answers only half.
26
+ */
27
+ import { z } from "zod";
28
+ import { ServiceNameSchema } from "./card.js";
29
+ import { EvmAddressSchema } from "./evm-address.js";
30
+ import { IdentifierSchema, TimestampSchema } from "./primitives.js";
31
+ /**
32
+ * What a merchant calls one of their keys, so one of several can be told from
33
+ * the others.
34
+ *
35
+ * Not empty, and not padded with spaces. Both defeat the only thing a label is
36
+ * for: a blank label is a row in a list with nothing in it, and a space at
37
+ * either end makes two labels that look identical wherever they are printed
38
+ * while being two different strings.
39
+ *
40
+ * What it does not do is worth saying, because a reader could take the rule for
41
+ * more than it is. Nothing here bounds the length, since no channel outside us
42
+ * carries this text and a number would be a bound nobody's format asks for, and
43
+ * nothing here holds a label to one alphabet: unlike the name a discovery
44
+ * catalog lists a seller under, a label never leaves the merchant's own
45
+ * screens, so a label written in Cyrillic is a label. A character that shows
46
+ * nothing is allowed through as well, which is the omission a reader is likeliest
47
+ * to be surprised by — an identifier refuses one, because an identifier is
48
+ * matched on and two that look alike are two keys nobody can tell apart, and a
49
+ * label is only ever read, so the same character makes one odd-looking row.
50
+ */
51
+ const KeyLabelSchema = z
52
+ .string()
53
+ .regex(/^\S(?:[\s\S]*\S)?$/u, "a label must not be empty or padded with spaces");
54
+ /**
55
+ * The key itself, in the only form its owner will ever see it.
56
+ *
57
+ * No whitespace anywhere in it, and that bound belongs to the transport rather
58
+ * than to us: a key travels as a bearer token, and the reader on the other side
59
+ * takes everything up to the first space. A secret with a space in it would
60
+ * arrive as a prefix of itself and open nothing, and the merchant would spend
61
+ * an afternoon on a key that looks perfectly good.
62
+ *
63
+ * Nothing here says how long a key is or what it starts with. Those are the
64
+ * gateway's, which is the side that generates them, and a shape written down
65
+ * here would be a promise about a format we mean to be free to change.
66
+ */
67
+ const KeySecretSchema = z
68
+ .string()
69
+ .regex(/^\S+$/, "a key travels as a bearer token, so it carries no whitespace and is not empty");
70
+ /**
71
+ * The code that stands in the door of registration.
72
+ *
73
+ * It is one value out of the gateway's configuration, handed to a person along
74
+ * with the address of the site (ADR-0014 §3). All this shape asks is that
75
+ * something was actually typed: a form submitted with an empty field is a
76
+ * mistake at the keyboard rather than a wrong code, and the two are worth
77
+ * telling apart before anything is compared.
78
+ */
79
+ const InvitationSchema = z
80
+ .string()
81
+ .regex(/\S/, "an invitation is the code handed over with the address of the site");
82
+ /**
83
+ * One key a merchant holds, as they read it.
84
+ *
85
+ * The secret is not here and cannot be put here. This is the document a screen
86
+ * lists, drawn again on every visit, and the secret is shown once by the call
87
+ * that made the key and never again — so a shape that could carry one is a
88
+ * shape that eventually does.
89
+ */
90
+ export const MerchantKeySchema = z
91
+ .strictObject({
92
+ id: IdentifierSchema,
93
+ label: KeyLabelSchema,
94
+ created_at: TimestampSchema,
95
+ /**
96
+ * The last call this key was seen on, and null where none is recorded.
97
+ *
98
+ * It is behind the truth by minutes and says so in the description: the
99
+ * instant is refreshed only once the one on the row has gone stale, because
100
+ * the alternative is a write in front of every purchase for a fact somebody
101
+ * reads when they are deciding what to revoke. What it answers is "this key
102
+ * was in use around then".
103
+ *
104
+ * Null is the absence of a record and not the absence of calls, and nothing
105
+ * here tells a reader which. A key made before this gateway began writing
106
+ * the field carries the same null as a key nobody has ever called with, and
107
+ * there is no field beside this one that separates them — one would be a
108
+ * permanent question on a public surface for an ambiguity that lasts weeks,
109
+ * so what carries it is the sentence a screen puts under the column.
110
+ */
111
+ last_used_at: TimestampSchema.nullable(),
112
+ /**
113
+ * When this key was revoked, and null while it still opens the door.
114
+ *
115
+ * Required and nullable rather than optional, because the two readings of
116
+ * an absent field are "this key works" and "nobody wrote it down", and a
117
+ * screen that guessed wrong would show a revoked key as live. The instant
118
+ * rather than a flag: a flag answers whether the key works, and the
119
+ * question somebody asks afterwards is when it stopped.
120
+ */
121
+ disabled_at: TimestampSchema.nullable(),
122
+ })
123
+ .meta({
124
+ description: "One key a merchant opens the door with, as they read it: what they called it, when it was made, when a call was last seen on it, and when it was revoked. A null disabled_at means the key still works; the field is always present, because an absent one is a silence a reader cannot tell from an oversight. The key itself is not in this document and never will be — what is kept is a digest of it, so nothing here or anywhere else can show a merchant their key a second time. last_used_at is not the instant of the last call and is not offered as one: it is refreshed at most once every few minutes per key, so a key under constant use carries a time that far behind, which is the price of not writing to the database on every call made with it. Null there means no call has been recorded against this key, which is not the same as no call having been made: a key made before this gateway began recording carries that same null, and nothing in this document tells such a key from one nobody has ever used. Nor is it a ledger of calls or an exact one: a mark that could not be written is swallowed rather than refusing the call it belonged to, so this is what was recorded and not everything that happened.",
125
+ });
126
+ /**
127
+ * The keys one merchant made for their own code, and the one this call was made
128
+ * with.
129
+ *
130
+ * `this_call` is the field the list cannot be assembled without, and the reason
131
+ * is a rule in the route rather than anything about the shape: a merchant
132
+ * cannot disable the key their own call was made with (ADR-0014 §5). A screen
133
+ * that did not know which of these that was would offer a button the route
134
+ * refuses, on the one page where being refused looks like the product being
135
+ * broken.
136
+ *
137
+ * It is not always one of the keys beside it, and that is the thing a reader is
138
+ * likeliest to assume and be wrong about. A cabinet calls with a key made for a
139
+ * cabinet, and those are in nobody's list, so a client that looked this
140
+ * identifier up among the rows would find nothing — which is an answer rather
141
+ * than an error, and a screen has to be built for it.
142
+ *
143
+ * An object rather than a bare array, for that reason before any other — an
144
+ * array has nowhere to put it.
145
+ */
146
+ export const MerchantKeyListSchema = z
147
+ .strictObject({
148
+ /**
149
+ * The keys this merchant made for their own code, the revoked ones among
150
+ * them.
151
+ *
152
+ * The keys a cabinet holds are not here and never will be: the merchant did
153
+ * not issue one and has nothing to do with one. A list is what somebody
154
+ * acts on, and a row nobody has any business acting on is a row that only
155
+ * raises the question of why it will not go away.
156
+ */
157
+ keys: z.array(MerchantKeySchema),
158
+ /** The key the request carrying this answer was made with. */
159
+ this_call: IdentifierSchema,
160
+ })
161
+ .meta({
162
+ description: "The keys one merchant made for their own code, working and revoked together, and the identifier of the key this very call was made with. That last field is here because a merchant cannot disable the key they are holding: without it a screen would offer a button the gateway refuses. It is not always among the keys listed — a cabinet calls with a key of its own, and those are in no list here — so a client matching it against the rows has to be built for finding none. The keys a cabinet holds are left out entirely: they are not issued by the merchant and cannot be revoked by them. This document does not say whether it is the whole list either — paging is not designed, and the absence of a field about it is not a promise that there is no more.",
163
+ });
164
+ /** What a merchant sends to have a key made. */
165
+ export const IssueKeyRequestSchema = z
166
+ .strictObject({
167
+ label: KeyLabelSchema,
168
+ })
169
+ .meta({
170
+ description: "What a merchant asks for when they want another key: the name they will know it by, and nothing else. There is nowhere here to put a secret, because a key is generated rather than chosen — one somebody picks is one somebody reuses somewhere else.",
171
+ });
172
+ /**
173
+ * A key that has just been made: the row, and the secret, once.
174
+ *
175
+ * Both halves are needed by the caller. Without the secret there is no key to
176
+ * hand to a worker; without the row there is a string and nothing to say which
177
+ * of the merchant's keys it is, which is the thing they need to disable it
178
+ * later.
179
+ */
180
+ export const IssuedKeySchema = z
181
+ .strictObject({
182
+ key: MerchantKeySchema,
183
+ /** The only moment this is readable. Nothing on our side keeps it. */
184
+ secret: KeySecretSchema,
185
+ })
186
+ .meta({
187
+ description: "A key as it comes back from being issued: the row a merchant will see in their list from now on, and the key itself. It carries the key once. Three answers in this contract carry one — this, what registering gives back, and the key a cabinet asks for — and nothing else does, because what is written down on our side is a digest. A key that is lost is replaced by a new one rather than read back.",
188
+ });
189
+ /**
190
+ * A key that has been revoked, as it now stands.
191
+ *
192
+ * An object rather than the key itself, so the day this answer has to say
193
+ * anything beside the key — how many of the merchant's keys still work, say — it
194
+ * grows a field instead of changing shape under every reader.
195
+ */
196
+ export const DisabledKeySchema = z
197
+ .strictObject({
198
+ key: MerchantKeySchema,
199
+ })
200
+ .meta({
201
+ description: "The key that was just revoked, with the instant it stopped working on it, so a merchant reads back what happened rather than taking the call's word for it. Revoking a key that was already revoked answers this same way and keeps the first instant, because that is the true one and a retry after a dropped connection must not rewrite it.",
202
+ });
203
+ /**
204
+ * A key made for a cabinet, which is the secret and nothing else.
205
+ *
206
+ * Every other answer that makes a key carries the row beside it, and this one
207
+ * cannot. A key made for a cabinet is in no merchant's list — they did not
208
+ * issue it and have no reason to know it exists — so an identifier here would
209
+ * name a row that no screen of theirs draws and no call of theirs reaches: not
210
+ * the list it is absent from, and not the revoking, which takes the keys a
211
+ * merchant issued and refuses this kind by name. What the caller does with this
212
+ * is put it on the row of whoever just signed in, and that is the whole of what
213
+ * it needs.
214
+ */
215
+ export const CabinetKeySchema = z
216
+ .strictObject({
217
+ /** The only moment this is readable. Nothing on our side keeps it. */
218
+ secret: KeySecretSchema,
219
+ })
220
+ .meta({
221
+ description: "A key made for a cabinet to call as one merchant, carried once and readable nowhere afterwards. There is no row beside it and there is nothing to put one: a key made this way is in no merchant's list of keys, and the call that revokes a key refuses this kind by name — so an identifier for it would name something no answer shows and no call acts on. Whoever asked for this holds it until they ask for another.",
222
+ });
223
+ /**
224
+ * That the key a call was made with is gone.
225
+ *
226
+ * A constant, and deliberately: the call has one outcome. It removes the key it
227
+ * was made with and no other, so there is nothing to count — a number here
228
+ * could only be about rows this call cannot reach — and nothing to name, since
229
+ * the caller is holding the only key it names. What is left to say is that it
230
+ * is done, and it is said in a field rather than left to a status code so that
231
+ * a client reads one document and not two kinds of evidence.
232
+ */
233
+ export const ForgottenCabinetKeySchema = z
234
+ .strictObject({
235
+ /** The key this call was made with no longer exists. */
236
+ forgotten: z.literal(true),
237
+ })
238
+ .meta({
239
+ description: "That the key this call was made with has been removed, which is the only thing this call does. There is nothing to count and nothing to name: it reaches one key, the one in the caller's hand, and the caller already knows which that is.",
240
+ });
241
+ /**
242
+ * What a merchant's products are sold under, as the merchant reads it back.
243
+ *
244
+ * Null is the fact "nobody has chosen one", which is every merchant on the day
245
+ * they register and is the state they stay in until they do. It is a value
246
+ * rather than an absent field, because an absent field would be
247
+ * indistinguishable from a client that dropped it, and a settings screen
248
+ * reading the second as the first would tell a merchant they are listed under
249
+ * nothing while they are listed under something.
250
+ *
251
+ * The name is held to the rule of the catalog that carries it, which is where
252
+ * this name is going: at most thirty-two characters of printable ASCII. That
253
+ * bound is not ours and is not about our own storage — the catalog drops what
254
+ * it cannot render and tells nobody, so a name refused here is a name the
255
+ * merchant is told about, and a name accepted here and dropped there is a
256
+ * seller trading under something they did not choose.
257
+ */
258
+ export const SellerNameSchema = z
259
+ .strictObject({
260
+ /** What buyers read beside this merchant's products, or nothing. */
261
+ seller_name: ServiceNameSchema.nullable(),
262
+ })
263
+ .meta({
264
+ description: "The name a merchant's products are sold under: what a discovery catalog lists them under and what a buyer's agent is shown beside the price. Null means nobody has chosen one, which is where every merchant starts. The field is always present rather than left out when there is no name: an absent field would be indistinguishable from a client that dropped it. What a name may be is the catalog's rule rather than ours — at most 32 characters of printable ASCII — because a name outside it is dropped there in silence, so it is refused here where somebody is told. A merchant with no name cannot publish a card: a card published without one reaches a buyer's agent inside a payment request that names no seller at all.",
265
+ });
266
+ /**
267
+ * What a merchant sends to change what their products are sold under.
268
+ *
269
+ * The same field held to the same rule, and one difference: there is no null.
270
+ * A merchant can go from having no name to having one and from one name to
271
+ * another, and not back. Having none is a starting state rather than a setting,
272
+ * because a payment request names the seller and there would be nobody to name:
273
+ * every card they had already published would come off sale, which is an end to
274
+ * their selling arriving under the name of editing a setting. What somebody
275
+ * reaching for that actually wants is one of two other acts: a different name,
276
+ * which is this same call, or an end to selling, which is the pause — and the
277
+ * pause leaves their cards where they can find them again.
278
+ *
279
+ * So it is two documents rather than one, and a cabinet still reads back the
280
+ * shape it sent. The message on a null is written here rather than left to a
281
+ * type error, because "expected string, received null" describes the shape and
282
+ * says nothing about which act the sender was reaching for.
283
+ */
284
+ export const SellerNameRequestSchema = z
285
+ .strictObject({
286
+ /**
287
+ * What buyers are to read beside this merchant's products.
288
+ *
289
+ * The rule lives once, in `ServiceNameSchema`, and this reaches it through
290
+ * a string that carries its own words for "this is not a name at all". A
291
+ * second copy of the length and the alphabet written out here is the copy
292
+ * that goes stale.
293
+ */
294
+ seller_name: z
295
+ .string({
296
+ // A field that is missing is a client with a bug and a field holding
297
+ // null is a client with a misunderstanding. Only the second gets this
298
+ // sentence; the first falls through to the ordinary words about a
299
+ // field that is not there, which is what its author needs to read.
300
+ error: (issue) => issue.input === undefined
301
+ ? undefined
302
+ : "a seller name cannot be taken away, only changed: a merchant who wants to stop being listed pauses their selling, which leaves their cards where they can put them back on sale",
303
+ })
304
+ .pipe(ServiceNameSchema),
305
+ })
306
+ .meta({
307
+ description: "What a merchant sends to change the name their products are sold under. The same rule as the answer — at most 32 characters of printable ASCII, the catalog's rule rather than ours — and one difference: null is refused. A merchant goes from no name to a name and from one name to another, never back to none, because a payment request names the seller and there would be nobody to name: every card they have published would come off sale, which is an end to their selling arriving under the name of editing a setting. Somebody reaching for null wants one of two other things: a different name, which is this call with a different value, or an end to selling, which is the pause.",
308
+ });
309
+ /**
310
+ * The wallet a merchant's sales are paid into, as the merchant reads it back.
311
+ *
312
+ * Payments here are not held by anybody on the way: a buyer's agent pays the
313
+ * merchant's own address directly, and this is that address. There is no
314
+ * account of theirs on our side with a balance in it and no moment at which
315
+ * their money is ours, which is why this field exists at all — without it there
316
+ * would have to be.
317
+ *
318
+ * Null is the fact "nobody has said where the money goes", which is every
319
+ * merchant on the day they register. It is a value rather than an absent field
320
+ * for the reason the seller name is: a settings screen cannot tell a silence
321
+ * from a client that dropped the field, and reading the second as the first
322
+ * tells a merchant they are set up to be paid when nothing of theirs can be.
323
+ *
324
+ * What comes back is always the spelling a wallet shows, whichever of the two
325
+ * accepted spellings was sent. The door takes both and everything behind it
326
+ * holds one (ADR-0017), and this is the one for a reason that is about the
327
+ * person rather than about the storage: a merchant pastes forty characters out
328
+ * of their wallet and reads them back on a settings screen, and handed the same
329
+ * address in lower case they cannot tell it from a different address without
330
+ * comparing character by character. On the one field money is sent to, that
331
+ * glance is the whole of the checking anybody does.
332
+ */
333
+ export const PayoutWalletSchema = z
334
+ .strictObject({
335
+ /** Where this merchant's sales are paid, or nothing at all. */
336
+ payout_wallet: EvmAddressSchema.nullable(),
337
+ })
338
+ .meta({
339
+ description: "The address a merchant's sales are paid into. Payments are not held by anybody on the way: a buyer's agent pays this address directly, and it is the payTo of every payment request made for this merchant's products. Null means nobody has set one, which is where every merchant starts; the field is always present rather than left out, because an absent field is indistinguishable from a client that dropped it. The address comes back in the mixed-case spelling a wallet shows, whichever of the two accepted spellings was sent — so what a merchant reads back on a screen is character for character what they copied out of their wallet. On a deployment that settles on a real chain a merchant with no wallet here cannot publish a card, because the money from that card's sales would have nowhere to go.",
340
+ });
341
+ /**
342
+ * What a merchant sends to change where their sales are paid.
343
+ *
344
+ * The same field held to the same rule, and one difference, which is the same
345
+ * difference the seller name has and rests on something harder. There is no
346
+ * null. A merchant goes from having no wallet to having one and from one wallet
347
+ * to another, and not back: a merchant who took their address away would keep
348
+ * every card they had already published on sale, and the payment request an
349
+ * agent is answered with cannot be built at all without an address — so the
350
+ * products would stop being buyable and nothing anywhere would say why. What
351
+ * somebody reaching for null actually wants is one of two other acts: a
352
+ * different address, which is this same call, or an end to selling, which is
353
+ * the pause, and the pause leaves their cards where they can put them back.
354
+ *
355
+ * There is nowhere here to put a key, and a document carrying one is refused
356
+ * rather than trimmed. This contract knows where a merchant is paid and has no
357
+ * business knowing anything that could spend it.
358
+ */
359
+ export const PayoutWalletRequestSchema = z
360
+ .strictObject({
361
+ /**
362
+ * Where this merchant's sales are to be paid.
363
+ *
364
+ * The rule lives once, in `EvmAddressSchema`, and this reaches it through a
365
+ * string that carries its own words for "this is not an address at all".
366
+ */
367
+ payout_wallet: z
368
+ .string({
369
+ // A field that is missing is a client with a bug and a field holding
370
+ // null is a client with a misunderstanding. Only the second gets this
371
+ // sentence; the first falls through to the ordinary words about a
372
+ // field that is not there.
373
+ error: (issue) => issue.input === undefined
374
+ ? undefined
375
+ : "a payout wallet cannot be taken away, only changed: a merchant who wants to stop being paid pauses their selling, which leaves their cards where they can put them back on sale — a merchant with cards on sale and no wallet has products a payment request cannot even be written for",
376
+ })
377
+ .pipe(EvmAddressSchema),
378
+ })
379
+ .meta({
380
+ description: "What a merchant sends to set or change the address their sales are paid into. The same rule as the answer — 0x and forty hexadecimal characters, in lower case or in the exact mixed-case spelling a wallet shows — and one difference: null is refused. A merchant goes from no wallet to a wallet and from one wallet to another, never back to none, because their published cards stay on sale and a payment request for one of them cannot be written without an address. Somebody reaching for null wants either a different address, which is this call with a different value, or an end to selling, which is the pause. Nothing here takes a private key or a seed phrase, and a document carrying one is refused rather than ignored: this contract knows where a merchant is paid and nothing that could spend it.",
381
+ });
382
+ /**
383
+ * What somebody sends to become a merchant.
384
+ *
385
+ * One field, and what is not here is most of what is worth reading. The address
386
+ * and the password belong to the account rather than to the merchant, and they
387
+ * stay on the other side of the boundary (ADR-0014 §1) — a gateway that took
388
+ * either would be holding a person's credentials on the money path, which is
389
+ * what this route's whole shape is arranged to avoid.
390
+ *
391
+ * The name the seller's products are sold under is not here either, and that
392
+ * omission is a decision rather than a simplification. It is a public answer,
393
+ * and asking for it here asks for it at the one moment a merchant knows least:
394
+ * no products, no catalogue seen, no idea what the name is for. It is asked for
395
+ * on the screen after this one instead, where there is room to say why it
396
+ * matters, and it can be changed afterwards from the merchant's own settings.
397
+ *
398
+ * The shape refuses a name rather than ignoring one, because a field quietly
399
+ * dropped is a person believing they have chosen what buyers will read.
400
+ */
401
+ export const RegistrationRequestSchema = z
402
+ .strictObject({
403
+ /** The code handed over with the address of the site. */
404
+ invitation: InvitationSchema,
405
+ })
406
+ .meta({
407
+ description: "What somebody sends to become a merchant: the invitation code they were given, and nothing else. The name their products are sold under is deliberately not here — it is a public answer, and asked for on the way in it is answered by somebody with no products and no idea what the name is for; it is set afterwards, and changed afterwards, through the merchant's own call for it. Nothing about an account is here either: an address and a password belong to whatever signs the person in, and are never sent to the gateway. A document carrying either is refused rather than trimmed, because a field accepted and dropped is somebody believing they said something.",
408
+ });
409
+ /**
410
+ * What registering answers with: a merchant and the key their cabinet will call
411
+ * as them with.
412
+ *
413
+ * The key is made for a cabinet rather than for the merchant's own code, and
414
+ * that is what the caller of this route is. So it is in no list: a merchant who
415
+ * has just registered has no keys of their own at all, and the first one they
416
+ * do have is one they ask for. No row travels beside the secret for the same
417
+ * reason no row appears in the list — the merchant did not issue it and cannot
418
+ * disable it, so an identifier for it would be a value with nothing to do.
419
+ *
420
+ * No name comes back either, because none was chosen. A merchant who has just
421
+ * registered is listed under nothing at all, and a field here would either be a
422
+ * name this call invented or a null that says the same thing at more length.
423
+ */
424
+ export const RegisteredMerchantSchema = z
425
+ .strictObject({
426
+ /** The merchant that now exists, which every key and card of theirs names. */
427
+ merchant_id: IdentifierSchema,
428
+ /** The key itself, shown once, exactly as issuing one shows it. */
429
+ secret: KeySecretSchema,
430
+ })
431
+ .meta({
432
+ description: "What registering produced: the merchant, and the key whoever registered them will call as them with. The key is readable here and nowhere afterwards, so whoever made this call is the only party that can keep it. It is a key made for a cabinet rather than one of the merchant's own: it appears in no list of their keys and the call that revokes a key refuses its kind by name, so no row for it comes back here either. A merchant who has just registered has no keys of their own until they ask for one. The merchant is listed under no name yet and this answer carries none — the name their products are sold under is chosen afterwards, and until it is, publishing a card is refused. What this answer does not carry either is any notion of an account or a session: registering makes a merchant and a key, and whatever signs a person in is on the other side of this call.",
433
+ });
@@ -0,0 +1,47 @@
1
+ /**
2
+ * What state an order is in, in the words an agent and a merchant both read.
3
+ *
4
+ * These are the state machine's own endings, and they live here because they
5
+ * are wire names: an agent asks what became of its purchase, a merchant
6
+ * restarting a worker asks what is still open, and both have to get the same
7
+ * answer. A second list kept anywhere else would be a second answer to one
8
+ * question, and the one the machine keeps would win without anybody noticing.
9
+ *
10
+ * Every value below says what the agent can be told truthfully, which is not
11
+ * always everything the machine knows. Where the two differ the value says so
12
+ * in its own words: `rejected` is coarser than the machine's states behind it,
13
+ * and `in_progress` covers every step of a purchase that has not finished —
14
+ * an order just sent to the merchant and one the merchant has taken on and
15
+ * owes a delivery for are one word here, because to the buyer they are one
16
+ * situation. A merchant who needs those apart is not reading this vocabulary;
17
+ * they are reading their own order, which carries the answer they gave.
18
+ *
19
+ * The word for a purchase still running exists on purpose: an agent that had
20
+ * to read silence as an answer would take an unfinished order for a refused
21
+ * one, and hold a budget against a sale that is still going to happen.
22
+ *
23
+ * Two of the machine's own distinctions are deliberately not here, and saying
24
+ * which is the point — a list that claims to name every ending has to admit
25
+ * what it folded. The machine separates a purchase that never reached the
26
+ * merchant from one the merchant refused, and it separates an order sent to
27
+ * the merchant from one the merchant has taken on. Both distinctions are the
28
+ * merchant's: the first their metrics need, the second their own record
29
+ * already holds. To the buyer each pair is one situation, and this vocabulary
30
+ * is the buyer's. A merchant reading it and expecting their own view of the
31
+ * order is reading the wrong list.
32
+ */
33
+ import { z } from "zod";
34
+ export declare const ORDER_STATUSES: readonly ["in_progress", "delivered", "rejected", "payment_unresolved", "declined", "expired", "cancelled", "refund_due", "refunded", "delivered_unpaid"];
35
+ export declare const OrderStatusSchema: z.ZodEnum<{
36
+ delivered: "delivered";
37
+ in_progress: "in_progress";
38
+ rejected: "rejected";
39
+ payment_unresolved: "payment_unresolved";
40
+ declined: "declined";
41
+ expired: "expired";
42
+ cancelled: "cancelled";
43
+ refund_due: "refund_due";
44
+ refunded: "refunded";
45
+ delivered_unpaid: "delivered_unpaid";
46
+ }>;
47
+ export type OrderStatus = z.infer<typeof OrderStatusSchema>;
@@ -0,0 +1,102 @@
1
+ /**
2
+ * What state an order is in, in the words an agent and a merchant both read.
3
+ *
4
+ * These are the state machine's own endings, and they live here because they
5
+ * are wire names: an agent asks what became of its purchase, a merchant
6
+ * restarting a worker asks what is still open, and both have to get the same
7
+ * answer. A second list kept anywhere else would be a second answer to one
8
+ * question, and the one the machine keeps would win without anybody noticing.
9
+ *
10
+ * Every value below says what the agent can be told truthfully, which is not
11
+ * always everything the machine knows. Where the two differ the value says so
12
+ * in its own words: `rejected` is coarser than the machine's states behind it,
13
+ * and `in_progress` covers every step of a purchase that has not finished —
14
+ * an order just sent to the merchant and one the merchant has taken on and
15
+ * owes a delivery for are one word here, because to the buyer they are one
16
+ * situation. A merchant who needs those apart is not reading this vocabulary;
17
+ * they are reading their own order, which carries the answer they gave.
18
+ *
19
+ * The word for a purchase still running exists on purpose: an agent that had
20
+ * to read silence as an answer would take an unfinished order for a refused
21
+ * one, and hold a budget against a sale that is still going to happen.
22
+ *
23
+ * Two of the machine's own distinctions are deliberately not here, and saying
24
+ * which is the point — a list that claims to name every ending has to admit
25
+ * what it folded. The machine separates a purchase that never reached the
26
+ * merchant from one the merchant refused, and it separates an order sent to
27
+ * the merchant from one the merchant has taken on. Both distinctions are the
28
+ * merchant's: the first their metrics need, the second their own record
29
+ * already holds. To the buyer each pair is one situation, and this vocabulary
30
+ * is the buyer's. A merchant reading it and expecting their own view of the
31
+ * order is reading the wrong list.
32
+ */
33
+ import { z } from "zod";
34
+ export const ORDER_STATUSES = Object.freeze([
35
+ /** Running: no terminal answer yet, and no reason to read one into it. */
36
+ "in_progress",
37
+ /** Success: the product is with the agent and the money with the merchant. */
38
+ "delivered",
39
+ /**
40
+ * Closed and the buyer's money is known not to have moved — the product was
41
+ * gone, the parameters did not fit, the payment failed its check, the charge
42
+ * came back failed, or a synchronous handler refused.
43
+ *
44
+ * The machine keeps a finer distinction behind this word: a purchase that
45
+ * never reached the merchant and one the merchant refused are separate
46
+ * states there, because the merchant's own accounting needs them apart. To
47
+ * the agent both are one sentence — a refusal — and folding them was argued
48
+ * on the grounds that the reason travels separately, in the refusal code.
49
+ *
50
+ * It does travel, and what it carries is worth being exact about. The
51
+ * agent's status document has a `refusal` beside this word, and where a
52
+ * merchant refused the order it holds the two things the merchant actually
53
+ * wrote: a short code to branch on and a sentence to show. The word here
54
+ * stays coarse because that is the agent's vocabulary, and the pair beside
55
+ * it is what makes the coarseness affordable.
56
+ *
57
+ * What it does not carry is the other half of the fold, and an agent
58
+ * planning around this value needs it said. Two endings reach this word with
59
+ * nobody's words behind them — a product that was gone, and a payment this
60
+ * gateway would not vouch for — so they arrive as a bare `rejected` and are
61
+ * not distinguishable from each other here. The first is not worded because
62
+ * the price answer that reports it is an availability flag and carries no
63
+ * reason at all; the second is not worded here because it is refused at the
64
+ * agent's door in an error envelope that says what the payment layer said,
65
+ * which is a better place for it than a status read afterwards. Inventing
66
+ * codes of our own for either would be putting words in a mouth that never
67
+ * opened.
68
+ */
69
+ "rejected",
70
+ /**
71
+ * Closed, and nobody can say whether the buyer was charged: the payment
72
+ * network was asked and never answered.
73
+ *
74
+ * Deliberately not `rejected`, and this is the fifth gate in one value. An
75
+ * agent told its purchase did not happen goes and buys the same thing
76
+ * elsewhere without looking at its wallet — a claim we would be making with
77
+ * no evidence behind it. What we know is that we asked and heard nothing;
78
+ * saying so is the honest answer, and it is what a dispute, an error text
79
+ * and the merchant's reconciliation all read.
80
+ */
81
+ "payment_unresolved",
82
+ /** The merchant answered a confirmation request with "I will not deliver". */
83
+ "declined",
84
+ /**
85
+ * A deadline passed with nobody at fault: the confirmation, the payment that
86
+ * was to follow it, or the synchronous delivery.
87
+ */
88
+ "expired",
89
+ /** The merchant left, and orders still open closed with them. */
90
+ "cancelled",
91
+ /** The money moved and the delivery did not happen. */
92
+ "refund_due",
93
+ /** That debt has since been paid back to the buyer. */
94
+ "refunded",
95
+ /**
96
+ * The merchant delivered synchronously and the payment did not execute. The
97
+ * goods exist, nothing was paid, and the order stays open until a repeat
98
+ * purchase carries the payment through.
99
+ */
100
+ "delivered_unpaid",
101
+ ]);
102
+ export const OrderStatusSchema = z.enum(ORDER_STATUSES);
@@ -0,0 +1,50 @@
1
+ /**
2
+ * The order, as it reaches the merchant's handler.
3
+ *
4
+ * By the time an order exists the purchase has already been checked on our
5
+ * side: the card was found, the parameters passed the card's own declaration,
6
+ * the price was settled and the payment was verified. Invalid purchases, stale
7
+ * prices and payments that did not verify never reach a handler, which is why
8
+ * this schema is short.
9
+ *
10
+ * Everything here answers a question the handler would otherwise have to ask
11
+ * us: what to deliver, to whom, under which of its own keys, at what price,
12
+ * and whether the money behind it is real.
13
+ *
14
+ * What state an order is in is not a field here. Reading state back — one
15
+ * order, or every order still open — is a separate call, and the words it
16
+ * answers with are `OrderStatusSchema` in `order-status.ts`.
17
+ *
18
+ * A second thing an order does not carry is its deadline. The design canon
19
+ * says an order carries one and the portal says a synchronous order does, but
20
+ * the portal's own table of what an order consists of does not list it, and
21
+ * this schema follows the table. The consequence stands either way: this
22
+ * package is the whole of the SDK's dependency tree, so nothing in it lets a
23
+ * synchronous handler learn how long it has — and in that mode the budget is
24
+ * ours, not something the merchant set and could look up. Which of the two
25
+ * readings is right is a question for whoever settles the numbers.
26
+ *
27
+ * One shape the documentation promises is still missing, and it is named here
28
+ * rather than left to be found as an absence. In the mode where the merchant
29
+ * is asked before the money moves, a confirmation request reaches the same
30
+ * subscription as the orders and is marked as a request rather than an order —
31
+ * no payment has happened, and delivering against one is not allowed. Nothing
32
+ * in this package carries that mark, so the two would be indistinguishable on
33
+ * the wire. Rather than leave that as a trap, the card refuses to publish in
34
+ * that mode at all until the request has a shape; see `card.ts`.
35
+ */
36
+ import { z } from "zod";
37
+ export declare const OrderSchema: z.ZodObject<{
38
+ id: z.ZodString;
39
+ merchant_item_id: z.ZodString;
40
+ params: z.ZodRecord<z.ZodString, z.ZodUnknown>;
41
+ price: z.ZodObject<{
42
+ amount: z.ZodString;
43
+ currency: z.ZodString;
44
+ at: z.ZodISODateTime;
45
+ as_of: z.ZodISODateTime;
46
+ }, z.core.$strict>;
47
+ price_id: z.ZodOptional<z.ZodString>;
48
+ test: z.ZodBoolean;
49
+ }, z.core.$strict>;
50
+ export type Order = z.infer<typeof OrderSchema>;