@nuanu-ai/agentify 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/backoff.d.ts +17 -39
- package/dist/backoff.js +17 -39
- package/dist/check-card.d.ts +15 -24
- package/dist/check-card.js +15 -24
- package/dist/cli.d.ts +6 -16
- package/dist/cli.js +6 -16
- package/dist/client.d.ts +123 -251
- package/dist/client.js +113 -255
- package/dist/contract.d.ts +7 -10
- package/dist/contract.js +7 -10
- package/dist/index.d.ts +16 -28
- package/dist/index.js +16 -28
- package/dist/schema.d.ts +15 -37
- package/dist/schema.js +12 -30
- package/dist/transport.d.ts +43 -122
- package/dist/transport.js +53 -144
- package/dist/verify.d.ts +8 -17
- package/dist/verify.js +15 -58
- package/dist/worker.d.ts +93 -236
- package/dist/worker.js +111 -255
- package/package.json +2 -2
package/dist/transport.js
CHANGED
|
@@ -1,65 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Making one call of the surface.
|
|
2
|
+
* Making one call of the surface. Every address, method and answer document
|
|
3
|
+
* comes from the route table in `@nuanu-ai/agentify-contracts`, and the key's
|
|
4
|
+
* header is built by `merchantKeyHeaderValue` from the same package, so this
|
|
5
|
+
* package writes down no path and no header of its own.
|
|
3
6
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* accept may hold a slash or a space and pasted into an address unencoded it
|
|
8
|
-
* becomes two segments and a different route.
|
|
7
|
+
* The answer is read the same way whatever its HTTP status: the route's
|
|
8
|
+
* document if it is one, otherwise the gateway's error envelope if it is one,
|
|
9
|
+
* and only otherwise a failure this package describes itself, with the status.
|
|
9
10
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* The first is the header the merchant's key travels in. The table says which
|
|
16
|
-
* door a call is behind and not how the door is built, so the choice is the
|
|
17
|
-
* gateway's and ours to match — and matching it by each writing the same two
|
|
18
|
-
* strings down was agreement by luck, where a call behind that door fails with
|
|
19
|
-
* an authorisation error and nothing in either repository says why. So the name
|
|
20
|
-
* and the scheme live in `@nuanu-ai/agentify-contracts`, in the one place both sides
|
|
21
|
-
* already import: `merchantKeyHeaderValue` builds the value this package sends,
|
|
22
|
-
* and `merchantKeyFrom` is what the gateway reads it back with.
|
|
23
|
-
*
|
|
24
|
-
* The second is the status code, about which the table says nothing either.
|
|
25
|
-
* Some of the answers this surface gives are refusals that are still documents
|
|
26
|
-
* — a card that was not accepted, an order call that did not go through — and
|
|
27
|
-
* rather than guess which of those arrive under a 200 and which under a 400,
|
|
28
|
-
* the answer is read the same way whatever the status. If the body is the
|
|
29
|
-
* document the route names, that is the answer. Otherwise it is asked whether
|
|
30
|
-
* it is the one envelope every route refuses in, and if it is, what the caller
|
|
31
|
-
* is told is the gateway's own code and its own sentence rather than a
|
|
32
|
-
* complaint from here about a document that did not parse — the gateway is the
|
|
33
|
-
* side that knows why, and the reason it wrote is the only thing the caller
|
|
34
|
-
* can act on. Only a body that is neither is a failure this package has to
|
|
35
|
-
* describe itself, and then the status is part of what it says.
|
|
36
|
-
*
|
|
37
|
-
* The third is what a failure is at all. Nothing here throws: a call comes
|
|
38
|
-
* back either as the document or as a failure carrying a sentence and, where
|
|
39
|
-
* there was one, the body that could not be read. The layer above decides
|
|
40
|
-
* which of those the merchant is entitled to see as a return value and which
|
|
41
|
-
* is an exception, and that decision is in `client.ts`, where the contract
|
|
42
|
-
* says which calls have a failure branch of their own. The body travels with
|
|
43
|
-
* the failure because one caller needs it: a poll answer this SDK cannot parse
|
|
44
|
-
* may still name the contract version the gateway speaks, and that is how a
|
|
45
|
-
* genuine difference of dialects is told apart from a broken gateway.
|
|
11
|
+
* Nothing here throws. A call comes back as the document or as a failure;
|
|
12
|
+
* `client.ts` decides which failures a merchant sees returned and which
|
|
13
|
+
* thrown. The body travels with a failure because a poll answer this SDK
|
|
14
|
+
* cannot parse may still name the gateway's contract version.
|
|
46
15
|
*/
|
|
47
16
|
import { API_ROUTES, ErrorEnvelopeSchema, expandPath, MERCHANT_KEY_HEADER, merchantKeyHeaderValue, } from "@nuanu-ai/agentify-contracts";
|
|
48
17
|
/**
|
|
49
|
-
* What is known about whether the call got there.
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* a
|
|
53
|
-
*
|
|
54
|
-
* abandoned when the worker stopped, or one whose connection broke after the
|
|
55
|
-
* gateway had it. Those are not the same fact, and a merchant reconciling
|
|
56
|
-
* their books from the wrong one of them is reconciling from something we made
|
|
57
|
-
* up.
|
|
58
|
-
*
|
|
59
|
-
* `not_received` is claimed only where the network says so plainly: the
|
|
60
|
-
* connection was refused or the name did not resolve, so nothing was ever
|
|
61
|
-
* handed over. Everything else that produced no readable answer is `unknown`,
|
|
62
|
-
* because it is.
|
|
18
|
+
* What is known about whether the call got there. `not_received` is claimed
|
|
19
|
+
* only where the network says plainly that nothing was handed over: the
|
|
20
|
+
* connection was refused or the name did not resolve. Everything else without
|
|
21
|
+
* a readable answer is `unknown`, so that a merchant never reconciles their
|
|
22
|
+
* books from something this package made up.
|
|
63
23
|
*/
|
|
64
24
|
export const REACH = Object.freeze({
|
|
65
25
|
/** No connection was made, so the gateway certainly has nothing. */
|
|
@@ -67,31 +27,23 @@ export const REACH = Object.freeze({
|
|
|
67
27
|
/** The gateway answered, and the answer is not one this package can read. */
|
|
68
28
|
ANSWERED: "answered",
|
|
69
29
|
/**
|
|
70
|
-
* The exchange was cut short
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
* before the request left, a connection that broke once it was under way,
|
|
75
|
-
* and an answer that started arriving and stopped. What they share is that
|
|
76
|
-
* no complete answer was had, and that is all this may be read as saying.
|
|
30
|
+
* The exchange was cut short, and what the call did is not known here. It
|
|
31
|
+
* says nothing about direction: an abort may have fired before the request
|
|
32
|
+
* left, a connection may have broken under way, or an answer may have
|
|
33
|
+
* stopped arriving.
|
|
77
34
|
*/
|
|
78
35
|
UNKNOWN: "unknown",
|
|
79
36
|
});
|
|
80
37
|
/**
|
|
81
|
-
* The
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
* which is the answer that claims least. A code added here is a claim that the
|
|
85
|
-
* request certainly never arrived, and it should be added only when that is
|
|
86
|
-
* true of it.
|
|
38
|
+
* The network codes that mean nothing was handed over. Every other code reads
|
|
39
|
+
* as unknown, so add one here only if it certainly means the request never
|
|
40
|
+
* arrived.
|
|
87
41
|
*/
|
|
88
42
|
const NOTHING_WAS_SENT = new Set(["ECONNREFUSED", "ENOTFOUND", "EAI_AGAIN"]);
|
|
89
43
|
/**
|
|
90
|
-
* The network's own codes for why a call failed
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
* at each of them, and what comes back is one error holding the others. All of
|
|
94
|
-
* them have to say nothing was sent before we say it.
|
|
44
|
+
* The network's own codes for why a call failed. A name that resolves to
|
|
45
|
+
* several addresses is tried at each and fails with one error holding the
|
|
46
|
+
* others, so there can be several.
|
|
95
47
|
*/
|
|
96
48
|
const codesUnder = (cause) => {
|
|
97
49
|
const inner = cause?.cause;
|
|
@@ -105,12 +57,9 @@ const codesUnder = (cause) => {
|
|
|
105
57
|
return typeof code === "string" ? [code] : [];
|
|
106
58
|
};
|
|
107
59
|
/**
|
|
108
|
-
* What a failed `fetch` says about whether anything was handed over
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
* every address tried has to say nothing was sent — not merely one of them —
|
|
112
|
-
* is not reachable from a test that can only produce one address at a time,
|
|
113
|
-
* and a rule nothing checks is a rule that will be relaxed by accident.
|
|
60
|
+
* What a failed `fetch` says about whether anything was handed over: nothing,
|
|
61
|
+
* only where every address tried says so. Exported for its own test, which
|
|
62
|
+
* can produce several addresses where a socket test cannot.
|
|
114
63
|
*/
|
|
115
64
|
export const reachOf = (cause) => {
|
|
116
65
|
const codes = codesUnder(cause);
|
|
@@ -119,13 +68,9 @@ export const reachOf = (cause) => {
|
|
|
119
68
|
: REACH.UNKNOWN;
|
|
120
69
|
};
|
|
121
70
|
/**
|
|
122
|
-
* How much of an answer
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
* the thing that is actually broken — and a whole one is worth less than
|
|
126
|
-
* nothing, because a gateway can answer with a megabyte and this text ends up
|
|
127
|
-
* in a merchant's log. What is cut is said in the same sentence rather than
|
|
128
|
-
* left as an ellipsis a reader has to interpret.
|
|
71
|
+
* How much of an unreadable answer is quoted back in the failure. A proxy's
|
|
72
|
+
* error page often names what is broken, but a whole one can be a megabyte in
|
|
73
|
+
* a merchant's log; what is cut is said in the same sentence.
|
|
129
74
|
*/
|
|
130
75
|
const QUOTED_CHARACTERS = 200;
|
|
131
76
|
const quote = (text) => text.length <= QUOTED_CHARACTERS
|
|
@@ -144,36 +89,18 @@ const failure = (route, reason, reach, body, refusal) => ({
|
|
|
144
89
|
},
|
|
145
90
|
});
|
|
146
91
|
/**
|
|
147
|
-
* What is known about a failed call,
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
* It lives here rather than in each caller so that the four cases are
|
|
151
|
-
* described in one place and cannot drift into four different vocabularies.
|
|
152
|
-
* Three of them are the reaches; the fourth is a refusal the gateway wrote in
|
|
153
|
-
* words we recognise, which is the one failure here that is not a silence and
|
|
154
|
-
* so does not belong to any of them.
|
|
92
|
+
* What is known about a failed call, as a clause to put before the failure's
|
|
93
|
+
* own `reason`: one for each reach, and one for a refusal the gateway wrote in
|
|
94
|
+
* words this package recognises.
|
|
155
95
|
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
* broke in flight was certainly sent. And an answer that stopped mid-sentence
|
|
161
|
-
* means the gateway certainly had the call — a status and part of a body came
|
|
162
|
-
* back. So this clause says nothing about direction: not that the call was
|
|
163
|
-
* sent, not that nothing came back, and not that whether it arrived is
|
|
164
|
-
* unknown, because on the third road it is known and the answer is yes. What
|
|
165
|
-
* the three share is that the exchange was cut short, and so what the call did
|
|
166
|
-
* is not known — and that is the whole of what may be claimed here. The
|
|
167
|
-
* specifics belong to the failure's own `reason`, which every caller prints
|
|
168
|
-
* immediately after this clause and which names the road it came down.
|
|
96
|
+
* The clause for `unknown` covers three situations (an abort before sending, a
|
|
97
|
+
* connection broken in flight, an answer cut off mid-way), so it claims only
|
|
98
|
+
* that the exchange did not finish and what the call did is not known; the
|
|
99
|
+
* `reason` that follows names which it was.
|
|
169
100
|
*/
|
|
170
101
|
export const whatIsKnown = (failure) => {
|
|
171
|
-
//
|
|
172
|
-
//
|
|
173
|
-
// us, in a shape we recognise, that it would not do it. Sending a merchant
|
|
174
|
-
// "what came back could not be read" in front of a reason we are about to
|
|
175
|
-
// quote is the message arguing with itself, and the half they believe is the
|
|
176
|
-
// first one.
|
|
102
|
+
// A refusal comes first: the gateway said what it would not do, and "what
|
|
103
|
+
// came back could not be read" in front of its reason would contradict it.
|
|
177
104
|
if (failure.refusal !== undefined) {
|
|
178
105
|
return "it reached us and the answer says the call did not go through";
|
|
179
106
|
}
|
|
@@ -188,13 +115,11 @@ export const whatIsKnown = (failure) => {
|
|
|
188
115
|
};
|
|
189
116
|
/**
|
|
190
117
|
* One call, answered with the route's document or with a sentence about why
|
|
191
|
-
* there is
|
|
118
|
+
* there is none.
|
|
192
119
|
*
|
|
193
|
-
* The cast at the end is
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
* static type is the union of all of them. `DocumentOf<N>` names the same
|
|
197
|
-
* schema for the caller, and the two are the same object.
|
|
120
|
+
* The cast at the end is safe though the compiler cannot follow it: for an
|
|
121
|
+
* unresolved `name` the static type of the schema is a union of every route's,
|
|
122
|
+
* while at runtime it is the right one.
|
|
198
123
|
*/
|
|
199
124
|
export const callRoute = async (gateway, name, options = {}) => {
|
|
200
125
|
const route = API_ROUTES[name];
|
|
@@ -225,9 +150,8 @@ export const callRoute = async (gateway, name, options = {}) => {
|
|
|
225
150
|
text = await response.text();
|
|
226
151
|
}
|
|
227
152
|
catch (cause) {
|
|
228
|
-
// The gateway answered and the answer was not read to the end
|
|
229
|
-
// "
|
|
230
|
-
// a connection that broke on our side of the exchange.
|
|
153
|
+
// The gateway answered and the answer was not read to the end; calling it
|
|
154
|
+
// "not JSON" would blame the gateway for a connection broken on our side.
|
|
231
155
|
return failure(name, `${name} at ${url.href} began answering ${response.status} and the answer could not be read to the end: ${String(cause)}`, REACH.UNKNOWN);
|
|
232
156
|
}
|
|
233
157
|
let body;
|
|
@@ -241,27 +165,12 @@ export const callRoute = async (gateway, name, options = {}) => {
|
|
|
241
165
|
if (parsed.success) {
|
|
242
166
|
return { ok: true, document: parsed.data };
|
|
243
167
|
}
|
|
244
|
-
// A body that is not the route's document
|
|
245
|
-
//
|
|
246
|
-
//
|
|
247
|
-
//
|
|
248
|
-
//
|
|
249
|
-
//
|
|
250
|
-
// What keeps this from swallowing one of the surface's own documents is not
|
|
251
|
-
// the order of these two reads. Some of those documents are refusals — an
|
|
252
|
-
// order call that did not go through says so inside the shape its route
|
|
253
|
-
// promises — and the envelope declines them on its own: its outer object is
|
|
254
|
-
// strict, so a body carrying anything beside `error` is not one, and the
|
|
255
|
-
// `ok` discriminator those answers lead with is exactly such a field. The
|
|
256
|
-
// guarantee is held where it can be checked rather than here, by a case in
|
|
257
|
-
// `api.test.ts` that puts an envelope to every route's document and demands
|
|
258
|
-
// a refusal from each. Reversing these two reads is not what would break it,
|
|
259
|
-
// which is why this paragraph does not claim the order is load-bearing.
|
|
260
|
-
//
|
|
261
|
-
// The status is not consulted, here or above. Which refusals of this surface
|
|
262
|
-
// arrive under which code is the gateway's to decide and is written down
|
|
263
|
-
// nowhere both sides read, so a rule keyed on it would be a guess; the shape
|
|
264
|
-
// of the answer is the agreement, and it is the same at 402 as at 500.
|
|
168
|
+
// A body that is not the route's document may be the error envelope, and
|
|
169
|
+
// then the caller gets what the gateway said. Refusals that live inside a
|
|
170
|
+
// route's own document cannot be mistaken for it, because the envelope's
|
|
171
|
+
// outer object is strict and those documents carry `ok`; a test in
|
|
172
|
+
// `api.test.ts` holds that for every route. The HTTP status is not
|
|
173
|
+
// consulted: which refusal arrives under which status is the gateway's.
|
|
265
174
|
const refused = ErrorEnvelopeSchema.safeParse(body);
|
|
266
175
|
if (refused.success) {
|
|
267
176
|
const { code, message, retryable } = refused.data.error;
|
package/dist/verify.d.ts
CHANGED
|
@@ -1,22 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `agentify verify
|
|
3
|
-
*
|
|
2
|
+
* `agentify verify`: the check a merchant runs on their card files before
|
|
3
|
+
* publishing them, whether each card is enough for an agent to assemble a
|
|
4
|
+
* correct purchase, read the way publication reads it.
|
|
4
5
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* output says so and where it is proved instead: with a test purchase against
|
|
11
|
-
* the merchant's own delivery system. Counted here as a check that "did not
|
|
12
|
-
* run", it made every complete card answer as something short of success, and
|
|
13
|
-
* a build reading the exit code took a good card for a failure.
|
|
14
|
-
*
|
|
15
|
-
* Which cards are checked is asked for rather than discovered. Nothing in this
|
|
16
|
-
* package, in the contract or in any decision says where a merchant keeps
|
|
17
|
-
* their cards, and a command that went looking would be inventing a
|
|
18
|
-
* convention — a file name, a directory, a manifest — that nobody agreed to
|
|
19
|
-
* and that every merchant would then have to work around.
|
|
6
|
+
* Whether the merchant's handler holds against one order delivered twice
|
|
7
|
+
* cannot be tried by a command holding only card files, so the output says it
|
|
8
|
+
* is not checked here and where it is proved instead; it does not count
|
|
9
|
+
* against the exit code. The cards to check are named on the command line,
|
|
10
|
+
* because nothing says where a merchant keeps them.
|
|
20
11
|
*/
|
|
21
12
|
/** What the command answers with. */
|
|
22
13
|
export declare const VERIFY_EXIT: Readonly<{
|
package/dist/verify.js
CHANGED
|
@@ -1,22 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `agentify verify
|
|
3
|
-
*
|
|
2
|
+
* `agentify verify`: the check a merchant runs on their card files before
|
|
3
|
+
* publishing them, whether each card is enough for an agent to assemble a
|
|
4
|
+
* correct purchase, read the way publication reads it.
|
|
4
5
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* output says so and where it is proved instead: with a test purchase against
|
|
11
|
-
* the merchant's own delivery system. Counted here as a check that "did not
|
|
12
|
-
* run", it made every complete card answer as something short of success, and
|
|
13
|
-
* a build reading the exit code took a good card for a failure.
|
|
14
|
-
*
|
|
15
|
-
* Which cards are checked is asked for rather than discovered. Nothing in this
|
|
16
|
-
* package, in the contract or in any decision says where a merchant keeps
|
|
17
|
-
* their cards, and a command that went looking would be inventing a
|
|
18
|
-
* convention — a file name, a directory, a manifest — that nobody agreed to
|
|
19
|
-
* and that every merchant would then have to work around.
|
|
6
|
+
* Whether the merchant's handler holds against one order delivered twice
|
|
7
|
+
* cannot be tried by a command holding only card files, so the output says it
|
|
8
|
+
* is not checked here and where it is proved instead; it does not count
|
|
9
|
+
* against the exit code. The cards to check are named on the command line,
|
|
10
|
+
* because nothing says where a merchant keeps them.
|
|
20
11
|
*/
|
|
21
12
|
import { readFileSync } from "node:fs";
|
|
22
13
|
import { basename } from "node:path";
|
|
@@ -56,43 +47,11 @@ const USAGE = [
|
|
|
56
47
|
"handler holds against an order delivered twice is not checked here.",
|
|
57
48
|
].join("\n");
|
|
58
49
|
/**
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
* gateway nothing, so it has no way to see anything that was published.
|
|
65
|
-
*
|
|
66
|
-
* The call it would have needed does exist, and saying so is the honest half
|
|
67
|
-
* of the answer: `list_merchant_cards` returns published cards whole, each of
|
|
68
|
-
* them the card its author wrote rather than an agent's projection of it. What
|
|
69
|
-
* that route promises is worth repeating exactly, because every part of it is
|
|
70
|
-
* easy to overstate. Whose cards it returns is settled: the merchant whose key
|
|
71
|
-
* the call was made with, and nobody else. A card reaches that list by being accepted at
|
|
72
|
-
* `publish_card`, which parses the same `CardSchema` and applies the same
|
|
73
|
-
* price rule this package does, so the cards on it passed this check on the
|
|
74
|
-
* day they went out — which is not the same as passing it now. The schema can
|
|
75
|
-
* move, and the price rule is applied only at publication: a stored card is
|
|
76
|
-
* parsed again on its way back out against the schema alone, so a card
|
|
77
|
-
* published before the price rule tightened is read back as it was, and one
|
|
78
|
-
* that no longer fits a schema that moved fails that read. And a
|
|
79
|
-
* command that builds no client can never learn which version of the contract
|
|
80
|
-
* the gateway is speaking. Publishing is more than this check, too: it can
|
|
81
|
-
* refuse a card for reasons the contract does not carry, which `checkCard`
|
|
82
|
-
* says in its own words.
|
|
83
|
-
*
|
|
84
|
-
* Taking no key is a choice and not a hole, which is worth saying here because
|
|
85
|
-
* the first line of the message reads like a limitation somebody would set out
|
|
86
|
-
* to remove. A check that read published cards over that route would all but
|
|
87
|
-
* always pass — they got onto it by passing this very check — and a green that
|
|
88
|
-
* cannot fail teaches a merchant to stop reading it. What would earn a keyed mode is a
|
|
89
|
-
* dry run of publishing, which can refuse for reasons no schema carries; there
|
|
90
|
-
* is no such route (`docs/research/00-open-questions.md`).
|
|
91
|
-
*
|
|
92
|
-
* So this is a stop and not a scolding, and it is answered as a call the
|
|
93
|
-
* command cannot work from. The way through it is to name the card files, which are
|
|
94
|
-
* the copy the merchant can still change — the file is what the next publish
|
|
95
|
-
* carries, whether the card is new or an edit to one already out.
|
|
50
|
+
* What the bare command, with no card files, answers. This command takes no
|
|
51
|
+
* key and builds no client, so it cannot see published cards; and checking
|
|
52
|
+
* them would all but always pass, since a card is published only after the
|
|
53
|
+
* same check. A green that cannot fail teaches a merchant to stop reading it.
|
|
54
|
+
* The card files are what the next publish carries, so they are what to name.
|
|
96
55
|
*/
|
|
97
56
|
const NOTHING_TO_CHECK = [
|
|
98
57
|
"agentify verify was given no card files, and it does not go looking for them:",
|
|
@@ -169,10 +128,8 @@ export const runVerify = async (argv, say) => {
|
|
|
169
128
|
}
|
|
170
129
|
const faulted = checked.filter((card) => card.problems.length > 0);
|
|
171
130
|
if (faulted.length > 0) {
|
|
172
|
-
// The truncation, said out loud
|
|
173
|
-
//
|
|
174
|
-
// reached when the first passes, so a short list is not a promise that
|
|
175
|
-
// one round of fixes is enough.
|
|
131
|
+
// The truncation, said out loud: the rules between fields are reached
|
|
132
|
+
// only once the shape passes, so one round of fixes may not be enough.
|
|
176
133
|
say(" Fix these and run again: a card whose shape is wrong is not checked against the rules that compare one field with another, so there may be more behind them.");
|
|
177
134
|
}
|
|
178
135
|
say("");
|