humanish 0.129.0 → 0.130.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/CHANGELOG.md +11 -0
- package/dist/actors/stop-cause.d.ts +1 -1
- package/dist/analysis/automatic.js +0 -1
- package/dist/analysis/evidence-sources.d.ts +44 -7
- package/dist/analysis/evidence-sources.js +40 -6
- package/dist/analysis/evidence.js +96 -122
- package/dist/analysis/service.js +12 -6
- package/dist/analysis/source.d.ts +28 -0
- package/dist/analysis/source.js +118 -0
- package/dist/feedback/export.js +0 -1
- package/dist/observer/data.d.ts +2 -2
- package/dist/observer-app.html +1 -1
- package/dist/run/bundle-shape.d.ts +2 -2
- package/dist/run/bundle.d.ts +8 -1
- package/dist/run/detail.js +4 -1
- package/dist/run/participant-records.d.ts +1 -1
- package/dist/run/stream-shape.d.ts +2 -2
- package/dist/run/stream-trace.d.ts +21 -7
- package/dist/run/stream-trace.js +42 -8
- package/dist/run/streams.d.ts +18 -0
- package/dist/study/bundle-estimate.d.ts +4 -1
- package/dist/study/bundle-estimate.js +40 -15
- package/dist/verify/verify.js +28 -21
- package/docs/contracts/run-bundle.md +2 -2
- package/docs/contracts/study-analysis.md +13 -1
- package/package.json +1 -1
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type StoredRunBundle } from "../run/bundle.js";
|
|
2
|
+
import { type PreparedRunArtifactPaths } from "../run/paths.js";
|
|
3
|
+
import type { RunStream } from "../run/streams.js";
|
|
4
|
+
/**
|
|
5
|
+
* A refusal of the run's recorded source. Its message is the code, as every analysis error's is,
|
|
6
|
+
* and `reason` says which file analysis did not read and why, for the person who asked.
|
|
7
|
+
*/
|
|
8
|
+
export declare class AnalysisSourceRefusedError extends Error {
|
|
9
|
+
readonly reason: string;
|
|
10
|
+
constructor(code: "ANALYSIS_SOURCE_TOO_LARGE" | "ANALYSIS_SOURCE_INVALID", reason: string);
|
|
11
|
+
}
|
|
12
|
+
/** The refusal of a run.json of `size` bytes, more than the `max` analysis reads of it. */
|
|
13
|
+
export declare function runFileTooLarge(size: number | bigint, max: number): AnalysisSourceRefusedError;
|
|
14
|
+
/**
|
|
15
|
+
* run.json's bundle, when it is a run bundle of the prepared run that analysis reads: at most
|
|
16
|
+
* EVIDENCE_LIMITS.sourceBytes, at most EVIDENCE_LIMITS.participants participants, and each
|
|
17
|
+
* participant and event with the fields analysis reads.
|
|
18
|
+
*/
|
|
19
|
+
export declare function parseAnalysisSource(prepared: PreparedRunArtifactPaths, bytes: Buffer): StoredRunBundle;
|
|
20
|
+
/**
|
|
21
|
+
* What `keep` takes from each stream of run.json's bundle with the stream's trace loaded, in stream
|
|
22
|
+
* order. Each trace file is read up to RUN_ARTIFACT_MAX_BYTES, as the loader, verify and the
|
|
23
|
+
* Observer read it, and is dropped once `keep` returns, so analysis holds one trace at a time
|
|
24
|
+
* whatever the number of participants. run.json's digest, which the analysis record binds, binds
|
|
25
|
+
* each trace's sha256. A trace whose index records more than the limit is refused as
|
|
26
|
+
* ANALYSIS_SOURCE_TOO_LARGE, and any other trace the loader refuses as ANALYSIS_SOURCE_INVALID.
|
|
27
|
+
*/
|
|
28
|
+
export declare function readParticipantTraces<T>(prepared: PreparedRunArtifactPaths, bundle: StoredRunBundle, keep: (stream: RunStream, index: number) => T): Promise<T[]>;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// How analysis reads a run's recorded source: run.json, then each participant's trace one at a time,
|
|
2
|
+
// and the refusals it gives for either.
|
|
3
|
+
import { isCommsReceivingEvidence } from "../comms/receiving-evidence.js";
|
|
4
|
+
import { isReadRunBundleSchema } from "../run/bundle.js";
|
|
5
|
+
import { runIdOf } from "../run/paths.js";
|
|
6
|
+
import { reduceRunTraces, StreamTraceRefusedError, StreamTraceTooLargeError, } from "../run/stream-trace.js";
|
|
7
|
+
import { isRecord } from "../run/type-guards.js";
|
|
8
|
+
import { EVIDENCE_LIMITS } from "./analysis-limits.js";
|
|
9
|
+
/**
|
|
10
|
+
* A refusal of the run's recorded source. Its message is the code, as every analysis error's is,
|
|
11
|
+
* and `reason` says which file analysis did not read and why, for the person who asked.
|
|
12
|
+
*/
|
|
13
|
+
export class AnalysisSourceRefusedError extends Error {
|
|
14
|
+
reason;
|
|
15
|
+
constructor(code, reason) {
|
|
16
|
+
super(code);
|
|
17
|
+
this.reason = reason;
|
|
18
|
+
this.name = "AnalysisSourceRefusedError";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const bytes = (count) => `${count.toLocaleString("en-US")} bytes`;
|
|
22
|
+
const limit = (max) => `${bytes(max)} (${(max / (1024 * 1024)).toLocaleString("en-US", { maximumFractionDigits: 1 })} MiB)`;
|
|
23
|
+
const runFileInvalid = (reason) => new AnalysisSourceRefusedError("ANALYSIS_SOURCE_INVALID", `Analysis did not read run.json: ${reason}`);
|
|
24
|
+
const traceInvalid = (streamId, reason) => new AnalysisSourceRefusedError("ANALYSIS_SOURCE_INVALID", `Analysis did not read participant ${streamId}'s trace: ${reason}`);
|
|
25
|
+
/** The refusal of a run.json of `size` bytes, more than the `max` analysis reads of it. */
|
|
26
|
+
export function runFileTooLarge(size, max) {
|
|
27
|
+
return new AnalysisSourceRefusedError("ANALYSIS_SOURCE_TOO_LARGE", `Analysis did not read run.json: it holds ${bytes(size)}, more than the ${limit(max)} analysis reads of it. run.json grows with each participant and the study's mission; split the participants across studies to keep it under the limit.`);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* run.json's bundle, when it is a run bundle of the prepared run that analysis reads: at most
|
|
31
|
+
* EVIDENCE_LIMITS.sourceBytes, at most EVIDENCE_LIMITS.participants participants, and each
|
|
32
|
+
* participant and event with the fields analysis reads.
|
|
33
|
+
*/
|
|
34
|
+
export function parseAnalysisSource(prepared, bytes) {
|
|
35
|
+
if (bytes.length > EVIDENCE_LIMITS.sourceBytes)
|
|
36
|
+
throw runFileTooLarge(bytes.length, EVIDENCE_LIMITS.sourceBytes);
|
|
37
|
+
const value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
38
|
+
if (!isRecord(value) || !isReadRunBundleSchema(value.schema))
|
|
39
|
+
throw runFileInvalid("it is not a run bundle of a schema this version of humanish reads.");
|
|
40
|
+
if (value.runId !== runIdOf(prepared))
|
|
41
|
+
throw runFileInvalid(`it records another run than ${runIdOf(prepared)}, the run in its folder.`);
|
|
42
|
+
if (!Array.isArray(value.streams) || !Array.isArray(value.events))
|
|
43
|
+
throw runFileInvalid("it has no participants or events list.");
|
|
44
|
+
if (value.streams.length > EVIDENCE_LIMITS.participants)
|
|
45
|
+
throw runFileInvalid(`it lists ${value.streams.length} participants, more than the ${EVIDENCE_LIMITS.participants} analysis reads; split the participants across studies.`);
|
|
46
|
+
if (value.commsReceiving !== undefined && !isCommsReceivingEvidence(value.commsReceiving))
|
|
47
|
+
throw runFileInvalid("its email receiving evidence holds a field or value analysis does not read.");
|
|
48
|
+
const ids = new Set();
|
|
49
|
+
for (const [index, stream] of value.streams.entries()) {
|
|
50
|
+
if (!isRecord(stream) ||
|
|
51
|
+
typeof stream.id !== "string" ||
|
|
52
|
+
stream.id.length === 0 ||
|
|
53
|
+
stream.id.length > 256 ||
|
|
54
|
+
ids.has(stream.id) ||
|
|
55
|
+
typeof stream.label !== "string" ||
|
|
56
|
+
typeof stream.status !== "string") {
|
|
57
|
+
throw runFileInvalid(`participant ${index + 1} needs an id of 1 to 256 characters no other participant has, a label and a status.`);
|
|
58
|
+
}
|
|
59
|
+
ids.add(stream.id);
|
|
60
|
+
if (stream.actor !== undefined && !isRecord(stream.actor))
|
|
61
|
+
throw runFileInvalid(`participant ${stream.id}'s actor is not an object.`);
|
|
62
|
+
}
|
|
63
|
+
for (const [index, event] of value.events.entries()) {
|
|
64
|
+
if (!isRecord(event) ||
|
|
65
|
+
typeof event.id !== "string" ||
|
|
66
|
+
typeof event.message !== "string" ||
|
|
67
|
+
typeof event.type !== "string")
|
|
68
|
+
throw runFileInvalid(`event ${index + 1} needs an id, a type and a message.`);
|
|
69
|
+
}
|
|
70
|
+
return value;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* What `keep` takes from each stream of run.json's bundle with the stream's trace loaded, in stream
|
|
74
|
+
* order. Each trace file is read up to RUN_ARTIFACT_MAX_BYTES, as the loader, verify and the
|
|
75
|
+
* Observer read it, and is dropped once `keep` returns, so analysis holds one trace at a time
|
|
76
|
+
* whatever the number of participants. run.json's digest, which the analysis record binds, binds
|
|
77
|
+
* each trace's sha256. A trace whose index records more than the limit is refused as
|
|
78
|
+
* ANALYSIS_SOURCE_TOO_LARGE, and any other trace the loader refuses as ANALYSIS_SOURCE_INVALID.
|
|
79
|
+
*/
|
|
80
|
+
export async function readParticipantTraces(prepared, bundle, keep) {
|
|
81
|
+
try {
|
|
82
|
+
return await reduceRunTraces(prepared, bundle, (stream, index) => {
|
|
83
|
+
validateTraceItems(stream);
|
|
84
|
+
return keep(stream, index);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
// The loader's refusal names the participant, the file, why, and what to do.
|
|
89
|
+
if (error instanceof StreamTraceRefusedError)
|
|
90
|
+
throw new AnalysisSourceRefusedError(error instanceof StreamTraceTooLargeError
|
|
91
|
+
? "ANALYSIS_SOURCE_TOO_LARGE"
|
|
92
|
+
: "ANALYSIS_SOURCE_INVALID", error.message);
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function validateTraceItems(stream) {
|
|
97
|
+
const actor = stream.actor;
|
|
98
|
+
if (actor === undefined)
|
|
99
|
+
return;
|
|
100
|
+
if (!isRecord(actor) || !Array.isArray(actor.items))
|
|
101
|
+
throw traceInvalid(stream.id, "it has no items list.");
|
|
102
|
+
// The place each id was first seen. A refusal names places, since an id is trace text.
|
|
103
|
+
const firstPlace = new Map();
|
|
104
|
+
for (const [index, item] of actor.items.entries()) {
|
|
105
|
+
if (!isRecord(item) ||
|
|
106
|
+
typeof item.id !== "string" ||
|
|
107
|
+
item.id.length === 0 ||
|
|
108
|
+
item.id.length > 256 ||
|
|
109
|
+
typeof item.kind !== "string" ||
|
|
110
|
+
typeof item.title !== "string" ||
|
|
111
|
+
(item.text !== undefined && typeof item.text !== "string"))
|
|
112
|
+
throw traceInvalid(stream.id, `item ${index + 1} needs an id of 1 to 256 characters, a kind and a title, and a text that is a string if it has one.`);
|
|
113
|
+
const first = firstPlace.get(item.id);
|
|
114
|
+
if (first !== undefined)
|
|
115
|
+
throw traceInvalid(stream.id, `items ${first + 1} and ${index + 1} share an id, and analysis cites each item by its id.`);
|
|
116
|
+
firstPlace.set(item.id, index);
|
|
117
|
+
}
|
|
118
|
+
}
|
package/dist/feedback/export.js
CHANGED
|
@@ -17,7 +17,6 @@ import { buildObserverData, OBSERVER_DATA_SCHEMA, observerDataFromSaved, } from
|
|
|
17
17
|
import { resolveRunPath } from "../run/locate.js";
|
|
18
18
|
import { loadRunTraces, StreamTraceRefusedError } from "../run/stream-trace.js";
|
|
19
19
|
import { verifyRun } from "../verify/verify.js";
|
|
20
|
-
import {} from "../run/bundle.js";
|
|
21
20
|
import { exportRedactedBundle } from "./export-bundle.js";
|
|
22
21
|
import { loadAnalysis } from "../analysis/load.js";
|
|
23
22
|
import { readNotesForSharing } from "../run/note-files.js";
|
package/dist/observer/data.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type ActorEnding } from "../actors/stop-cause.js";
|
|
2
2
|
import type { RunBundle, RunCostSummary, RunEvent, RunSimulation } from "../run/bundle.js";
|
|
3
3
|
import { type RunDisplay } from "../run/display.js";
|
|
4
|
-
import type { RunStream, RunStreamKind } from "../run/streams.js";
|
|
4
|
+
import type { RunStream, RunStreamKind, StoredRunStream } from "../run/streams.js";
|
|
5
5
|
/**
|
|
6
6
|
* The Observer's data. From v2 observer-data.json leaves out each participant's items, and from v3
|
|
7
7
|
* each participant's per-request lists (REQUEST_LISTS in src/run/stream-trace.ts), which no part
|
|
@@ -96,7 +96,7 @@ export interface ObserverData {
|
|
|
96
96
|
artifactRoot: string;
|
|
97
97
|
};
|
|
98
98
|
}
|
|
99
|
-
export interface ObserverStream extends
|
|
99
|
+
export interface ObserverStream extends StoredRunStream {
|
|
100
100
|
/** Only the attached server may grant provider-origin access for a live desktop iframe. */
|
|
101
101
|
embed?: NonNullable<RunStream["embed"]> & {
|
|
102
102
|
runtimeDesktop?: true;
|