@mandateops/audit 0.4.2 → 0.5.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/dist/index.d.ts +18 -6
- package/dist/index.js +34 -8
- package/dist/reading.d.ts +17 -0
- package/dist/reading.js +16 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type { CapacityReading, HistoryReading, ReadingProfile } from "./reading.js";
|
|
1
|
+
import type { CapacityReading, HistoryReading, ReadingProfile, StatusReading } from "./reading.js";
|
|
2
2
|
import type { ProviderWire, StagedWire } from "./providers/index.js";
|
|
3
3
|
export * from "./providers/index.js";
|
|
4
|
-
export { parseCapacityReading, parseHistoryReading, ReadingError, requireHistoryFits, } from "./reading.js";
|
|
5
|
-
export type { AttemptEnd, AttemptResult, CapacityReading, Flow, HistoryEvent, HistoryReading, Instruction, MinorUnits, Payout, PayoutState, ReadingProfile, HistoryConsistency, CapacityConsistency, } from "./reading.js";
|
|
4
|
+
export { parseCapacityReading, parseHistoryReading, parseStatusReading, ReadingError, requireHistoryFits, } from "./reading.js";
|
|
5
|
+
export type { AttemptEnd, AttemptResult, CapacityReading, Flow, HistoryEvent, HistoryReading, Instruction, MinorUnits, Payout, PayoutState, ReadingProfile, StatusReading, HistoryConsistency, CapacityConsistency, } from "./reading.js";
|
|
6
6
|
/**
|
|
7
7
|
* The largest amount the stand-in holds, the audit core's `u128` maximum.
|
|
8
8
|
* `fixtures/wire/vectors.json` pins it for every reader.
|
|
@@ -20,11 +20,23 @@ export type ReadContext = Readonly<{
|
|
|
20
20
|
viewerCredential: string;
|
|
21
21
|
providerPayment: string;
|
|
22
22
|
}>;
|
|
23
|
-
/**
|
|
24
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Reads a backend that reserves funds: its lifecycle and capacity, in the
|
|
25
|
+
* shape of `contracts/history-reading.md`.
|
|
26
|
+
*/
|
|
27
|
+
export type ReadingAdapter = Readonly<{
|
|
25
28
|
history: (context: ReadContext) => Promise<HistoryReading>;
|
|
26
29
|
capacity: (context: ReadContext) => Promise<CapacityReading>;
|
|
27
30
|
}>;
|
|
31
|
+
/**
|
|
32
|
+
* Reads the outcome a backend without reservations reports for the payout,
|
|
33
|
+
* judged against the provider's records under `payout-effects-only`.
|
|
34
|
+
*/
|
|
35
|
+
export type StatusAdapter = Readonly<{
|
|
36
|
+
status: (context: ReadContext) => Promise<StatusReading>;
|
|
37
|
+
}>;
|
|
38
|
+
/** Reads the backend: lifecycle and capacity, or only its reported status. */
|
|
39
|
+
export type Adapter = ReadingAdapter | StatusAdapter;
|
|
28
40
|
export declare const defineAdapter: (adapter: Adapter) => Adapter;
|
|
29
41
|
/** The provider identity every effect must carry. */
|
|
30
42
|
export type ProviderTerms = Readonly<{
|
|
@@ -212,7 +224,7 @@ type Credentials = Readonly<Record<ServiceRole, string>>;
|
|
|
212
224
|
* The session protocol this SDK speaks. The host announces its own when
|
|
213
225
|
* ready; the two must match, as they do for an SDK shipped with its CLI.
|
|
214
226
|
*/
|
|
215
|
-
export declare const SESSION_PROTOCOL =
|
|
227
|
+
export declare const SESSION_PROTOCOL = 4;
|
|
216
228
|
/** Why a host of another protocol is refused, and how to fix it. */
|
|
217
229
|
export declare function protocolMismatch(announced: number | undefined): string;
|
|
218
230
|
/** A sealed result, checked before anything reads it. */
|
package/dist/index.js
CHANGED
|
@@ -12,10 +12,10 @@ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
|
12
12
|
import { tmpdir } from "node:os";
|
|
13
13
|
import { dirname, join, resolve } from "node:path";
|
|
14
14
|
import { setTimeout as sleep } from "node:timers/promises";
|
|
15
|
-
import { parseCapacityReading, parseHistoryReading, requireHistoryFits, } from "./reading.js";
|
|
15
|
+
import { parseCapacityReading, parseHistoryReading, parseStatusReading, requireHistoryFits, } from "./reading.js";
|
|
16
16
|
// Every provider format and its types, from the one list that names them.
|
|
17
17
|
export * from "./providers/index.js";
|
|
18
|
-
export { parseCapacityReading, parseHistoryReading, ReadingError, requireHistoryFits, } from "./reading.js";
|
|
18
|
+
export { parseCapacityReading, parseHistoryReading, parseStatusReading, ReadingError, requireHistoryFits, } from "./reading.js";
|
|
19
19
|
const MINOR_UNITS = /^[1-9][0-9]*$/;
|
|
20
20
|
/**
|
|
21
21
|
* The largest amount the stand-in holds, the audit core's `u128` maximum.
|
|
@@ -76,7 +76,7 @@ const isRecord = (value) => typeof value === "object" && value !== null && !Arra
|
|
|
76
76
|
* The session protocol this SDK speaks. The host announces its own when
|
|
77
77
|
* ready; the two must match, as they do for an SDK shipped with its CLI.
|
|
78
78
|
*/
|
|
79
|
-
export const SESSION_PROTOCOL =
|
|
79
|
+
export const SESSION_PROTOCOL = 4;
|
|
80
80
|
/** Why a host of another protocol is refused, and how to fix it. */
|
|
81
81
|
export function protocolMismatch(announced) {
|
|
82
82
|
const host = announced === undefined
|
|
@@ -253,8 +253,15 @@ const readingProfile = (config) => config.financialProfile === "payout-effects-o
|
|
|
253
253
|
*/
|
|
254
254
|
export function requireRunnable(config) {
|
|
255
255
|
const profile = readingProfile(config);
|
|
256
|
-
if (profile === undefined &&
|
|
257
|
-
|
|
256
|
+
if (profile === undefined &&
|
|
257
|
+
config.adapter &&
|
|
258
|
+
!("status" in config.adapter)) {
|
|
259
|
+
throw new AuditUsageError("payout-effects-only reads no lifecycle or capacity: give it a status " +
|
|
260
|
+
"adapter (contracts/history-reading.md), or none");
|
|
261
|
+
}
|
|
262
|
+
if (profile !== undefined && config.adapter && "status" in config.adapter) {
|
|
263
|
+
throw new AuditUsageError(`${profile} reads lifecycle and capacity, not a status: a status ` +
|
|
264
|
+
"adapter is for payout-effects-only");
|
|
258
265
|
}
|
|
259
266
|
if (profile !== undefined && !config.adapter) {
|
|
260
267
|
throw new AuditUsageError(`${profile} needs an adapter with history and capacity readings ` +
|
|
@@ -391,10 +398,12 @@ async function session(config, body, channel, backend, credentials) {
|
|
|
391
398
|
const context = () => providerPayment === undefined
|
|
392
399
|
? undefined
|
|
393
400
|
: { backend, viewerCredential, providerPayment };
|
|
401
|
+
const readings = config.adapter && "history" in config.adapter ? config.adapter : undefined;
|
|
402
|
+
const statusAdapter = config.adapter && "status" in config.adapter ? config.adapter : undefined;
|
|
394
403
|
const readHistory = async (slot) => {
|
|
395
404
|
const read = context();
|
|
396
|
-
if (
|
|
397
|
-
const { history } =
|
|
405
|
+
if (readings && profile && read && authorised) {
|
|
406
|
+
const { history } = readings;
|
|
398
407
|
const reference = authorised.expected.reference;
|
|
399
408
|
await reading(slot, async () => {
|
|
400
409
|
const taken = parseHistoryReading(await history(read));
|
|
@@ -405,11 +414,27 @@ async function session(config, body, channel, backend, credentials) {
|
|
|
405
414
|
};
|
|
406
415
|
const readCapacity = async (slot) => {
|
|
407
416
|
const read = context();
|
|
408
|
-
const capacity =
|
|
417
|
+
const capacity = readings?.capacity;
|
|
409
418
|
if (capacity && read) {
|
|
410
419
|
await reading(slot, async () => parseCapacityReading(await capacity(read)));
|
|
411
420
|
}
|
|
412
421
|
};
|
|
422
|
+
/**
|
|
423
|
+
* The backend's reported outcome, after the provider's records. Without a
|
|
424
|
+
* binding there is nothing to read by, which is reported, never skipped.
|
|
425
|
+
*/
|
|
426
|
+
const readStatus = async () => {
|
|
427
|
+
if (!statusAdapter || !authorised)
|
|
428
|
+
return;
|
|
429
|
+
const reference = authorised.expected.reference;
|
|
430
|
+
const read = context();
|
|
431
|
+
await reading("capture_status", async () => {
|
|
432
|
+
if (!read) {
|
|
433
|
+
throw new AuditUsageError("a status adapter reads by the provider payment: call bindProviderPayment");
|
|
434
|
+
}
|
|
435
|
+
return parseStatusReading(await statusAdapter.status(read), reference);
|
|
436
|
+
});
|
|
437
|
+
};
|
|
413
438
|
const requireBound = (doing) => {
|
|
414
439
|
if (profile && (authorised || competing) && providerPayment === undefined) {
|
|
415
440
|
throw new AuditUsageError(`${doing} under ${profile} needs bindProviderPayment first: adapters read by that identifier`);
|
|
@@ -583,6 +608,7 @@ async function session(config, body, channel, backend, credentials) {
|
|
|
583
608
|
await readCapacity("capture_capacity");
|
|
584
609
|
await channel.send({ type: "read_effects" });
|
|
585
610
|
await readHistory("recapture_history");
|
|
611
|
+
await readStatus();
|
|
586
612
|
const result = {
|
|
587
613
|
...parseResult(await channel.send({ type: "seal" })),
|
|
588
614
|
reading_problems: problems,
|
package/dist/reading.d.ts
CHANGED
|
@@ -83,6 +83,16 @@ export type CapacityReading = Readonly<{
|
|
|
83
83
|
committed: MinorUnits;
|
|
84
84
|
consumed: MinorUnits;
|
|
85
85
|
}>;
|
|
86
|
+
/**
|
|
87
|
+
* The outcome the backend itself reports for one payout, taken once after
|
|
88
|
+
* the provider's records under `payout-effects-only`.
|
|
89
|
+
*/
|
|
90
|
+
export type StatusReading = Readonly<{
|
|
91
|
+
source: "adapter";
|
|
92
|
+
/** The reference given to `expectPayment`. */
|
|
93
|
+
payment: string;
|
|
94
|
+
state: PayoutState;
|
|
95
|
+
}>;
|
|
86
96
|
/** The profiles an adapter reading is taken under. */
|
|
87
97
|
export type ReadingProfile = "payout-effects" | "payout-commitment";
|
|
88
98
|
/**
|
|
@@ -101,6 +111,13 @@ export declare class ReadingError extends TypeError {
|
|
|
101
111
|
* @throws {ReadingError} naming the first field that cannot be interpreted.
|
|
102
112
|
*/
|
|
103
113
|
export declare function parseHistoryReading(value: unknown): HistoryReading;
|
|
114
|
+
/**
|
|
115
|
+
* Check an untrusted status reading against the contract, and that it names
|
|
116
|
+
* the payout the test expects.
|
|
117
|
+
*
|
|
118
|
+
* @throws {ReadingError} naming the first field that cannot be interpreted.
|
|
119
|
+
*/
|
|
120
|
+
export declare function parseStatusReading(value: unknown, reference: string): StatusReading;
|
|
104
121
|
/**
|
|
105
122
|
* Check an untrusted capacity reading against the contract.
|
|
106
123
|
*
|
package/dist/reading.js
CHANGED
|
@@ -231,6 +231,22 @@ export function parseHistoryReading(value) {
|
|
|
231
231
|
});
|
|
232
232
|
return value;
|
|
233
233
|
}
|
|
234
|
+
/**
|
|
235
|
+
* Check an untrusted status reading against the contract, and that it names
|
|
236
|
+
* the payout the test expects.
|
|
237
|
+
*
|
|
238
|
+
* @throws {ReadingError} naming the first field that cannot be interpreted.
|
|
239
|
+
*/
|
|
240
|
+
export function parseStatusReading(value, reference) {
|
|
241
|
+
const found = members(value, "", ["source", "payment", "state"]);
|
|
242
|
+
requireExactly("adapter", found["source"], "source");
|
|
243
|
+
requireText(found["payment"], "payment");
|
|
244
|
+
if (found["payment"] !== reference) {
|
|
245
|
+
refuse("payment", "the reference given to expectPayment", found["payment"]);
|
|
246
|
+
}
|
|
247
|
+
requireOneOf(STATES, found["state"], "state");
|
|
248
|
+
return value;
|
|
249
|
+
}
|
|
234
250
|
/**
|
|
235
251
|
* Check an untrusted capacity reading against the contract.
|
|
236
252
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mandateops/audit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Write audit tests that check a payout backend keeps one payment identity, money limits and commitments when a provider commits but the reply is lost. Needs the mandateops CLI.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://getmandateops.com",
|