@mandateops/audit 0.4.2 → 0.6.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 +19 -7
- package/dist/index.js +35 -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.
|
|
@@ -10,7 +10,7 @@ export type { AttemptEnd, AttemptResult, CapacityReading, Flow, HistoryEvent, Hi
|
|
|
10
10
|
export declare const SYNTHETIC_MAX_MINOR_UNITS = 340282366920938463463374607431768211455n;
|
|
11
11
|
/** Integer minor units as a decimal string, so no language loses precision. */
|
|
12
12
|
export declare function minorUnits(value: string): string;
|
|
13
|
-
declare const PROPERTIES: readonly ["instruction_binding", "commitment_retention", "retry_identity", "effect_within_authorisation", "reconciliation_consistency", "external_outcome"];
|
|
13
|
+
declare const PROPERTIES: readonly ["instruction_binding", "commitment_retention", "retry_identity", "effect_within_authorisation", "reconciliation_consistency", "external_outcome", "reported_outcome"];
|
|
14
14
|
export type Property = (typeof PROPERTIES)[number];
|
|
15
15
|
declare const PROPERTY_RESULTS: readonly ["satisfied", "violated", "inconclusive", "not_applicable"];
|
|
16
16
|
declare const ASSURANCES: readonly ["backend_and_provider", "backend_and_provider_commitments_not_checked", "money_out_only_commitments_not_checked", "no_assurance"];
|
|
@@ -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.
|
|
@@ -42,6 +42,7 @@ const PROPERTIES = [
|
|
|
42
42
|
"effect_within_authorisation",
|
|
43
43
|
"reconciliation_consistency",
|
|
44
44
|
"external_outcome",
|
|
45
|
+
"reported_outcome",
|
|
45
46
|
];
|
|
46
47
|
const PROPERTY_RESULTS = [
|
|
47
48
|
"satisfied",
|
|
@@ -76,7 +77,7 @@ const isRecord = (value) => typeof value === "object" && value !== null && !Arra
|
|
|
76
77
|
* The session protocol this SDK speaks. The host announces its own when
|
|
77
78
|
* ready; the two must match, as they do for an SDK shipped with its CLI.
|
|
78
79
|
*/
|
|
79
|
-
export const SESSION_PROTOCOL =
|
|
80
|
+
export const SESSION_PROTOCOL = 4;
|
|
80
81
|
/** Why a host of another protocol is refused, and how to fix it. */
|
|
81
82
|
export function protocolMismatch(announced) {
|
|
82
83
|
const host = announced === undefined
|
|
@@ -253,8 +254,15 @@ const readingProfile = (config) => config.financialProfile === "payout-effects-o
|
|
|
253
254
|
*/
|
|
254
255
|
export function requireRunnable(config) {
|
|
255
256
|
const profile = readingProfile(config);
|
|
256
|
-
if (profile === undefined &&
|
|
257
|
-
|
|
257
|
+
if (profile === undefined &&
|
|
258
|
+
config.adapter &&
|
|
259
|
+
!("status" in config.adapter)) {
|
|
260
|
+
throw new AuditUsageError("payout-effects-only reads no lifecycle or capacity: give it a status " +
|
|
261
|
+
"adapter (contracts/history-reading.md), or none");
|
|
262
|
+
}
|
|
263
|
+
if (profile !== undefined && config.adapter && "status" in config.adapter) {
|
|
264
|
+
throw new AuditUsageError(`${profile} reads lifecycle and capacity, not a status: a status ` +
|
|
265
|
+
"adapter is for payout-effects-only");
|
|
258
266
|
}
|
|
259
267
|
if (profile !== undefined && !config.adapter) {
|
|
260
268
|
throw new AuditUsageError(`${profile} needs an adapter with history and capacity readings ` +
|
|
@@ -391,10 +399,12 @@ async function session(config, body, channel, backend, credentials) {
|
|
|
391
399
|
const context = () => providerPayment === undefined
|
|
392
400
|
? undefined
|
|
393
401
|
: { backend, viewerCredential, providerPayment };
|
|
402
|
+
const readings = config.adapter && "history" in config.adapter ? config.adapter : undefined;
|
|
403
|
+
const statusAdapter = config.adapter && "status" in config.adapter ? config.adapter : undefined;
|
|
394
404
|
const readHistory = async (slot) => {
|
|
395
405
|
const read = context();
|
|
396
|
-
if (
|
|
397
|
-
const { history } =
|
|
406
|
+
if (readings && profile && read && authorised) {
|
|
407
|
+
const { history } = readings;
|
|
398
408
|
const reference = authorised.expected.reference;
|
|
399
409
|
await reading(slot, async () => {
|
|
400
410
|
const taken = parseHistoryReading(await history(read));
|
|
@@ -405,11 +415,27 @@ async function session(config, body, channel, backend, credentials) {
|
|
|
405
415
|
};
|
|
406
416
|
const readCapacity = async (slot) => {
|
|
407
417
|
const read = context();
|
|
408
|
-
const capacity =
|
|
418
|
+
const capacity = readings?.capacity;
|
|
409
419
|
if (capacity && read) {
|
|
410
420
|
await reading(slot, async () => parseCapacityReading(await capacity(read)));
|
|
411
421
|
}
|
|
412
422
|
};
|
|
423
|
+
/**
|
|
424
|
+
* The backend's reported outcome, after the provider's records. Without a
|
|
425
|
+
* binding there is nothing to read by, which is reported, never skipped.
|
|
426
|
+
*/
|
|
427
|
+
const readStatus = async () => {
|
|
428
|
+
if (!statusAdapter || !authorised)
|
|
429
|
+
return;
|
|
430
|
+
const reference = authorised.expected.reference;
|
|
431
|
+
const read = context();
|
|
432
|
+
await reading("capture_status", async () => {
|
|
433
|
+
if (!read) {
|
|
434
|
+
throw new AuditUsageError("a status adapter reads by the provider payment: call bindProviderPayment");
|
|
435
|
+
}
|
|
436
|
+
return parseStatusReading(await statusAdapter.status(read), reference);
|
|
437
|
+
});
|
|
438
|
+
};
|
|
413
439
|
const requireBound = (doing) => {
|
|
414
440
|
if (profile && (authorised || competing) && providerPayment === undefined) {
|
|
415
441
|
throw new AuditUsageError(`${doing} under ${profile} needs bindProviderPayment first: adapters read by that identifier`);
|
|
@@ -583,6 +609,7 @@ async function session(config, body, channel, backend, credentials) {
|
|
|
583
609
|
await readCapacity("capture_capacity");
|
|
584
610
|
await channel.send({ type: "read_effects" });
|
|
585
611
|
await readHistory("recapture_history");
|
|
612
|
+
await readStatus();
|
|
586
613
|
const result = {
|
|
587
614
|
...parseResult(await channel.send({ type: "seal" })),
|
|
588
615
|
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.6.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",
|