@nuanu-ai/agentify 0.4.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/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
- * Every address, method and answer document comes out of the route table in
5
- * `@nuanu-ai/agentify-contracts`. No path is written down in this package, and the
6
- * table's own `expandPath` does the substituting, because an identifier we
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
- * The table deliberately leaves out three things that a program actually
11
- * making the calls cannot avoid deciding, and it is worth saying here what was
12
- * decided, because each of them is a place where the two sides can disagree
13
- * without either of them noticing.
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
- * Three and not two, and the third is the one that matters. A first draft had
52
- * a boolean, which meant every failure that was not an answer was filed as
53
- * "it did not reach us" — including a request that was fully sent and then
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. What the call did is not known here.
71
- *
72
- * Deliberately silent about direction, because three different things end
73
- * up here and they do not agree about it: an abort that may have fired
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 three codes the network gives us that mean nothing was handed over.
82
- *
83
- * Kept short on purpose: every code not on this list is read as "unknown",
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, however many there were.
91
- *
92
- * There can be several: a name that resolves to more than one address is tried
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
- * Exported for its own test rather than only through a socket: the rule that
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 we could not read is quoted back in the failure.
123
- *
124
- * A quote is worth more than a length — an HTML error page from a proxy names
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, in a clause that can be dropped into a
148
- * sentence about whatever the call was carrying.
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
- * Each clause is held to what its own case actually knows, and the last reach
157
- * is the one that has to be written carefully, because it is one sentence in
158
- * front of three different situations. A call abandoned on an abort throws
159
- * with no code at all and may never have left this process. A connection that
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
- // The fourth case, ahead of the three reaches. Each of those says some
172
- // version of "we cannot tell you what this call did"; here the gateway told
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 no document.
118
+ * there is none.
192
119
  *
193
- * The cast at the end is the one place this file steps outside what the
194
- * compiler can follow. `API_ROUTES[name]` for an unresolved `name` is a union
195
- * of every route, so the schema reached at runtime is the right one while its
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. Saying
229
- // "answered with something that is not JSON" here would be blaming it for
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 is asked whether it is the
245
- // envelope every route says no in, and if it is, the caller is handed what
246
- // the gateway said rather than our complaint about being unable to read it.
247
- // That is the difference between telling a merchant their order is over and
248
- // sending them to read our schemas.
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,74 +1,23 @@
1
1
  /**
2
- * `agentify verify` — the check a merchant runs on themselves before they
3
- * ask us to look.
4
- *
5
- * The portal describes two checks. Is the card enough for an agent to assemble
6
- * a correct purchase, and does the merchant's handler hold idempotency — that
7
- * is, does a second delivery appear when one and the same order arrives twice.
8
- *
9
- * The first is here in full. The second is not, and this file refuses to
10
- * pretend otherwise: it names what is missing, in the same words anyone can
11
- * check against the route table, and answers with a code of its own that says
12
- * "did not run" rather than "passed" or "failed". The reasons are written out
13
- * in `IDEMPOTENCY_IS_NOT_BUILDABLE` below and are worth reading before anybody
14
- * tries to add the check, because the gap is not in this package.
15
- *
16
- * Which cards are checked is asked for rather than discovered. Nothing in this
17
- * package, in the contract or in any decision says where a merchant keeps
18
- * their cards, and a command that went looking would be inventing a
19
- * convention — a file name, a directory, a manifest — that nobody agreed to
20
- * and that every merchant would then have to work around.
21
- */
22
- /**
23
- * What the command answers with.
24
- *
25
- * Four and not two, because a build that branches on this needs to tell a
26
- * check that failed from a check that never ran. Collapsing the two would make
27
- * a missing check look like a passing one on the day somebody decides that
28
- * anything non-zero is a failure and anything else is fine.
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.
5
+ *
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.
29
11
  */
12
+ /** What the command answers with. */
30
13
  export declare const VERIFY_EXIT: Readonly<{
31
- /** Every check ran and every check passed. */
14
+ /** Every card it was given is complete as far as the contract can tell. */
32
15
  PASSED: 0;
33
- /** A check ran and found something. */
16
+ /** A card has findings. */
34
17
  PROBLEMS: 1;
35
18
  /** The command was called with something it cannot work from. */
36
19
  USAGE: 2;
37
- /** A check could not be run at all, so nothing is claimed about it. */
38
- COULD_NOT_RUN: 3;
39
20
  }>;
40
- /**
41
- * Why the idempotency run is not in this version, stated so it can be checked
42
- * rather than taken on trust.
43
- *
44
- * The run the portal describes needs a test order to exist: the same order
45
- * delivered twice through the live subscription, marked `test`, against a card
46
- * that is published but not yet in any catalog. Four things it needs are
47
- * absent, and none of them is ours to add here.
48
- *
49
- * There is no route that asks for one. The only way an order comes into being
50
- * on this surface is `purchase_item`, and that is the payment exchange itself
51
- * — an agent buying — not a request a merchant can make about their own card.
52
- *
53
- * `PurchaseRequestSchema` is closed and carries the purchase parameters and
54
- * nothing else, so there is nowhere in a purchase to say "this one is a test".
55
- *
56
- * Nothing says how an order's `test` flag comes to be true. The order carries
57
- * it, the handler is told to branch on it, and no document in the contract
58
- * describes who sets it or from what.
59
- *
60
- * And a card carries no marker for "published, not yet in catalogs", which is
61
- * the state the portal says these orders are raised against.
62
- *
63
- * What is missing is not an environment to run it in. There are two sites, the
64
- * test one settles against test funds, and a key says which it belongs to
65
- * (ADR-0020) — so a merchant already has somewhere to try their integration
66
- * without spending anything. What is missing is a way to make an order happen
67
- * there: no route raises one, and the four absences above are the whole of what
68
- * would have to exist first. Inventing a route or a field here would be
69
- * designing that, and this is not the place where it is designed.
70
- */
71
- export declare const IDEMPOTENCY_IS_NOT_BUILDABLE: string;
72
21
  /** The code a finding carries when the file held no card to check at all. */
73
22
  export declare const NOT_JSON = "not_json";
74
23
  export type Say = (line: string) => void;
package/dist/verify.js CHANGED
@@ -1,90 +1,33 @@
1
1
  /**
2
- * `agentify verify` — the check a merchant runs on themselves before they
3
- * ask us to look.
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
- * The portal describes two checks. Is the card enough for an agent to assemble
6
- * a correct purchase, and does the merchant's handler hold idempotency — that
7
- * is, does a second delivery appear when one and the same order arrives twice.
8
- *
9
- * The first is here in full. The second is not, and this file refuses to
10
- * pretend otherwise: it names what is missing, in the same words anyone can
11
- * check against the route table, and answers with a code of its own that says
12
- * "did not run" rather than "passed" or "failed". The reasons are written out
13
- * in `IDEMPOTENCY_IS_NOT_BUILDABLE` below and are worth reading before anybody
14
- * tries to add the check, because the gap is not in this package.
15
- *
16
- * Which cards are checked is asked for rather than discovered. Nothing in this
17
- * package, in the contract or in any decision says where a merchant keeps
18
- * their cards, and a command that went looking would be inventing a
19
- * convention — a file name, a directory, a manifest — that nobody agreed to
20
- * 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.
21
11
  */
22
12
  import { readFileSync } from "node:fs";
23
13
  import { basename } from "node:path";
24
14
  import { checkCard } from "./check-card.js";
25
15
  import { describeProblems } from "./schema.js";
26
- /**
27
- * What the command answers with.
28
- *
29
- * Four and not two, because a build that branches on this needs to tell a
30
- * check that failed from a check that never ran. Collapsing the two would make
31
- * a missing check look like a passing one on the day somebody decides that
32
- * anything non-zero is a failure and anything else is fine.
33
- */
16
+ /** What the command answers with. */
34
17
  export const VERIFY_EXIT = Object.freeze({
35
- /** Every check ran and every check passed. */
18
+ /** Every card it was given is complete as far as the contract can tell. */
36
19
  PASSED: 0,
37
- /** A check ran and found something. */
20
+ /** A card has findings. */
38
21
  PROBLEMS: 1,
39
22
  /** The command was called with something it cannot work from. */
40
23
  USAGE: 2,
41
- /** A check could not be run at all, so nothing is claimed about it. */
42
- COULD_NOT_RUN: 3,
43
24
  });
44
- /**
45
- * Why the idempotency run is not in this version, stated so it can be checked
46
- * rather than taken on trust.
47
- *
48
- * The run the portal describes needs a test order to exist: the same order
49
- * delivered twice through the live subscription, marked `test`, against a card
50
- * that is published but not yet in any catalog. Four things it needs are
51
- * absent, and none of them is ours to add here.
52
- *
53
- * There is no route that asks for one. The only way an order comes into being
54
- * on this surface is `purchase_item`, and that is the payment exchange itself
55
- * — an agent buying — not a request a merchant can make about their own card.
56
- *
57
- * `PurchaseRequestSchema` is closed and carries the purchase parameters and
58
- * nothing else, so there is nowhere in a purchase to say "this one is a test".
59
- *
60
- * Nothing says how an order's `test` flag comes to be true. The order carries
61
- * it, the handler is told to branch on it, and no document in the contract
62
- * describes who sets it or from what.
63
- *
64
- * And a card carries no marker for "published, not yet in catalogs", which is
65
- * the state the portal says these orders are raised against.
66
- *
67
- * What is missing is not an environment to run it in. There are two sites, the
68
- * test one settles against test funds, and a key says which it belongs to
69
- * (ADR-0020) — so a merchant already has somewhere to try their integration
70
- * without spending anything. What is missing is a way to make an order happen
71
- * there: no route raises one, and the four absences above are the whole of what
72
- * would have to exist first. Inventing a route or a field here would be
73
- * designing that, and this is not the place where it is designed.
74
- */
75
- export const IDEMPOTENCY_IS_NOT_BUILDABLE = [
76
- "The idempotency run needs a test order, and nothing on the surface can ask for one:",
77
- " - no route raises an order for a merchant's own card; the only way an order",
78
- " comes into being is purchase_item, which is an agent's payment exchange",
79
- " - purchase_item's body carries the purchase parameters and nothing else, so",
80
- " there is nowhere in it to say that a purchase is a test",
81
- " - nothing in the contract says how an order's test flag comes to be true",
82
- " - a card carries no marker for published-but-not-yet-in-catalogs, which is",
83
- " the state the documentation raises these orders against",
84
- "The test site is not what is missing: it exists, it settles against test",
85
- "funds, and a csk_test_ key belongs to it. What is missing is a way to make",
86
- "an order happen there, and inventing a route or a field here would be",
87
- "designing one.",
25
+ /** What this command does not check, and where that is proved instead. */
26
+ const NOT_CHECKED_HERE = [
27
+ "Repeats of one order: not checked by this command.",
28
+ " Whether a second delivery of the same order makes second goods is proved",
29
+ " against your own delivery system, with a test purchase: the buyer has to",
30
+ " keep what the first delivery carried.",
88
31
  ].join("\n");
89
32
  /** The code a finding carries when the file held no card to check at all. */
90
33
  export const NOT_JSON = "not_json";
@@ -99,49 +42,16 @@ const USAGE = [
99
42
  "",
100
43
  "Checks each card against the published contract before it is published.",
101
44
  "",
102
- "Answers: 1 a check found something, 2 called with something it cannot work",
103
- "from, 3 a check could not be run. Zero, which means every check passed, is",
104
- "not reachable today: the idempotency run cannot be built, and this command",
105
- "will not report success for a check that never happened.",
45
+ "Answers: 0 every card is complete as far as the contract can tell, 1 a card",
46
+ "has findings, 2 called with something it cannot work from. Whether your",
47
+ "handler holds against an order delivered twice is not checked here.",
106
48
  ].join("\n");
107
49
  /**
108
- * Why the bare command the documentation shows does not run.
109
- *
110
- * `agentify verify` with nothing after it would check the cards the merchant
111
- * has already published. The binding reason it cannot is the plainest one:
112
- * this command takes no key and no address, builds no client and asks the
113
- * gateway nothing, so it has no way to see anything that was published.
114
- *
115
- * The call it would have needed does exist, and saying so is the honest half
116
- * of the answer: `list_merchant_cards` returns published cards whole, each of
117
- * them the card its author wrote rather than an agent's projection of it. What
118
- * that route promises is worth repeating exactly, because every part of it is
119
- * easy to overstate. Whose cards it returns is settled: the merchant whose key
120
- * the call was made with, and nobody else. A card reaches that list by being accepted at
121
- * `publish_card`, which parses the same `CardSchema` and applies the same
122
- * price rule this package does, so the cards on it passed this check on the
123
- * day they went out — which is not the same as passing it now. The schema can
124
- * move, and the price rule is applied only at publication: a stored card is
125
- * parsed again on its way back out against the schema alone, so a card
126
- * published before the price rule tightened is read back as it was, and one
127
- * that no longer fits a schema that moved fails that read. And a
128
- * command that builds no client can never learn which version of the contract
129
- * the gateway is speaking. Publishing is more than this check, too: it can
130
- * refuse a card for reasons the contract does not carry, which `checkCard`
131
- * says in its own words.
132
- *
133
- * Taking no key is a choice and not a hole, which is worth saying here because
134
- * the first line of the message reads like a limitation somebody would set out
135
- * to remove. A check that read published cards over that route would all but
136
- * always pass — they got onto it by passing this very check — and a green that
137
- * cannot fail teaches a merchant to stop reading it. What would earn a keyed mode is a
138
- * dry run of publishing, which can refuse for reasons no schema carries; there
139
- * is no such route (`docs/research/00-open-questions.md`).
140
- *
141
- * So this is a stop and not a scolding, and it is answered with the code that
142
- * means "did not run". The way through it is to name the card files, which are
143
- * the copy the merchant can still change — the file is what the next publish
144
- * 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.
145
55
  */
146
56
  const NOTHING_TO_CHECK = [
147
57
  "agentify verify was given no card files, and it does not go looking for them:",
@@ -195,7 +105,7 @@ export const runVerify = async (argv, say) => {
195
105
  say(NOTHING_TO_CHECK);
196
106
  say("");
197
107
  say(USAGE);
198
- return VERIFY_EXIT.COULD_NOT_RUN;
108
+ return VERIFY_EXIT.USAGE;
199
109
  }
200
110
  const checked = [];
201
111
  for (const file of files) {
@@ -218,19 +128,15 @@ export const runVerify = async (argv, say) => {
218
128
  }
219
129
  const faulted = checked.filter((card) => card.problems.length > 0);
220
130
  if (faulted.length > 0) {
221
- // The truncation, said out loud. A card is checked in two stages and the
222
- // second — the rules that compare one field against another — is only
223
- // reached when the first passes, so a short list is not a promise that
224
- // 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.
225
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.");
226
134
  }
227
135
  say("");
228
- say("Idempotency");
229
- say(" could not be run.");
230
- say(IDEMPOTENCY_IS_NOT_BUILDABLE);
136
+ say(NOT_CHECKED_HERE);
231
137
  say("");
232
138
  say(faulted.length > 0
233
- ? "Verdict: the cards have findings, and the idempotency run did not happen."
234
- : "Verdict: the cards are complete. Nothing is claimed about idempotency — that check did not run.");
235
- return faulted.length > 0 ? VERIFY_EXIT.PROBLEMS : VERIFY_EXIT.COULD_NOT_RUN;
139
+ ? "Verdict: the cards have findings."
140
+ : "Verdict: the cards are complete as far as the contract can tell.");
141
+ return faulted.length > 0 ? VERIFY_EXIT.PROBLEMS : VERIFY_EXIT.PASSED;
236
142
  };