@ziffer-io/client 0.1.0 → 0.2.0
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/README.md +110 -49
- package/dist/client.d.ts +317 -14
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +478 -40
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/dist/retry.d.ts +201 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/retry.js +240 -0
- package/dist/retry.js.map +1 -0
- package/package.json +14 -11
package/README.md
CHANGED
|
@@ -1,69 +1,130 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @ziffer-io/client
|
|
2
2
|
|
|
3
|
-
The
|
|
4
|
-
|
|
3
|
+
The ZIFFER client for TypeScript. Propose an action, wait for the decision, and verify the signed
|
|
4
|
+
receipt in your own process before you act.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
5
7
|
|
|
6
8
|
```bash
|
|
7
9
|
npm install @ziffer-io/client
|
|
8
10
|
```
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
with it. `verifyReceipt` is **re-exported unchanged** from the verifier's own package, so there is
|
|
12
|
-
one verifier in your dependency tree rather than two that can disagree — the tests assert it is
|
|
13
|
-
the same function object, so the re-export cannot decay into a copy quietly.
|
|
12
|
+
Node 22 or later. The wire types and the receipt verifier are installed with it.
|
|
14
13
|
|
|
15
|
-
##
|
|
14
|
+
## Quickstart
|
|
16
15
|
|
|
17
16
|
```ts
|
|
18
|
-
import {
|
|
17
|
+
import { readFileSync } from 'node:fs';
|
|
18
|
+
import { ZifferClient, verifyReceipt, type TrustAnchor } from '@ziffer-io/client';
|
|
19
19
|
|
|
20
|
-
const
|
|
21
|
-
const anchor: TrustAnchor =
|
|
20
|
+
const keys = JSON.parse(readFileSync(process.env.ZIFFER_TRUST_ANCHOR, 'utf8'));
|
|
21
|
+
const anchor: TrustAnchor = {
|
|
22
|
+
classical: Buffer.from(keys.ed25519_pk_hex, 'hex'),
|
|
23
|
+
pq: Buffer.from(keys.mldsa65_pk_hex, 'hex'),
|
|
24
|
+
minSuite: process.env.ZIFFER_SUITE_FLOOR,
|
|
25
|
+
};
|
|
22
26
|
|
|
27
|
+
const client = new ZifferClient(process.env.ZIFFER_API_URL, process.env.ZIFFER_API_KEY);
|
|
23
28
|
const submitted = await client.propose(proposal);
|
|
24
29
|
const decision = await client.wait(submitted.decision_id, { timeoutMs: 30_000 });
|
|
25
|
-
if (decision.outcome !== 'ALLOW') throw new Error(`ziffer refused: ${decision.
|
|
30
|
+
if (decision.outcome !== 'ALLOW') throw new Error(`ziffer refused: ${decision.refusal_category}`);
|
|
26
31
|
|
|
27
32
|
verifyReceipt(decision.receipt, new TextEncoder().encode(JSON.stringify(proposal)), anchor);
|
|
28
|
-
await bank.transfer(amount, toAccount);
|
|
33
|
+
await bank.transfer(amount, toAccount); // your line, unchanged
|
|
29
34
|
```
|
|
30
35
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
36
|
+
You pass the proposal twice on purpose. The verifier hashes the bytes you hand it and compares
|
|
37
|
+
them with the receipt's claim. The check is against your copy, not against ours. Key order and
|
|
38
|
+
spacing do not matter.
|
|
39
|
+
|
|
40
|
+
`verifyReceipt` checks the answer. Your own `if` is what stops the action.
|
|
41
|
+
|
|
42
|
+
An action that needs human approval answers `ATTEST` and no receipt. Keep polling
|
|
43
|
+
`client.wait` for the same decision id until `decision.receipt` is present, then verify it.
|
|
44
|
+
|
|
45
|
+
Under TypeScript's strict settings `process.env` values are typed `string | undefined`. Read them
|
|
46
|
+
through a small helper that throws when one is not set. The SDK guide has one.
|
|
47
|
+
|
|
48
|
+
## Configuration
|
|
49
|
+
|
|
50
|
+
| Variable | What it is | Where the value comes from |
|
|
51
|
+
| --- | --- | --- |
|
|
52
|
+
| `ZIFFER_API_KEY` | Your API key. It carries your tenant, so no request names a tenant. | We issue it. It expires after 90 days unless you ask for another lifetime. |
|
|
53
|
+
| `ZIFFER_TRUST_ANCHOR` | Path to the public key file your receipts are signed under. | We give you the file. Take it from us, never from the API you are checking. |
|
|
54
|
+
| `ZIFFER_SUITE_FLOOR` | The weakest signature suite you will accept. | You choose it. There is no default. |
|
|
55
|
+
| `ZIFFER_API_URL` | The base URL of the ZIFFER deployment you call. | We give it to you with your key. |
|
|
56
|
+
|
|
57
|
+
## When a request is refused
|
|
58
|
+
|
|
59
|
+
Every refusal is a thrown `Refusal` whose `clause` names the rule that fired; the table of every
|
|
60
|
+
clause, what it means and what to do is at https://ziffer.io/docs/refusals. Narrow with
|
|
61
|
+
`instanceof Refusal`, record the clause, and do not retry it.
|
|
62
|
+
|
|
63
|
+
## When a call does not get through
|
|
64
|
+
|
|
65
|
+
The client resends a request only when resending can help. That is: when the call got no answer at
|
|
66
|
+
all — the connection was refused, DNS failed, or the round trip took longer than 10 seconds — and
|
|
67
|
+
when the answer was 429, 502, 503 or 504.
|
|
68
|
+
|
|
69
|
+
Everything else reaches you as it is. A 400, 401, 403 or 404 means the request was refused on its
|
|
70
|
+
merits, and the same bytes will be refused the same way. A 500 is not resent either, and that one
|
|
71
|
+
is deliberate: ZIFFER reports its own degradations as 502 and 503, so a 500 is a fault it did not
|
|
72
|
+
expect, and repeating it repeats the fault.
|
|
73
|
+
|
|
74
|
+
Your bytes are sent again unchanged. A resent proposal lands on the hold the first attempt already
|
|
75
|
+
opened, so a retry never asks a second person to approve the same action.
|
|
76
|
+
|
|
77
|
+
There are at most five attempts. The wait between them doubles and is randomised, up to four
|
|
78
|
+
seconds before the last one — randomised so that a fleet of your processes that all saw the same
|
|
79
|
+
outage does not come back at the same instant. When an answer asks for a specific wait, in whole
|
|
80
|
+
seconds, the client waits exactly that long and adds nothing of its own. Retrying is also a
|
|
81
|
+
budget: ten retries the client decides for itself, one earned back per call that succeeds. A
|
|
82
|
+
client whose calls are all failing stops resending instead of adding to the load.
|
|
83
|
+
|
|
84
|
+
**The 10-second timeout is per attempt, not per call.** A call that retries can take longer than
|
|
85
|
+
10 seconds and is not wrong for doing so. Waiting for a person to approve is not an attempt at
|
|
86
|
+
all: that is `client.wait`, which polls, and each poll is its own attempt with its own timeout.
|
|
87
|
+
|
|
88
|
+
### Putting a bound on the whole call
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
await client.propose(proposal, { deadlineMs: 5_000 });
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
With a deadline the client never starts a wait that would end after it. It throws
|
|
95
|
+
`DeadlineExceeded` instead, and that error tells you what was actually failing — `lastError` and
|
|
96
|
+
`status` — so you can tell "the gateway was down" from "my bound was too short". Without a
|
|
97
|
+
deadline there is no such check, and a server that asks for a long wait gets it.
|
|
98
|
+
|
|
99
|
+
### What the client has been doing
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
const { retries_directed, retries_computed, retry_bucket_level } = client.retryCounters;
|
|
61
103
|
```
|
|
62
104
|
|
|
63
|
-
|
|
105
|
+
How many resends the server asked for, how many the client decided on itself, and how much of the
|
|
106
|
+
retry budget is left (10 when full). The two counts run for the life of the client; read them
|
|
107
|
+
before and after a call for that call's own numbers. They are worth a gauge in your own metrics:
|
|
108
|
+
a `retries_computed` that climbs while `retry_bucket_level` sits at zero is the shape of an
|
|
109
|
+
outage you are riding out rather than one you are told about.
|
|
110
|
+
|
|
111
|
+
## Documentation
|
|
112
|
+
|
|
113
|
+
- Quickstart: https://ziffer.io/docs/quickstart
|
|
114
|
+
- Integrating the SDK: https://ziffer.io/docs/developers/sdk
|
|
115
|
+
- Sandbox tenants: https://ziffer.io/docs/developers/sandbox
|
|
116
|
+
- Every refusal: https://ziffer.io/docs/refusals
|
|
117
|
+
- Policy by example: https://ziffer.io/docs/policy/by-example
|
|
118
|
+
- Glossary: https://ziffer.io/docs/glossary
|
|
119
|
+
|
|
120
|
+
## Support
|
|
121
|
+
|
|
122
|
+
Write to hello@ziffer.io. Your API key, your trust anchor file and your suite floor come from us.
|
|
123
|
+
So does an answer about a refusal you cannot explain.
|
|
124
|
+
|
|
125
|
+
## License
|
|
64
126
|
|
|
65
|
-
Proprietary. Copyright (c) 2026 code75 SASU, Paris, France. ZIFFER is a registered trademark of
|
|
66
|
-
SASU. This package is
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
their own licences.
|
|
127
|
+
Proprietary. Copyright (c) 2026 code75 SASU, Paris, France. ZIFFER is a registered trademark of
|
|
128
|
+
code75 SASU. This package is not open source. Its use is governed by your agreement with code75
|
|
129
|
+
and by `LICENSE` beside this file. The open-source components it redistributes are listed in
|
|
130
|
+
`THIRD-PARTY-NOTICES`, under their own licences.
|
package/dist/client.d.ts
CHANGED
|
@@ -43,8 +43,31 @@
|
|
|
43
43
|
* - A poll that outlives its deadline throws {@link WaitTimeout}. A timeout
|
|
44
44
|
* is "no answer yet", never "the answer was no" (ingress-low's rule for
|
|
45
45
|
* its own dependency, one layer out).
|
|
46
|
+
* - A call that was still retrying when the caller's deadline arrived throws
|
|
47
|
+
* {@link DeadlineExceeded}, carrying the last failure's name and status.
|
|
48
|
+
* Inside {@link ZifferClient.wait} it is caught and re-thrown as
|
|
49
|
+
* {@link WaitTimeout} with that `DeadlineExceeded` as its `cause`: `wait`
|
|
50
|
+
* has ONE name for "no answer yet" and a caller already catches it.
|
|
51
|
+
*
|
|
52
|
+
* # Timeouts and retries
|
|
53
|
+
*
|
|
54
|
+
* Every round trip carries an abort signal set to {@link REQUEST_TIMEOUT_MS}.
|
|
55
|
+
* **That timeout covers ONE ROUND TRIP** — the send and the reading of the
|
|
56
|
+
* answer's body — and is not a budget for the call: a call that retries may
|
|
57
|
+
* take several times as long and is not wrong for doing so. Waiting for a
|
|
58
|
+
* human to approve is not a round trip at all; that is {@link
|
|
59
|
+
* ZifferClient.wait}, which polls, and each poll is its own round trip under
|
|
60
|
+
* its own timeout.
|
|
61
|
+
*
|
|
62
|
+
* There is ONE request path and the retry loop is inside it, so `propose`,
|
|
63
|
+
* `decision` and every poll of `wait` retry under exactly the same rules
|
|
64
|
+
* (`retry.ts` holds them, `retry.test.ts` replays the corpus both SDKs share).
|
|
65
|
+
* The bytes are serialised once, above the loop, and the same bytes are
|
|
66
|
+
* resent: the gateway keys a pending hold on the hash of what it received, so
|
|
67
|
+
* identical bytes land on the same hold instead of opening a second one.
|
|
46
68
|
*/
|
|
47
69
|
import type { wire } from '@ziffer-io/types';
|
|
70
|
+
import { type RetryCounters } from './retry.js';
|
|
48
71
|
/**
|
|
49
72
|
* The header every SUCCESSFUL answer carries (ACP-256 §5): the instant the key
|
|
50
73
|
* that authenticated the call ends, RFC 3339 in UTC. Lowercase because that is
|
|
@@ -92,11 +115,50 @@ export interface WireProposalPayload {
|
|
|
92
115
|
}
|
|
93
116
|
/** The two §1 states. `decided` means the relay recorded a verdict; it does
|
|
94
117
|
* NOT mean a receipt exists — `receipt` is present iff one does. */
|
|
118
|
+
/**
|
|
119
|
+
* ACP-392. One short message about something our documentation does not
|
|
120
|
+
* answer, and the ONLY thing this client sends that is not a Proposal.
|
|
121
|
+
*
|
|
122
|
+
* The three members are the route's CLOSED set: a fourth is refused by the
|
|
123
|
+
* gateway rather than dropped, so this type cannot quietly grow a field that
|
|
124
|
+
* carries a developer's environment off their machine. `context` is optional
|
|
125
|
+
* and absent means absent.
|
|
126
|
+
*/
|
|
127
|
+
export interface Feedback {
|
|
128
|
+
/** Which tool could not answer — free text, because the tools naming
|
|
129
|
+
* themselves live on the other side of this wire. */
|
|
130
|
+
readonly tool: string;
|
|
131
|
+
/** What was asked. */
|
|
132
|
+
readonly question: string;
|
|
133
|
+
/** Anything else worth knowing. Never a credential: it is stored as written
|
|
134
|
+
* and read by an operator. */
|
|
135
|
+
readonly context?: string;
|
|
136
|
+
}
|
|
137
|
+
/** What `POST /v1/feedback` answers. `tenant` is the one thing the caller did
|
|
138
|
+
* not send — the key carried it. */
|
|
139
|
+
export interface FeedbackStored {
|
|
140
|
+
readonly stored: true;
|
|
141
|
+
readonly tenant: string;
|
|
142
|
+
}
|
|
95
143
|
export type DecisionStatus = 'pending' | 'decided';
|
|
96
144
|
/**
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
145
|
+
* RF-4's refusal category (ACP-402): the one thing a caller's code needs to
|
|
146
|
+
* decide whether to try again, served INSTEAD of the clause on every
|
|
147
|
+
* tenant-facing decision route. A closed set of four, carried verbatim (RF-2):
|
|
148
|
+
* never mapped from anything, never invented, and a value outside the set is
|
|
149
|
+
* refused as a malformed answer rather than repaired into one of these.
|
|
150
|
+
*
|
|
151
|
+
* - `PolicyRefused` — a rule refused the action: never retry.
|
|
152
|
+
* - `PolicyBasisMoved` — the policy basis moved: retry once after activation.
|
|
153
|
+
* - `RateBounded` — retry later.
|
|
154
|
+
* - `ProposalMalformed` — fix the submission.
|
|
155
|
+
*/
|
|
156
|
+
export type RefusalCategory = 'PolicyRefused' | 'PolicyBasisMoved' | 'RateBounded' | 'ProposalMalformed';
|
|
157
|
+
/**
|
|
158
|
+
* What both §1 routes answer. A refusal exposes `{outcome, refusal_category}`
|
|
159
|
+
* — never the clause, since ACP-402 (RF-4: the caller may be the agent being
|
|
160
|
+
* contained; the clause is in the console) — and an absent member is ABSENT,
|
|
161
|
+
* never `null`: one object, one encoding.
|
|
100
162
|
*/
|
|
101
163
|
export interface Decision {
|
|
102
164
|
/** The locator (T). Compare nothing against it; fetch with it. */
|
|
@@ -105,7 +167,11 @@ export interface Decision {
|
|
|
105
167
|
/** The engine's outcome type — a fourth spelling of ALLOW / ATTEST / DENY
|
|
106
168
|
* would be a fourth definition of the object every component agrees on. */
|
|
107
169
|
readonly outcome?: wire.DecisionOutcome;
|
|
108
|
-
|
|
170
|
+
/** RF-4's category, for a DENY or a refused release. Absent otherwise. */
|
|
171
|
+
readonly refusal_category?: RefusalCategory;
|
|
172
|
+
/** DR-15: the action is HELD until this instant (RFC 3339 UTC) and no
|
|
173
|
+
* receipt is readable yet. Absent once it releases or is refused. */
|
|
174
|
+
readonly held_until?: string;
|
|
109
175
|
/**
|
|
110
176
|
* The signed receipt, verbatim from the one route that serves receipts
|
|
111
177
|
* (GET). Deliberately `unknown`: its ONLY consumer is `verifyReceipt`,
|
|
@@ -114,6 +180,87 @@ export interface Decision {
|
|
|
114
180
|
*/
|
|
115
181
|
readonly receipt?: unknown;
|
|
116
182
|
}
|
|
183
|
+
/** What a list item's `receipt` says. TWO WORDS, never the document: the
|
|
184
|
+
* receipt is served by exactly one route (`GET /v1/decisions/{id}`), and a
|
|
185
|
+
* boolean would read as "the receipt says no" — which is the sentence a DENY
|
|
186
|
+
* makes true and this member does not mean. */
|
|
187
|
+
export type ReceiptPresence = 'attached' | 'absent';
|
|
188
|
+
/**
|
|
189
|
+
* One row of {@link ZifferClient.list} (ACP-356).
|
|
190
|
+
*
|
|
191
|
+
* IT CARRIES NO CLAUSE AND NO RECEIPT DOCUMENT, and neither is an oversight.
|
|
192
|
+
* The list answers WHAT was decided and never WHY: the caller may be a
|
|
193
|
+
* compromised agent, and a sweepable list of clauses is an oracle over the
|
|
194
|
+
* customer's signed rules. The reason a request was refused is in the audit
|
|
195
|
+
* trail and, for a signed-in human, in the console — not at the API.
|
|
196
|
+
*/
|
|
197
|
+
export interface DecisionListItem {
|
|
198
|
+
readonly decision_id: string;
|
|
199
|
+
readonly status: DecisionStatus;
|
|
200
|
+
readonly receipt: ReceiptPresence;
|
|
201
|
+
/** When the gateway recorded the submission. Strict RFC 3339 UTC, to the
|
|
202
|
+
* second — the same grammar `since` takes. */
|
|
203
|
+
readonly created_at: string;
|
|
204
|
+
/** The Policy Engine is still holding this Proposal for a quorum. NOT the
|
|
205
|
+
* same as `outcome === 'ATTEST'`, which only says one was asked for. */
|
|
206
|
+
readonly waiting: boolean;
|
|
207
|
+
/** Absent while pending. */
|
|
208
|
+
readonly outcome?: wire.DecisionOutcome;
|
|
209
|
+
/** When the hold ends (AT-5). Present only while `waiting`. */
|
|
210
|
+
readonly expires_at?: string;
|
|
211
|
+
/** RF-4's category — the category, never the clause. */
|
|
212
|
+
readonly refusal_category?: RefusalCategory;
|
|
213
|
+
/** DR-15: held until this instant, no receipt yet. */
|
|
214
|
+
readonly held_until?: string;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* One page of {@link ZifferClient.list}, and where the next one starts.
|
|
218
|
+
*
|
|
219
|
+
* `next_cursor` is `null` at the end — never `undefined`, because both values
|
|
220
|
+
* are ANSWERS and a caller must tell "this is the end" from "the server did
|
|
221
|
+
* not say". Hand it back UNCHANGED: it is opaque, and a cursor built by a
|
|
222
|
+
* caller is a second implementation of the gateway's ordering.
|
|
223
|
+
*/
|
|
224
|
+
export interface DecisionPage {
|
|
225
|
+
readonly items: readonly DecisionListItem[];
|
|
226
|
+
readonly next_cursor: string | null;
|
|
227
|
+
}
|
|
228
|
+
/** Per-call options for {@link ZifferClient.list}. */
|
|
229
|
+
export interface ListOptions extends RequestOptions {
|
|
230
|
+
/** Strict RFC 3339 UTC, exactly `2026-09-21T18:00:00Z`. Default: 24 hours
|
|
231
|
+
* ago. May not reach further back than 730 days. */
|
|
232
|
+
readonly since?: string;
|
|
233
|
+
/** 1..200. Default 50. */
|
|
234
|
+
readonly limit?: number;
|
|
235
|
+
/** `next_cursor` from a previous page, unchanged. */
|
|
236
|
+
readonly cursor?: string;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* What `GET /v1/whoami` answers (ACP-391): the customer this key is bound to,
|
|
240
|
+
* and when the key stops being accepted.
|
|
241
|
+
*
|
|
242
|
+
* TWO MEMBERS AND NO THIRD. A wider "tenant info" was considered and dropped:
|
|
243
|
+
* the live policy epoch, the bundle's expiry, the attester names and the three
|
|
244
|
+
* windows are the POLICY AUTHOR's business, nothing in this client needs them
|
|
245
|
+
* to call {@link ZifferClient.propose}, and a refusal already names its own
|
|
246
|
+
* clause. What is genuinely unanswerable from this side of the wire is which
|
|
247
|
+
* customer the key in an environment variable belongs to.
|
|
248
|
+
*
|
|
249
|
+
* `key_expires_at` is the same value {@link API_KEY_EXPIRES_HEADER} carries on
|
|
250
|
+
* every 2xx — one date written twice by the gateway from one source, so a
|
|
251
|
+
* caller that ASKED does not have to reach for a header to be told.
|
|
252
|
+
*/
|
|
253
|
+
export interface Identity {
|
|
254
|
+
/** The tenant the API key resolves to, as the key store holds it. Never a
|
|
255
|
+
* value this client chose: there is no parameter for it and a `tenant_id`
|
|
256
|
+
* in a proposal that disagrees is refused (`TenantMismatch`), not rewritten. */
|
|
257
|
+
readonly tenant_id: string;
|
|
258
|
+
/** Strict RFC 3339 UTC, rendered by the store. A string and not a parsed
|
|
259
|
+
* instant on purpose — the comparison that decides liveness is the store's,
|
|
260
|
+
* and a second place that parsed this would be a second opinion about when a
|
|
261
|
+
* key ends. */
|
|
262
|
+
readonly key_expires_at: string;
|
|
263
|
+
}
|
|
117
264
|
/** §1 names, exported so callers and tests never retype the strings. The set
|
|
118
265
|
* is OPEN — the gateway may name more; {@link ApiRefusal} carries any name
|
|
119
266
|
* verbatim and this list closes nothing. */
|
|
@@ -122,6 +269,17 @@ export declare const ERROR_TENANT_MISMATCH = "TenantMismatch";
|
|
|
122
269
|
export declare const ERROR_PROPOSAL_MALFORMED = "ProposalMalformed";
|
|
123
270
|
export declare const ERROR_DECISION_UNKNOWN = "DecisionUnknown";
|
|
124
271
|
export declare const ERROR_ADMISSION_UNAVAILABLE = "AdmissionUnavailable";
|
|
272
|
+
/** ACP-356. ONE name for every way the list's query string is not a legal
|
|
273
|
+
* one — which parameter, and why, is deliberately not said. */
|
|
274
|
+
export declare const ERROR_LIST_QUERY_MALFORMED = "ListQueryMalformed";
|
|
275
|
+
/** ACP-392. `POST /v1/feedback`'s four, in the gateway's own spelling
|
|
276
|
+
* (`services/gateway/src/gateway.rs::error_name`). They are exported for the
|
|
277
|
+
* same reason the five above are: a caller branching on a refusal should
|
|
278
|
+
* import the string rather than type it. */
|
|
279
|
+
export declare const ERROR_FEEDBACK_MALFORMED = "FeedbackMalformed";
|
|
280
|
+
export declare const ERROR_FEEDBACK_TOO_LARGE = "FeedbackTooLarge";
|
|
281
|
+
export declare const ERROR_FEEDBACK_RATE_LIMITED = "FeedbackRateLimited";
|
|
282
|
+
export declare const ERROR_FEEDBACK_UNAVAILABLE = "FeedbackUnavailable";
|
|
125
283
|
/**
|
|
126
284
|
* The gateway answered, and the answer was a named refusal. `error` is the
|
|
127
285
|
* gateway's name, verbatim — the machine-readable half, as `Refusal.clause`
|
|
@@ -145,10 +303,18 @@ export declare class ResponseMalformed extends Error {
|
|
|
145
303
|
constructor(message: string);
|
|
146
304
|
}
|
|
147
305
|
/** The deadline passed with the decision still `pending`. Not a refusal and
|
|
148
|
-
* not an answer — the decision may still decide; the id remains fetchable.
|
|
306
|
+
* not an answer — the decision may still decide; the id remains fetchable.
|
|
307
|
+
*
|
|
308
|
+
* `options.cause` is how the reason survives the rename. A poll that ran out
|
|
309
|
+
* of retry budget produced a {@link DeadlineExceeded} naming the last failure
|
|
310
|
+
* and its status; `wait` reports the event under the name its caller catches
|
|
311
|
+
* and hands the original through as `cause`, so nothing the caller could have
|
|
312
|
+
* learned from the poll is thrown away to keep one name at the surface. A
|
|
313
|
+
* rename that dropped the reason would make "no answer yet" indistinguishable
|
|
314
|
+
* from "the gateway was shedding for thirty seconds". */
|
|
149
315
|
export declare class WaitTimeout extends Error {
|
|
150
316
|
readonly decisionId: string;
|
|
151
|
-
constructor(decisionId: string, timeoutMs: number);
|
|
317
|
+
constructor(decisionId: string, timeoutMs: number, options?: ErrorOptions);
|
|
152
318
|
}
|
|
153
319
|
/** Options for {@link ZifferClient.wait}. */
|
|
154
320
|
export interface WaitOptions {
|
|
@@ -157,6 +323,55 @@ export interface WaitOptions {
|
|
|
157
323
|
/** Delay between polls. Default 500. */
|
|
158
324
|
readonly intervalMs?: number;
|
|
159
325
|
}
|
|
326
|
+
/** What this client needs of `fetch`, and no more. Narrow on purpose: it is
|
|
327
|
+
* the seam the retry corpus is replayed through, and a seam typed as the whole
|
|
328
|
+
* of `fetch` would invite a test to script something this client never sends. */
|
|
329
|
+
export interface FetchInit {
|
|
330
|
+
readonly method: string;
|
|
331
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
332
|
+
readonly body?: string;
|
|
333
|
+
readonly signal: AbortSignal;
|
|
334
|
+
}
|
|
335
|
+
/** The injectable `fetch`. The default is the global one. */
|
|
336
|
+
export type FetchLike = (url: string, init: FetchInit) => Promise<Response>;
|
|
337
|
+
/** Per-call options for {@link ZifferClient.propose} and
|
|
338
|
+
* {@link ZifferClient.decision}. */
|
|
339
|
+
export interface RequestOptions {
|
|
340
|
+
/**
|
|
341
|
+
* R5. How long from NOW this call may keep retrying, in milliseconds.
|
|
342
|
+
*
|
|
343
|
+
* Before every sleep, directed or computed, the client asks whether the
|
|
344
|
+
* sleep would end after this instant; if it would, it does not sleep and
|
|
345
|
+
* throws {@link DeadlineExceeded} carrying the last failure. Absent means
|
|
346
|
+
* no such check at all — the attempt cap and the token bucket are then the
|
|
347
|
+
* only bounds, and a server that asks for an hour gets an hour.
|
|
348
|
+
*/
|
|
349
|
+
readonly deadlineMs?: number;
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Construction-time options. Every one of them has a real default; the clock,
|
|
353
|
+
* the sleep, the draw and `fetch` exist as options so the shared retry corpus
|
|
354
|
+
* can be replayed deterministically, and for no other reason — a client built
|
|
355
|
+
* with none of them is the client a customer runs.
|
|
356
|
+
*/
|
|
357
|
+
export interface ZifferClientOptions {
|
|
358
|
+
/** R8. The abort timeout on ONE round trip. Default
|
|
359
|
+
* {@link REQUEST_TIMEOUT_MS}. */
|
|
360
|
+
readonly timeoutMs?: number;
|
|
361
|
+
/** Default: the global `fetch`. */
|
|
362
|
+
readonly fetch?: FetchLike;
|
|
363
|
+
/** Default: `Date.now`. */
|
|
364
|
+
readonly now?: () => number;
|
|
365
|
+
/** Default: `setTimeout`. */
|
|
366
|
+
readonly sleep?: (ms: number) => Promise<void>;
|
|
367
|
+
/** R2's `u`, in [0,1). Default: `Math.random`. */
|
|
368
|
+
readonly random?: () => number;
|
|
369
|
+
/** R6's bucket at construction. Default: full
|
|
370
|
+
* ({@link RETRY_BUCKET_CAPACITY}). A test seam — a deployment that wanted a
|
|
371
|
+
* smaller retry budget would be asking for a different rule, not a
|
|
372
|
+
* different starting level. */
|
|
373
|
+
readonly retryBucketInitial?: number;
|
|
374
|
+
}
|
|
160
375
|
/**
|
|
161
376
|
* The client. One instance per (gateway, key); the KEY determines the tenant
|
|
162
377
|
* server-side (§1), so there is nothing tenant-shaped to configure here —
|
|
@@ -165,33 +380,121 @@ export interface WaitOptions {
|
|
|
165
380
|
export declare class ZifferClient {
|
|
166
381
|
private readonly baseUrl;
|
|
167
382
|
private readonly apiKey;
|
|
168
|
-
|
|
383
|
+
private readonly timeoutMs;
|
|
384
|
+
private readonly fetchImpl;
|
|
385
|
+
private readonly now;
|
|
386
|
+
private readonly sleep;
|
|
387
|
+
private readonly random;
|
|
388
|
+
/** R6/R9: per CLIENT INSTANCE, as the rules say. Two clients do not share a
|
|
389
|
+
* retry budget, and one client's `propose` and `wait` do. */
|
|
390
|
+
private readonly retry;
|
|
391
|
+
constructor(baseUrl: string, apiKey: string, options?: ZifferClientOptions);
|
|
392
|
+
/**
|
|
393
|
+
* R9. The two retry counters and the bucket gauge, as a plain object taken
|
|
394
|
+
* at this instant. Cumulative over the client's life — a caller that wants
|
|
395
|
+
* a delta over one call reads it before and after.
|
|
396
|
+
*/
|
|
397
|
+
get retryCounters(): RetryCounters;
|
|
169
398
|
/**
|
|
170
399
|
* POST /v1/proposals. The proposal is serialised as given — the caller's
|
|
171
400
|
* values, no edits — and the answer never carries a receipt (§1): fetch it
|
|
172
401
|
* with {@link decision} once decided.
|
|
402
|
+
*
|
|
403
|
+
* `opts.deadlineMs` bounds the RETRYING, not the round trip (R5).
|
|
173
404
|
*/
|
|
174
|
-
propose(proposal: WireProposal): Promise<Decision>;
|
|
405
|
+
propose(proposal: WireProposal, opts?: RequestOptions): Promise<Decision>;
|
|
406
|
+
/**
|
|
407
|
+
* POST /v1/feedback — tell ZIFFER what our documentation did not answer
|
|
408
|
+
* (ACP-392).
|
|
409
|
+
*
|
|
410
|
+
* THE TEXT LEAVES THE MACHINE. It is stored under the tenant this API key
|
|
411
|
+
* carries and read by an operator; nothing is filtered on the way, so
|
|
412
|
+
* anything put in `question` or `context` is anything an operator will
|
|
413
|
+
* read. The gateway refuses a member this shape does not define, which is
|
|
414
|
+
* what stops the object growing a field that carries more than a sentence.
|
|
415
|
+
*
|
|
416
|
+
* # ONE round trip and NO retry, unlike every other call on this client
|
|
417
|
+
*
|
|
418
|
+
* `request` retries a 429 and honours its `Retry-After`, which is right for
|
|
419
|
+
* work: a proposal that was refused for backpressure still has to happen.
|
|
420
|
+
* This is not work. A rate-limited feedback message is a message that will
|
|
421
|
+
* not be stored, and riding out a 60-second bucket would block the coding
|
|
422
|
+
* agent that called it for a minute to deliver a sentence. So the failure
|
|
423
|
+
* is raised as it arrives — [`ApiRefusal`] with the gateway's own name —
|
|
424
|
+
* and the caller decides.
|
|
425
|
+
*/
|
|
426
|
+
feedback(message: Feedback): Promise<FeedbackStored>;
|
|
175
427
|
/**
|
|
176
428
|
* GET /v1/decisions/{id}. `receipt` is present iff a signed receipt
|
|
177
429
|
* exists; hand it to `verifyReceipt` with your OWN copy of the proposal
|
|
178
430
|
* bytes — the id proves nothing (T), the recomputed hash is the binding.
|
|
179
431
|
*/
|
|
180
|
-
decision(id: string): Promise<Decision>;
|
|
432
|
+
decision(id: string, opts?: RequestOptions): Promise<Decision>;
|
|
433
|
+
/** {@link decision}, with the deadline already resolved to an instant —
|
|
434
|
+
* which is what {@link wait} has and a caller does not. */
|
|
435
|
+
private fetchDecision;
|
|
436
|
+
/**
|
|
437
|
+
* GET /v1/whoami — which customer this key is bound to, and when it ends.
|
|
438
|
+
*
|
|
439
|
+
* The one call that asks about the CREDENTIAL rather than about a decision.
|
|
440
|
+
* It sends no body and takes no argument: there is nothing to name, because
|
|
441
|
+
* the key is the question. A dead key — expired, revoked or never minted —
|
|
442
|
+
* is one `ApiRefusal` (`ApiKeyUnknown`, 401) and the three cannot be told
|
|
443
|
+
* apart, which is deliberate at the gateway and is not this client's to
|
|
444
|
+
* undo.
|
|
445
|
+
*/
|
|
446
|
+
whoami(opts?: RequestOptions): Promise<Identity>;
|
|
447
|
+
/**
|
|
448
|
+
* GET /v1/decisions — what ZIFFER holds and decided for THIS key.
|
|
449
|
+
*
|
|
450
|
+
* Every request waiting for approval (with when its hold ends) and every
|
|
451
|
+
* decision in the window, newest first. The tenant is the API key's and
|
|
452
|
+
* cannot be named any other way: there is no parameter for it and the
|
|
453
|
+
* gateway refuses one by name.
|
|
454
|
+
*
|
|
455
|
+
* Every bad parameter is one refusal, `ListQueryMalformed` (400). The API
|
|
456
|
+
* does not say which one, for the same reason an item carries no clause.
|
|
457
|
+
*/
|
|
458
|
+
list(opts?: ListOptions): Promise<DecisionPage>;
|
|
181
459
|
/**
|
|
182
460
|
* Poll {@link decision} until `decided` or the deadline. A `WaitTimeout`
|
|
183
461
|
* is "no answer yet", never a verdict; every named refusal (404 included)
|
|
184
462
|
* propagates immediately — retrying `DecisionUnknown` would be the client
|
|
185
463
|
* deciding the server was wrong.
|
|
464
|
+
*
|
|
465
|
+
* A poll that runs out of budget surfaces as `WaitTimeout` too, and NOT as
|
|
466
|
+
* the `DeadlineExceeded` the request path raised. Both mean "the wait's own
|
|
467
|
+
* deadline arrived with the decision still pending"; which of the two a
|
|
468
|
+
* caller saw depended on whether the last poll happened to be mid-retry,
|
|
469
|
+
* which is a detail of the gateway's load and not of this API. The
|
|
470
|
+
* `DeadlineExceeded` rides along as `cause`, so the last failure's name and
|
|
471
|
+
* status are still there for whoever wants them.
|
|
186
472
|
*/
|
|
187
473
|
wait(id: string, opts?: WaitOptions): Promise<Decision>;
|
|
474
|
+
/** R5: the caller's duration becomes an instant on this client's clock,
|
|
475
|
+
* once, at the start of the call. `undefined` in, `undefined` out — no
|
|
476
|
+
* deadline means no check, not a check against infinity. */
|
|
477
|
+
private deadlineAt;
|
|
188
478
|
/**
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
479
|
+
* THE request path: one loop, R1..R6, for every call this client makes.
|
|
480
|
+
*
|
|
481
|
+
* `body` arrives already serialised and is resent unchanged (R7). The loop
|
|
482
|
+
* owns nothing about the rules themselves — `retry.ts` decides, this
|
|
483
|
+
* sleeps, sends again, or throws what the last attempt produced.
|
|
194
484
|
*/
|
|
195
485
|
private request;
|
|
486
|
+
/**
|
|
487
|
+
* ONE round trip: one request, one parse, one narrowing. 2xx returns the
|
|
488
|
+
* parsed body for the caller's guard; anything else must be §1's
|
|
489
|
+
* `{"error": name}` and yields {@link ApiRefusal} with the name verbatim. A
|
|
490
|
+
* non-JSON or unnamed error body yields {@link ResponseMalformed} — an
|
|
491
|
+
* intermediary's HTML 502 is not the gateway's answer and is never dressed
|
|
492
|
+
* up as one, though it IS retried, because 502 is retryable whoever wrote it.
|
|
493
|
+
*
|
|
494
|
+
* Nothing is thrown from here: the failure is RETURNED, because whether it
|
|
495
|
+
* becomes the caller's error is the retry loop's question and not this
|
|
496
|
+
* method's.
|
|
497
|
+
*/
|
|
498
|
+
private roundTrip;
|
|
196
499
|
}
|
|
197
500
|
//# sourceMappingURL=client.d.ts.map
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmEG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAE7C,OAAO,EAOL,KAAK,aAAa,EACnB,MAAM,YAAY,CAAC;AAIpB;;;;;;;;;;GAUG;AACH,eAAO,MAAM,sBAAsB,6BAA6B,CAAC;AAEjE,qEAAqE;AACrE,eAAO,MAAM,2BAA2B,KAAK,CAAC;AAS9C,gEAAgE;AAChE,wBAAgB,yBAAyB,IAAI,IAAI,CAEhD;AAuCD;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IAC9C,QAAQ,CAAC,cAAc,EAAE,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC;IACxD,QAAQ,CAAC,WAAW,EAAE,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;IAClD,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IAC7C,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IAC9C,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC;CACvC;AAED;mCACmC;AACnC,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC;IACrD,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC;IACpD,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC;IAClD,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;IAChD,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;CAC/C;AAED;oEACoE;AACpE;;;;;;;;GAQG;AACH,MAAM,WAAW,QAAQ;IACvB;yDACqD;IACrD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,sBAAsB;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;kCAC8B;IAC9B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;oCACoC;AACpC,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,SAAS,CAAC;AAEnD;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,eAAe,GACvB,eAAe,GACf,kBAAkB,GAClB,aAAa,GACb,mBAAmB,CAAC;AAyCxB;;;;;GAKG;AACH,MAAM,WAAW,QAAQ;IACvB,kEAAkE;IAClE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC;+EAC2E;IAC3E,QAAQ,CAAC,OAAO,CAAC,EAAE,IAAI,CAAC,eAAe,CAAC;IACxC,0EAA0E;IAC1E,QAAQ,CAAC,gBAAgB,CAAC,EAAE,eAAe,CAAC;IAC5C;yEACqE;IACrE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;;+CAG+C;AAC/C,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,QAAQ,CAAC;AAEpD;;;;;;;;GAQG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC;kDAC8C;IAC9C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B;4EACwE;IACxE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,4BAA4B;IAC5B,QAAQ,CAAC,OAAO,CAAC,EAAE,IAAI,CAAC,eAAe,CAAC;IACxC,+DAA+D;IAC/D,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,wDAAwD;IACxD,QAAQ,CAAC,gBAAgB,CAAC,EAAE,eAAe,CAAC;IAC5C,sDAAsD;IACtD,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC5C,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CACrC;AAED,sDAAsD;AACtD,MAAM,WAAW,WAAY,SAAQ,cAAc;IACjD;wDACoD;IACpD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,0BAA0B;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,qDAAqD;IACrD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,QAAQ;IACvB;;oFAEgF;IAChF,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;;;mBAGe;IACf,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC;AAID;;4CAE4C;AAC5C,eAAO,MAAM,qBAAqB,kBAAkB,CAAC;AACrD,eAAO,MAAM,qBAAqB,mBAAmB,CAAC;AACtD,eAAO,MAAM,wBAAwB,sBAAsB,CAAC;AAC5D,eAAO,MAAM,sBAAsB,oBAAoB,CAAC;AACxD,eAAO,MAAM,2BAA2B,yBAAyB,CAAC;AAClE;+DAC+D;AAC/D,eAAO,MAAM,0BAA0B,uBAAuB,CAAC;AAC/D;;;4CAG4C;AAC5C,eAAO,MAAM,wBAAwB,sBAAsB,CAAC;AAC5D,eAAO,MAAM,wBAAwB,qBAAqB,CAAC;AAC3D,eAAO,MAAM,2BAA2B,wBAAwB,CAAC;AACjE,eAAO,MAAM,0BAA0B,wBAAwB,CAAC;AAEhE;;;;;GAKG;AACH,qBAAa,UAAW,SAAQ,KAAK;IACnC,kEAAkE;IAClE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,8CAA8C;IAC9C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBAEZ,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;CAM1C;AAED;;;;;GAKG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;gBAC9B,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;;;;;yDASyD;AACzD,qBAAa,WAAY,SAAQ,KAAK;IACpC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;gBAEhB,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY;CAK1E;AAwLD,6CAA6C;AAC7C,MAAM,WAAW,WAAW;IAC1B,2EAA2E;IAC3E,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,wCAAwC;IACxC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;iFAEiF;AACjF,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACnD,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;CAC9B;AAED,6DAA6D;AAC7D,MAAM,MAAM,SAAS,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AAE5E;oCACoC;AACpC,MAAM,WAAW,cAAc;IAC7B;;;;;;;;OAQG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IAClC;qCACiC;IACjC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,mCAAmC;IACnC,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,CAAC;IAC3B,2BAA2B;IAC3B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IAC5B,6BAA6B;IAC7B,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,kDAAkD;IAClD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,MAAM,CAAC;IAC/B;;;mCAG+B;IAC/B,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;CACtC;AAuCD;;;;GAIG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;IACtC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgC;IACtD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;IACtC;iEAC6D;IAC7D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAc;gBAExB,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB;IAwB1E;;;;OAIG;IACH,IAAI,aAAa,IAAI,aAAa,CAEjC;IAED;;;;;;OAMG;IACG,OAAO,CAAC,QAAQ,EAAE,YAAY,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,QAAQ,CAAC;IAe/E;;;;;;;;;;;;;;;;;;;OAmBG;IACG,QAAQ,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC;IAyB1D;;;;OAIG;IACG,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,QAAQ,CAAC;IAOpE;+DAC2D;YAC7C,aAAa;IAsB3B;;;;;;;;;OASG;IACG,MAAM,CAAC,IAAI,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,QAAQ,CAAC;IAUtD;;;;;;;;;;OAUG;IACG,IAAI,CAAC,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;IAqBrD;;;;;;;;;;;;;OAaG;IACG,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC;IAmC7D;;gEAE4D;IAC5D,OAAO,CAAC,UAAU;IAUlB;;;;;;OAMG;YACW,OAAO;IAgCrB;;;;;;;;;;;OAWG;YACW,SAAS;CA2FxB"}
|