@fedify/cli 2.3.0-dev.1358 → 2.3.0-dev.1361

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.
Files changed (57) hide show
  1. package/dist/bench/action.js +29 -189
  2. package/dist/bench/command.js +13 -43
  3. package/dist/bench/load/clock.js +2 -20
  4. package/dist/bench/load/generator.js +9 -42
  5. package/dist/bench/metrics/stats-client.js +3 -65
  6. package/dist/bench/mod.js +2 -9
  7. package/dist/bench/render/markdown.js +0 -1
  8. package/dist/bench/render/text.js +0 -1
  9. package/dist/bench/result/build.js +10 -133
  10. package/dist/bench/result/expect/evaluate.js +1 -1
  11. package/dist/bench/result/schema.js +3 -353
  12. package/dist/bench/safety/gate.js +2 -4
  13. package/dist/bench/scenario/normalize.js +2 -1
  14. package/dist/bench/scenario/schema.js +9 -50
  15. package/dist/bench/scenario/validate.js +2 -2
  16. package/dist/bench/scenarios/inbox.js +12 -4
  17. package/dist/bench/scenarios/registry.js +1 -19
  18. package/dist/bench/scenarios/runner.js +1 -21
  19. package/dist/bench/scenarios/webfinger.js +1 -1
  20. package/dist/cache.js +1 -1
  21. package/dist/config.js +1 -1
  22. package/dist/deno.js +1 -1
  23. package/dist/docloader.js +1 -1
  24. package/dist/generate-vocab/action.js +1 -1
  25. package/dist/generate-vocab/command.js +3 -5
  26. package/dist/generate-vocab/mod.js +4 -0
  27. package/dist/imagerenderer.js +2 -2
  28. package/dist/inbox/command.js +4 -6
  29. package/dist/inbox.js +4 -4
  30. package/dist/init/mod.js +3 -0
  31. package/dist/log.js +2 -2
  32. package/dist/lookup.js +123 -12
  33. package/dist/mod.js +23 -2
  34. package/dist/nodeinfo.js +9 -11
  35. package/dist/options.js +1 -1
  36. package/dist/relay/command.js +4 -6
  37. package/dist/relay.js +2 -2
  38. package/dist/runner.js +46 -69
  39. package/dist/tempserver.js +1 -1
  40. package/dist/tunnel.js +4 -6
  41. package/dist/utils.js +4 -5
  42. package/dist/webfinger/action.js +1 -1
  43. package/dist/webfinger/command.js +4 -6
  44. package/dist/webfinger/lib.js +1 -1
  45. package/dist/webfinger/mod.js +4 -0
  46. package/package.json +12 -13
  47. package/dist/bench/compare/schema.js +0 -16
  48. package/dist/bench/compare.js +0 -667
  49. package/dist/bench/scenarios/actor.js +0 -38
  50. package/dist/bench/scenarios/failure.js +0 -363
  51. package/dist/bench/scenarios/fanout.js +0 -261
  52. package/dist/bench/scenarios/mixed.js +0 -244
  53. package/dist/bench/scenarios/object-discovery.js +0 -211
  54. package/dist/bench/scenarios/object.js +0 -54
  55. package/dist/bench/scenarios/read.js +0 -108
  56. package/dist/commands.js +0 -110
  57. package/dist/lookup/command.js +0 -121
package/dist/bench/mod.js CHANGED
@@ -1,11 +1,4 @@
1
1
  import "@js-temporal/polyfill";
2
+ import "./action.js";
2
3
  import "./command.js";
3
- import runBench$1 from "./action.js";
4
- import { runBenchCompare } from "./compare.js";
5
- //#region src/bench/mod.ts
6
- /** Runs a parsed benchmark command. */
7
- function runBench(command) {
8
- return command.mode === "compare" ? runBenchCompare(command) : runBench$1(command);
9
- }
10
- //#endregion
11
- export { runBench };
4
+ export {};
@@ -32,7 +32,6 @@ function renderScenario(scenario) {
32
32
  lines.push(`| Requests | ${formatNumber(r.total)} |`);
33
33
  lines.push(`| Success rate | ${formatPercent(r.successRate)} |`);
34
34
  lines.push(`| Throughput | ${formatNumber(scenario.throughputPerSec)}/s |`);
35
- if (scenario.deliveryThroughputPerSec != null) lines.push(`| Delivery throughput | ${formatNumber(scenario.deliveryThroughputPerSec)}/s |`);
36
35
  const l = scenario.client.latencyMs;
37
36
  lines.push(`| Latency p50 | ${formatNumber(l.p50)}ms |`);
38
37
  lines.push(`| Latency p95 | ${formatNumber(l.p95)}ms |`);
@@ -28,7 +28,6 @@ function renderScenario(scenario) {
28
28
  const r = scenario.requests;
29
29
  lines.push(` Requests: ${formatNumber(r.total)} (ok ${formatNumber(r.ok)}, failed ${formatNumber(r.failed)}, success ${formatPercent(r.successRate)})`);
30
30
  lines.push(` Throughput: ${formatNumber(scenario.throughputPerSec)} req/s`);
31
- if (scenario.deliveryThroughputPerSec != null) lines.push(` Delivery throughput: ${formatNumber(scenario.deliveryThroughputPerSec)} deliveries/s`);
32
31
  const l = scenario.client.latencyMs;
33
32
  lines.push(` Client latency (ms): p50 ${formatNumber(l.p50)} p95 ${formatNumber(l.p95)} p99 ${formatNumber(l.p99)} mean ${formatNumber(l.mean)} max ${formatNumber(l.max)}`);
34
33
  if (scenario.server?.signatureVerificationMs != null) lines.push(` Server signature verification (ms): ${describePartial(scenario.server.signatureVerificationMs.overall)}`);
@@ -3,8 +3,8 @@ import { version } from "../../deno.js";
3
3
  import { evaluateExpect } from "./expect/evaluate.js";
4
4
  import { REPORT_SCHEMA_ID } from "./schema.js";
5
5
  import process from "node:process";
6
- import { cpus } from "node:os";
7
6
  import { createHash } from "node:crypto";
7
+ import { cpus } from "node:os";
8
8
  //#region src/bench/result/build.ts
9
9
  /**
10
10
  * Assembly of the canonical benchmark report from measured scenario data.
@@ -23,25 +23,19 @@ import { createHash } from "node:crypto";
23
23
  * @returns The assembled scenario result.
24
24
  */
25
25
  function buildScenarioResult(scenario, measurement) {
26
- const measurements = Array.isArray(measurement) ? measurement : [measurement];
27
- if (measurements.length < 1) throw new RangeError("At least one scenario measurement is required.");
28
- const aggregate = measurements.length === 1 ? measurements[0] : aggregateMeasurements(measurements);
29
- const { results, passed } = evaluateExpect(scenario.expect, aggregate);
26
+ const { results, passed } = evaluateExpect(scenario.expect, measurement);
30
27
  return {
31
28
  name: scenario.name,
32
29
  type: scenario.type,
33
30
  load: loadSummary(scenario),
34
- requests: aggregate.requests,
35
- throughputPerSec: aggregate.throughputPerSec,
36
- ...aggregate.deliveryThroughputPerSec == null ? {} : { deliveryThroughputPerSec: aggregate.deliveryThroughputPerSec },
37
- client: aggregate.client,
38
- server: aggregate.server,
39
- errors: aggregate.errors,
31
+ requests: measurement.requests,
32
+ throughputPerSec: measurement.throughputPerSec,
33
+ client: measurement.client,
34
+ server: measurement.server,
35
+ errors: measurement.errors,
40
36
  expectations: results,
41
- passed: passed && measurements.every((m) => m.requests.total > 0),
42
- runCount: measurements.length,
43
- ...measurements.length > 1 ? { runs: measurements.map((m, index) => runResult(index + 1, m)) } : {},
44
- ...aggregate.histogram ? { histogram: aggregate.histogram } : {}
37
+ passed: passed && measurement.requests.total > 0,
38
+ ...measurement.histogram ? { histogram: measurement.histogram } : {}
45
39
  };
46
40
  }
47
41
  /**
@@ -53,7 +47,7 @@ function buildScenarioResult(scenario, measurement) {
53
47
  function buildReport(input) {
54
48
  return {
55
49
  $schema: REPORT_SCHEMA_ID,
56
- schemaVersion: 3,
50
+ schemaVersion: 1,
57
51
  tool: {
58
52
  name: "@fedify/cli",
59
53
  version
@@ -67,123 +61,6 @@ function buildReport(input) {
67
61
  scenarios: input.scenarios
68
62
  };
69
63
  }
70
- function aggregateMeasurements(measurements) {
71
- const errors = sumErrorBuckets(measurements.flatMap((m) => m.errors));
72
- const total = sum(measurements.map((m) => m.requests.total));
73
- const ok = sum(measurements.map((m) => m.requests.ok));
74
- const failed = sum(measurements.map((m) => m.requests.failed));
75
- const delivery = medianPresent(measurements.map((m) => m.deliveryThroughputPerSec));
76
- return {
77
- requests: {
78
- total,
79
- ok,
80
- failed,
81
- successRate: Math.min(...measurements.map((m) => m.requests.successRate))
82
- },
83
- throughputPerSec: median(measurements.map((m) => m.throughputPerSec)),
84
- ...delivery == null ? {} : { deliveryThroughputPerSec: delivery },
85
- client: { latencyMs: {
86
- p50: median(measurements.map((m) => m.client.latencyMs.p50)),
87
- p95: median(measurements.map((m) => m.client.latencyMs.p95)),
88
- p99: median(measurements.map((m) => m.client.latencyMs.p99)),
89
- mean: median(measurements.map((m) => m.client.latencyMs.mean)),
90
- max: median(measurements.map((m) => m.client.latencyMs.max))
91
- } },
92
- server: aggregateServer(measurements.map((m) => m.server)),
93
- errors
94
- };
95
- }
96
- function runResult(run, measurement) {
97
- return {
98
- run,
99
- requests: measurement.requests,
100
- throughputPerSec: measurement.throughputPerSec,
101
- ...measurement.deliveryThroughputPerSec == null ? {} : { deliveryThroughputPerSec: measurement.deliveryThroughputPerSec },
102
- client: measurement.client,
103
- server: measurement.server,
104
- errors: measurement.errors,
105
- ...measurement.histogram ? { histogram: measurement.histogram } : {}
106
- };
107
- }
108
- function aggregateServer(servers) {
109
- const present = servers.filter((s) => s != null);
110
- if (present.length !== servers.length) return null;
111
- const signature = aggregateSignatureVerification(present);
112
- const queue = aggregateQueue(present);
113
- return {
114
- ...signature == null ? {} : { signatureVerificationMs: signature },
115
- ...queue == null ? {} : { queue }
116
- };
117
- }
118
- function aggregateSignatureVerification(servers) {
119
- const values = servers.map((s) => s.signatureVerificationMs).filter((s) => s != null);
120
- if (values.length !== servers.length) return null;
121
- const standards = /* @__PURE__ */ new Set();
122
- for (const value of values) for (const key of Object.keys(value.byStandard ?? {})) standards.add(key);
123
- const byStandard = {};
124
- for (const standard of standards) byStandard[standard] = aggregatePartial(values.map((v) => v.byStandard?.[standard]), "present");
125
- return {
126
- overall: aggregatePartial(values.map((v) => v.overall)),
127
- ...Object.keys(byStandard).length < 1 ? {} : { byStandard }
128
- };
129
- }
130
- function aggregateQueue(servers) {
131
- const values = servers.map((s) => s.queue).filter((q) => q != null);
132
- if (values.length !== servers.length) return null;
133
- const drainMs = aggregatePartial(values.map((v) => v.drainMs));
134
- const depths = values.map((v) => v.depthMax);
135
- return {
136
- ...hasPartial(drainMs) ? { drainMs } : {},
137
- ...depths.every(isNumber) ? { depthMax: Math.max(...depths) } : {}
138
- };
139
- }
140
- function aggregatePartial(values, mode = "complete") {
141
- return {
142
- ...partialField(values, "p50", mode),
143
- ...partialField(values, "p95", mode),
144
- ...partialField(values, "p99", mode)
145
- };
146
- }
147
- function partialField(values, key, mode) {
148
- const fieldValues = values.map((v) => v?.[key]);
149
- if (mode === "present") {
150
- const present = fieldValues.filter(isNumber);
151
- return present.length < 1 ? {} : { [key]: median(present) };
152
- }
153
- return fieldValues.every(isNumber) ? { [key]: median(fieldValues) } : {};
154
- }
155
- function hasPartial(value) {
156
- return value.p50 != null || value.p95 != null || value.p99 != null;
157
- }
158
- function sumErrorBuckets(errors) {
159
- const buckets = /* @__PURE__ */ new Map();
160
- for (const error of errors) {
161
- const key = `${error.kind}|${error.status ?? ""}|${error.reason}`;
162
- const previous = buckets.get(key);
163
- buckets.set(key, {
164
- ...error,
165
- count: (previous?.count ?? 0) + error.count
166
- });
167
- }
168
- return [...buckets.values()].sort((a, b) => b.count - a.count);
169
- }
170
- function medianPresent(values) {
171
- const present = values.filter(isNumber);
172
- return present.length < 1 ? null : median(present);
173
- }
174
- function median(values) {
175
- if (values.length < 1) return 0;
176
- const sorted = [...values].sort((a, b) => a - b);
177
- const middle = Math.floor(sorted.length / 2);
178
- if (sorted.length % 2 === 1) return sorted[middle];
179
- return (sorted[middle - 1] + sorted[middle]) / 2;
180
- }
181
- function sum(values) {
182
- return values.reduce((a, b) => a + b, 0);
183
- }
184
- function isNumber(value) {
185
- return typeof value === "number" && Number.isFinite(value);
186
- }
187
64
  /** Detects the current runtime environment for reproducibility metadata. */
188
65
  function detectEnvironment() {
189
66
  const g = globalThis;
@@ -78,7 +78,7 @@ function lookupValue(metrics, metric) {
78
78
  switch (metric) {
79
79
  case "successRate": return metrics.requests.successRate;
80
80
  case "throughputPerSec": return metrics.throughputPerSec;
81
- case "deliveryThroughput": return metrics.deliveryThroughputPerSec ?? null;
81
+ case "deliveryThroughput": return null;
82
82
  case "errors.total": return sumErrors(metrics.errors);
83
83
  case "errors.4xx": return sumErrors(metrics.errors, {
84
84
  min: 400,
@@ -4,362 +4,12 @@ import "@js-temporal/polyfill";
4
4
  * The embedded JSON Schema (draft 2020-12) for benchmark report output.
5
5
  *
6
6
  * Like the scenario schema, this object is the runtime copy and is published,
7
- * byte-for-byte, as files under *schema/bench/*; a drift guard keeps the two in
8
- * sync. The matching TypeScript types live in {@link ./model.ts}.
7
+ * byte-for-byte, as *schema/bench/report-v1.json*; a drift guard keeps the two
8
+ * in sync. The matching TypeScript types live in {@link ./model.ts}.
9
9
  * @since 2.3.0
10
10
  * @module
11
11
  */
12
12
  /** The hosted URL that serves the report schema. */
13
- const REPORT_SCHEMA_ID = "https://json-schema.fedify.dev/bench/report-v3.json";
14
- /** The hosted URL for the version 2 report schema. */
15
- const REPORT_SCHEMA_V2_ID = "https://json-schema.fedify.dev/bench/report-v2.json";
16
- /** The benchmark report JSON Schema (draft 2020-12). */
17
- const reportSchemaV1 = {
18
- $schema: "https://json-schema.org/draft/2020-12/schema",
19
- $id: "https://json-schema.fedify.dev/bench/report-v1.json",
20
- title: "Fedify benchmark report",
21
- type: "object",
22
- additionalProperties: false,
23
- required: [
24
- "schemaVersion",
25
- "tool",
26
- "environment",
27
- "target",
28
- "startedAt",
29
- "finishedAt",
30
- "suite",
31
- "passed",
32
- "scenarios"
33
- ],
34
- properties: {
35
- $schema: { type: "string" },
36
- schemaVersion: { const: 1 },
37
- tool: {
38
- type: "object",
39
- additionalProperties: false,
40
- required: ["name", "version"],
41
- properties: {
42
- name: { type: "string" },
43
- version: { type: "string" }
44
- }
45
- },
46
- environment: {
47
- type: "object",
48
- additionalProperties: false,
49
- required: [
50
- "runtime",
51
- "runtimeVersion",
52
- "os",
53
- "cpuCount"
54
- ],
55
- properties: {
56
- runtime: { type: "string" },
57
- runtimeVersion: { type: "string" },
58
- os: { type: "string" },
59
- cpuCount: {
60
- type: "integer",
61
- minimum: 0
62
- }
63
- }
64
- },
65
- target: {
66
- type: "object",
67
- additionalProperties: false,
68
- required: ["url", "statsAvailable"],
69
- properties: {
70
- url: { type: "string" },
71
- fedifyVersion: { type: ["string", "null"] },
72
- statsAvailable: { type: "boolean" }
73
- }
74
- },
75
- startedAt: { type: "string" },
76
- finishedAt: { type: "string" },
77
- suite: {
78
- type: "object",
79
- additionalProperties: false,
80
- required: ["configHash"],
81
- properties: {
82
- name: { type: "string" },
83
- configHash: { type: "string" }
84
- }
85
- },
86
- passed: { type: "boolean" },
87
- scenarios: {
88
- type: "array",
89
- items: { $ref: "#/$defs/scenarioResult" }
90
- }
91
- },
92
- $defs: {
93
- latencyMs: {
94
- type: "object",
95
- additionalProperties: false,
96
- required: [
97
- "p50",
98
- "p95",
99
- "p99",
100
- "mean",
101
- "max"
102
- ],
103
- properties: {
104
- p50: { type: "number" },
105
- p95: { type: "number" },
106
- p99: { type: "number" },
107
- mean: { type: "number" },
108
- max: { type: "number" }
109
- }
110
- },
111
- partialLatencyMs: {
112
- type: "object",
113
- additionalProperties: false,
114
- properties: {
115
- p50: { type: "number" },
116
- p95: { type: "number" },
117
- p99: { type: "number" }
118
- }
119
- },
120
- loadSummary: {
121
- type: "object",
122
- additionalProperties: false,
123
- required: [
124
- "model",
125
- "durationMs",
126
- "warmupMs"
127
- ],
128
- properties: {
129
- model: { enum: ["open", "closed"] },
130
- ratePerSec: { type: "number" },
131
- arrival: { type: "string" },
132
- concurrency: { type: "integer" },
133
- durationMs: { type: "number" },
134
- warmupMs: { type: "number" },
135
- maxInFlight: { type: "integer" }
136
- },
137
- oneOf: [{
138
- properties: { model: { const: "open" } },
139
- required: ["ratePerSec", "arrival"],
140
- not: { required: ["concurrency"] }
141
- }, {
142
- properties: { model: { const: "closed" } },
143
- required: ["concurrency"],
144
- not: { anyOf: [{ required: ["ratePerSec"] }, { required: ["arrival"] }] }
145
- }]
146
- },
147
- requestSummary: {
148
- type: "object",
149
- additionalProperties: false,
150
- required: [
151
- "total",
152
- "ok",
153
- "failed",
154
- "successRate"
155
- ],
156
- properties: {
157
- total: {
158
- type: "integer",
159
- minimum: 0
160
- },
161
- ok: {
162
- type: "integer",
163
- minimum: 0
164
- },
165
- failed: {
166
- type: "integer",
167
- minimum: 0
168
- },
169
- successRate: {
170
- type: "number",
171
- minimum: 0,
172
- maximum: 1
173
- }
174
- }
175
- },
176
- clientMetrics: {
177
- type: "object",
178
- additionalProperties: false,
179
- required: ["latencyMs"],
180
- properties: { latencyMs: { $ref: "#/$defs/latencyMs" } }
181
- },
182
- serverMetrics: {
183
- type: "object",
184
- additionalProperties: false,
185
- properties: {
186
- signatureVerificationMs: {
187
- type: "object",
188
- additionalProperties: false,
189
- required: ["overall"],
190
- properties: {
191
- overall: { $ref: "#/$defs/partialLatencyMs" },
192
- byStandard: {
193
- type: "object",
194
- additionalProperties: { $ref: "#/$defs/partialLatencyMs" }
195
- }
196
- }
197
- },
198
- queue: {
199
- type: "object",
200
- additionalProperties: false,
201
- properties: {
202
- drainMs: { $ref: "#/$defs/partialLatencyMs" },
203
- depthMax: { type: "number" }
204
- }
205
- }
206
- }
207
- },
208
- errorBucket: {
209
- type: "object",
210
- additionalProperties: false,
211
- required: [
212
- "kind",
213
- "reason",
214
- "count"
215
- ],
216
- properties: {
217
- kind: { type: "string" },
218
- status: { type: "integer" },
219
- reason: { type: "string" },
220
- count: {
221
- type: "integer",
222
- minimum: 0
223
- }
224
- }
225
- },
226
- expectResult: {
227
- type: "object",
228
- additionalProperties: false,
229
- required: [
230
- "metric",
231
- "op",
232
- "threshold",
233
- "unit",
234
- "actual",
235
- "severity",
236
- "pass"
237
- ],
238
- properties: {
239
- metric: { type: "string" },
240
- op: { enum: [
241
- "lt",
242
- "lte",
243
- "gt",
244
- "gte",
245
- "eq"
246
- ] },
247
- threshold: { type: "number" },
248
- unit: { type: ["string", "null"] },
249
- actual: { type: ["number", "null"] },
250
- severity: { enum: ["warn", "fail"] },
251
- pass: { type: "boolean" }
252
- }
253
- },
254
- scenarioResult: {
255
- type: "object",
256
- additionalProperties: false,
257
- required: [
258
- "name",
259
- "type",
260
- "load",
261
- "requests",
262
- "throughputPerSec",
263
- "client",
264
- "server",
265
- "errors",
266
- "expectations",
267
- "passed"
268
- ],
269
- properties: {
270
- name: { type: "string" },
271
- type: { enum: [
272
- "inbox",
273
- "webfinger",
274
- "actor",
275
- "object",
276
- "fanout",
277
- "collection",
278
- "failure",
279
- "mixed"
280
- ] },
281
- load: { $ref: "#/$defs/loadSummary" },
282
- requests: { $ref: "#/$defs/requestSummary" },
283
- throughputPerSec: { type: "number" },
284
- client: { $ref: "#/$defs/clientMetrics" },
285
- server: { anyOf: [{ $ref: "#/$defs/serverMetrics" }, { type: "null" }] },
286
- errors: {
287
- type: "array",
288
- items: { $ref: "#/$defs/errorBucket" }
289
- },
290
- expectations: {
291
- type: "array",
292
- items: { $ref: "#/$defs/expectResult" }
293
- },
294
- passed: { type: "boolean" },
295
- histogram: { $ref: "#/$defs/serializedHistogram" }
296
- }
297
- },
298
- serializedHistogram: {
299
- type: "object",
300
- additionalProperties: false,
301
- required: [
302
- "version",
303
- "subBucketCount",
304
- "count",
305
- "zeroCount",
306
- "min",
307
- "max",
308
- "sum",
309
- "indices",
310
- "counts"
311
- ],
312
- properties: {
313
- version: { const: 1 },
314
- subBucketCount: {
315
- type: "integer",
316
- minimum: 1
317
- },
318
- count: {
319
- type: "integer",
320
- minimum: 0
321
- },
322
- zeroCount: {
323
- type: "integer",
324
- minimum: 0
325
- },
326
- min: { type: "number" },
327
- max: { type: "number" },
328
- sum: { type: "number" },
329
- indices: {
330
- type: "array",
331
- items: { type: "integer" }
332
- },
333
- counts: {
334
- type: "array",
335
- items: {
336
- type: "integer",
337
- minimum: 0
338
- }
339
- }
340
- }
341
- }
342
- }
343
- };
344
- /** The benchmark report JSON Schema (draft 2020-12). */
345
- const reportSchemaV2 = {
346
- ...reportSchemaV1,
347
- $id: REPORT_SCHEMA_V2_ID,
348
- properties: {
349
- ...reportSchemaV1.properties,
350
- schemaVersion: { const: 2 }
351
- },
352
- $defs: {
353
- ...reportSchemaV1.$defs,
354
- scenarioResult: {
355
- ...reportSchemaV1.$defs.scenarioResult,
356
- properties: {
357
- ...reportSchemaV1.$defs.scenarioResult.properties,
358
- deliveryThroughputPerSec: { type: "number" }
359
- }
360
- }
361
- }
362
- };
363
- ({ ...reportSchemaV2 }), { ...reportSchemaV2.properties }, { ...reportSchemaV2.$defs }, { ...reportSchemaV2.$defs.scenarioResult }, [...reportSchemaV2.$defs.scenarioResult.required], { ...reportSchemaV2.$defs.scenarioResult.properties };
13
+ const REPORT_SCHEMA_ID = "https://json-schema.fedify.dev/bench/report-v1.json";
364
14
  //#endregion
365
15
  export { REPORT_SCHEMA_ID };
@@ -20,9 +20,8 @@ function assertTargetAllowed(context) {
20
20
  *
21
21
  * The override is only meaningful for a public target that does not advertise
22
22
  * benchmark mode. In that caution tier, the operator must name the target on
23
- * the command line for this run and must explicitly set load, duration, and
24
- * runs, so the built-in defaults cannot accidentally create a long public
25
- * benchmark.
23
+ * the command line for this run and must explicitly set load and duration, so
24
+ * the built-in defaults cannot accidentally create a long public benchmark.
26
25
  * @param context The unsafe override decision inputs.
27
26
  * @throws {UnsafeTargetError} If the unsafe override is too broad.
28
27
  */
@@ -32,7 +31,6 @@ function assertUnsafeOverrideAllowed(context) {
32
31
  for (const scenario of context.scenarios) {
33
32
  if (!scenario.explicitLoad) throw new UnsafeTargetError(`Scenario "${scenario.name}" uses the built-in benchmark load default. Set rate or concurrency explicitly before using --allow-unsafe-target against a public target.`);
34
33
  if (!scenario.explicitDuration) throw new UnsafeTargetError(`Scenario "${scenario.name}" uses the built-in benchmark duration default. Set duration explicitly before using --allow-unsafe-target against a public target.`);
35
- if (!scenario.explicitRuns) throw new UnsafeTargetError(`Scenario "${scenario.name}" uses the built-in benchmark runs default. Set runs explicitly before using --allow-unsafe-target against a public target.`);
36
34
  }
37
35
  }
38
36
  /**
@@ -19,7 +19,7 @@ const DEFAULT_DURATION_MS = 6e4;
19
19
  const DEFAULT_WARMUP_MS = 0;
20
20
  const DEFAULT_RATE_PER_SEC = 50;
21
21
  const DEFAULT_SIGNING = "pipeline";
22
- const DEFAULT_RUNS = 3;
22
+ const DEFAULT_RUNS = 1;
23
23
  /** An error raised when a suite cannot be normalized. */
24
24
  var SuiteNormalizeError = class extends Error {};
25
25
  /**
@@ -57,6 +57,7 @@ function resolveScenario(scenario, suite) {
57
57
  const warmupMs = resolveDuration(scenario.warmup ?? defaults.warmup, DEFAULT_WARMUP_MS);
58
58
  if (warmupMs >= durationMs) throw new SuiteNormalizeError(`Scenario "${scenario.name}": warmup (${warmupMs}ms) must be shorter than duration (${durationMs}ms); otherwise no requests are measured.`);
59
59
  const runs = scenario.runs ?? defaults.runs ?? DEFAULT_RUNS;
60
+ if (runs > 1) throw new SuiteNormalizeError(`Scenario "${scenario.name}": multiple runs (runs > 1) are not yet implemented in fedify bench; set runs to 1.`);
60
61
  return {
61
62
  name: scenario.name,
62
63
  type: scenario.type,