@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/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,22 +1,13 @@
1
1
  /**
2
- * `agentify verify` — the check a merchant runs on their cards before they
3
- * publish them.
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
- * It checks one thing, in full: whether a card is enough for an agent to
6
- * assemble a correct purchase, read the way publication reads it. Whether the
7
- * merchant's handler holds against one order delivered twice is not something
8
- * a command holding only card files can try — no route raises an order for a
9
- * merchant's own card — so it is not one of this command's checks, and the
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` — the check a merchant runs on their cards before they
3
- * publish them.
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
- * It checks one thing, in full: whether a card is enough for an agent to
6
- * assemble a correct purchase, read the way publication reads it. Whether the
7
- * merchant's handler holds against one order delivered twice is not something
8
- * a command holding only card files can try — no route raises an order for a
9
- * merchant's own card — so it is not one of this command's checks, and the
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
- * Why the bare command the documentation shows does not run.
60
- *
61
- * `agentify verify` with nothing after it would check the cards the merchant
62
- * has already published. The binding reason it cannot is the plainest one:
63
- * this command takes no key and no address, builds no client and asks the
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. A card is checked in two stages and the
173
- // second — the rules that compare one field against another — is only
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("");