humanish 0.109.1 → 0.110.1
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/ARCHITECTURE.md +14 -11
- package/CHANGELOG.md +24 -0
- package/CONTEXT.md +8 -6
- package/README.md +97 -48
- package/dist/actors/computer-use/loop/ending.js +4 -4
- package/dist/actors/local-agent/claude-participant.d.ts +74 -0
- package/dist/actors/local-agent/claude-participant.js +243 -0
- package/dist/actors/local-agent/claude-session.d.ts +16 -3
- package/dist/actors/local-agent/claude-session.js +64 -23
- package/dist/actors/local-agent/cli.d.ts +11 -0
- package/dist/actors/local-agent/cli.js +103 -24
- package/dist/actors/local-agent/readiness.d.ts +2 -2
- package/dist/actors/local-agent/readiness.js +18 -11
- package/dist/cli/commands/analyze.js +21 -5
- package/dist/cli/commands/comms.js +25 -7
- package/dist/cli/commands/observe.js +4 -2
- package/dist/cli/commands/run-command.js +4 -3
- package/dist/cli/commands/run-signals.d.ts +2 -0
- package/dist/cli/commands/run-signals.js +96 -44
- package/dist/cli/commands/runs.d.ts +5 -0
- package/dist/cli/commands/runs.js +89 -67
- package/dist/cli/commands/setup.js +19 -5
- package/dist/cli/commands/study-format.js +29 -2
- package/dist/cli/commands/study-route-computer-use.js +2 -0
- package/dist/cli/commands/study-route-scripted.js +2 -0
- package/dist/cli/commands/study-route-shared-world.js +2 -0
- package/dist/cli/commands/study-route-terminal.js +2 -0
- package/dist/cli/commands/study.js +4 -3
- package/dist/cli/commands/tui.js +4 -3
- package/dist/cli/commands/watch.js +2 -2
- package/dist/cli/doctor.d.ts +7 -2
- package/dist/cli/doctor.js +47 -8
- package/dist/cli/findings.d.ts +103 -0
- package/dist/cli/findings.js +316 -0
- package/dist/cli/first-run-path.d.ts +5 -4
- package/dist/cli/first-run-path.js +14 -7
- package/dist/cli/io.d.ts +17 -3
- package/dist/cli/io.js +22 -4
- package/dist/cli/product-sentence.d.ts +3 -3
- package/dist/cli/product-sentence.js +3 -3
- package/dist/comms/receiving-runtime.js +1 -1
- package/dist/comms/setup.js +1 -1
- package/dist/evidence/redaction.d.ts +7 -3
- package/dist/evidence/redaction.js +68 -24
- package/dist/feedback/export-bundle.js +6 -1
- package/dist/feedback/export.js +3 -0
- package/dist/keys/env-file.d.ts +4 -0
- package/dist/keys/env-file.js +17 -0
- package/dist/keys/key-resolution.d.ts +7 -0
- package/dist/keys/key-resolution.js +10 -8
- package/dist/observer/data.d.ts +14 -1
- package/dist/observer/data.js +19 -9
- package/dist/observer/library.d.ts +4 -0
- package/dist/observer/library.js +18 -15
- package/dist/observer/render.js +8 -2
- package/dist/observer/run-routes.d.ts +2 -0
- package/dist/observer/run-routes.js +43 -34
- package/dist/observer-app.html +3 -3
- package/dist/routes/computer-use/bundle.js +3 -1
- package/dist/routes/computer-use/fanout-records.js +1 -1
- package/dist/routes/computer-use/live-phase.d.ts +1 -0
- package/dist/routes/computer-use/participant-execution.js +3 -1
- package/dist/routes/computer-use/participant-model.js +5 -2
- package/dist/routes/computer-use/plan.js +4 -4
- package/dist/routes/computer-use/preflight.js +2 -2
- package/dist/routes/computer-use/result.js +17 -28
- package/dist/routes/computer-use/single-bundle.js +7 -1
- package/dist/routes/computer-use/types.d.ts +6 -0
- package/dist/routes/scripted/bundle.js +4 -2
- package/dist/routes/scripted/plan.js +2 -2
- package/dist/routes/scripted/result.d.ts +3 -5
- package/dist/routes/scripted/result.js +16 -22
- package/dist/routes/scripted/scenario.js +4 -4
- package/dist/routes/scripted/types.d.ts +1 -0
- package/dist/routes/shared-world/bundle-records.js +6 -1
- package/dist/routes/shared-world/bundle.js +2 -0
- package/dist/routes/shared-world/plan.js +1 -1
- package/dist/routes/shared-world/result.js +22 -22
- package/dist/routes/shared-world/route.js +1 -1
- package/dist/routes/shared-world/types.d.ts +2 -0
- package/dist/routes/terminal/bundle.js +4 -2
- package/dist/routes/terminal/credentials.js +1 -1
- package/dist/routes/terminal/dry-run.js +8 -12
- package/dist/routes/terminal/ledger.d.ts +3 -3
- package/dist/routes/terminal/ledger.js +6 -6
- package/dist/routes/terminal/live-finish.js +23 -17
- package/dist/routes/terminal/plan.js +4 -4
- package/dist/routes/terminal/result.d.ts +6 -7
- package/dist/routes/terminal/result.js +6 -22
- package/dist/routes/terminal/session.js +1 -1
- package/dist/routes/terminal/types.d.ts +3 -0
- package/dist/run/bundle-shape.d.ts +3 -1
- package/dist/run/bundle-shape.js +26 -1
- package/dist/run/bundle.d.ts +30 -0
- package/dist/run/display.d.ts +59 -0
- package/dist/run/display.js +106 -0
- package/dist/run/dry-run.js +11 -7
- package/dist/run/judge.d.ts +3 -2
- package/dist/run/judge.js +10 -0
- package/dist/run/pricing.js +1 -1
- package/dist/run/projection.d.ts +0 -2
- package/dist/run/projection.js +0 -11
- package/dist/run/reclaim-outcomes.d.ts +89 -0
- package/dist/run/reclaim-outcomes.js +213 -0
- package/dist/run/reclaim.d.ts +18 -14
- package/dist/run/reclaim.js +179 -105
- package/dist/run/results.d.ts +11 -18
- package/dist/run/results.js +2 -2
- package/dist/run/run-index.d.ts +11 -1
- package/dist/run/run-index.js +11 -0
- package/dist/run/run.d.ts +34 -18
- package/dist/run/run.js +177 -72
- package/dist/run/sandbox-creates.d.ts +59 -0
- package/dist/run/sandbox-creates.js +138 -0
- package/dist/run/sandbox-ids.d.ts +53 -0
- package/dist/run/sandbox-ids.js +198 -0
- package/dist/run/sandbox-receipts.d.ts +16 -0
- package/dist/run/sandbox-receipts.js +70 -0
- package/dist/run/stats.d.ts +5 -0
- package/dist/run/stats.js +8 -4
- package/dist/run/status.d.ts +6 -9
- package/dist/run/status.js +7 -20
- package/dist/run/stored-runs.d.ts +5 -14
- package/dist/run/stored-runs.js +31 -137
- package/dist/run/streams.d.ts +3 -2
- package/dist/run-study.js +6 -1
- package/dist/study/doctor.d.ts +1 -1
- package/dist/study/doctor.js +7 -4
- package/dist/study/init-templates.js +2 -2
- package/dist/study/init.js +14 -3
- package/dist/study/keys.d.ts +12 -0
- package/dist/study/keys.js +32 -0
- package/dist/study/persona.d.ts +4 -0
- package/dist/study/persona.js +4 -2
- package/dist/study/types.d.ts +1 -1
- package/dist/substrates/e2b/peer-install.d.ts +51 -0
- package/dist/substrates/e2b/peer-install.js +161 -0
- package/dist/substrates/e2b/sandbox.d.ts +34 -6
- package/dist/substrates/e2b/sandbox.js +128 -35
- package/dist/substrates/e2b/sdk.d.ts +33 -0
- package/dist/substrates/e2b/sdk.js +21 -21
- package/dist/substrates/shell.d.ts +2 -0
- package/dist/substrates/shell.js +4 -0
- package/dist/tui-app.js +133 -133
- package/dist/verify/artifacts.d.ts +4 -0
- package/dist/verify/artifacts.js +8 -0
- package/dist/verify/liveness.d.ts +19 -3
- package/dist/verify/liveness.js +53 -28
- package/dist/verify/verify.d.ts +4 -1
- package/dist/verify/verify.js +27 -3
- package/docs/architecture/actor-contract.md +2 -1
- package/docs/architecture/observer.md +22 -7
- package/docs/architecture/restricted-codex-analysis.md +1 -1
- package/docs/architecture/task-protocol-support.md +13 -13
- package/docs/architecture/terminal-product-route.md +1 -1
- package/docs/contracts/run-bundle.md +90 -7
- package/docs/contracts/schemas.md +39 -22
- package/docs/contracts/study-analysis.md +45 -0
- package/docs/contracts/study-costs.md +8 -0
- package/docs/decisions/0001-run-bundle-is-the-source-of-truth.md +3 -0
- package/docs/decisions/0003-contained-paths-and-cleanup-authority.md +9 -3
- package/docs/principles/invariants-and-defaults.md +11 -8
- package/package.json +5 -3
- package/skills/humanish/SKILL.md +18 -4
|
@@ -37,6 +37,10 @@ export declare function rawScreenshotStreamIds(bundle: RunBundle, redactedShapeF
|
|
|
37
37
|
interface RegisteredStreamMedia {
|
|
38
38
|
recordingPaths: Set<string>;
|
|
39
39
|
screenshotPaths: Set<string>;
|
|
40
|
+
/** The run's raw sandbox ids, from its receipts; any other file holding one is listed below. */
|
|
41
|
+
sandboxIds?: readonly string[];
|
|
42
|
+
/** Filled by the scan: files other than the receipts that name a raw sandbox id. */
|
|
43
|
+
sandboxIdFiles?: string[];
|
|
40
44
|
}
|
|
41
45
|
/**
|
|
42
46
|
* Scans every run file for secret and path patterns and returns the findings. A file that is not
|
package/dist/verify/artifacts.js
CHANGED
|
@@ -7,6 +7,8 @@ import { readPlainText } from "../evidence/plain-text.js";
|
|
|
7
7
|
import { containsSensitive } from "../evidence/redaction.js";
|
|
8
8
|
import { isLocalEvidenceArtifactPath, isRiskyPublicArtifactPath, validatePreparedRunArtifactPaths, } from "../run/paths.js";
|
|
9
9
|
import { openContainedRegularFile } from "../run/contained-output.js";
|
|
10
|
+
import { holdsKeyedSandboxId } from "../run/sandbox-ids.js";
|
|
11
|
+
import { SANDBOX_RECEIPTS_ARTIFACT } from "../run/sandbox-receipts.js";
|
|
10
12
|
import { readSafeRunArtifactBytes, readSafeRunArtifactJson } from "../run/locate.js";
|
|
11
13
|
import { isRecord } from "../run/type-guards.js";
|
|
12
14
|
import { TERMINAL_EVENTS_ARTIFACT } from "../run/terminal-contract.js";
|
|
@@ -348,6 +350,12 @@ async function scanRunPublicSafetyDirectory(runPaths, relativeDirectory, finding
|
|
|
348
350
|
unscanned.push(relativePath);
|
|
349
351
|
continue;
|
|
350
352
|
}
|
|
353
|
+
// A receipt's id in any other file, or a raw id at a sandbox-id key, which an older run or
|
|
354
|
+
// export copy can hold with no receipt to name it.
|
|
355
|
+
if (relativePath !== SANDBOX_RECEIPTS_ARTIFACT &&
|
|
356
|
+
(media.sandboxIds?.some((id) => decoded.text.includes(id)) ||
|
|
357
|
+
holdsKeyedSandboxId(relativePath, decoded.text)))
|
|
358
|
+
media.sandboxIdFiles?.push(relativePath);
|
|
351
359
|
// serve renders observer/index.html from run.json and export regenerates it, so the on-disk
|
|
352
360
|
// copy reaches neither. It embeds the Observer's own base64 fonts and scripts.
|
|
353
361
|
const scan = scanEncodedTextCached(decoded.text, {
|
|
@@ -1,7 +1,23 @@
|
|
|
1
1
|
import type { RunBundle } from "../run/bundle.js";
|
|
2
2
|
import type { PreparedRunArtifactPaths } from "../run/paths.js";
|
|
3
|
+
/** What verify can say about a run that is not finished. */
|
|
4
|
+
export interface UnfinishedRun {
|
|
5
|
+
liveness: "running" | "interrupted";
|
|
6
|
+
/**
|
|
7
|
+
* clean: the run's reclaim receipt records every sandbox gone and a finished search of E2B by
|
|
8
|
+
* the run's tags. unconfirmed: it records a sandbox not confirmed gone, or the run journaled one
|
|
9
|
+
* it does not cover. unknown: there is no reclaim receipt, or it records no finished tag search
|
|
10
|
+
* (written by humanish 0.110.0 or earlier, or by a reclaim that did not finish), so a sandbox whose
|
|
11
|
+
* id never reached a receipt could still run.
|
|
12
|
+
*/
|
|
13
|
+
sandboxes: "clean" | "unconfirmed" | "unknown";
|
|
14
|
+
}
|
|
3
15
|
/**
|
|
4
|
-
* One warning when the run is not finished, by the run index's
|
|
5
|
-
* has a usable one, else a participant still `running` in its
|
|
16
|
+
* One warning, and the unfinished-run facts, when the run is not finished, by the run index's
|
|
17
|
+
* rule: its status record when it has a usable one, else a participant still `running` in its
|
|
18
|
+
* bundle's `simulations[]`. Nothing for a finished run.
|
|
6
19
|
*/
|
|
7
|
-
export declare function
|
|
20
|
+
export declare function runNotFinished(runPaths: PreparedRunArtifactPaths, bundle: RunBundle, nowMs?: number): Promise<{
|
|
21
|
+
warnings: string[];
|
|
22
|
+
unfinished?: UnfinishedRun;
|
|
23
|
+
}>;
|
package/dist/verify/liveness.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
// `humanish verify`'s RUN_NOT_FINISHED warning
|
|
2
|
-
// and a run killed mid-way leaves an
|
|
3
|
-
//
|
|
1
|
+
// `humanish verify`'s RUN_NOT_FINISHED warning and the unfinished-run facts its one-line output
|
|
2
|
+
// leads with. Verify checks the integrity of what a run wrote, and a run killed mid-way leaves an
|
|
3
|
+
// in-progress bundle that passes those checks. The warning says what verify saw on disk, so
|
|
4
|
+
// `ok: true` is not read as "the run finished" or "its sandboxes stopped". It never flips `ok`.
|
|
5
|
+
import { sandboxIdDigest } from "../evidence/redaction.js";
|
|
4
6
|
import path from "node:path";
|
|
5
7
|
import { readRunJsonIfExists, readRunTextIfExists } from "../run/locate.js";
|
|
6
8
|
import { isRecord } from "../run/type-guards.js";
|
|
@@ -8,19 +10,21 @@ import { RECLAIM_RECEIPT_ARTIFACT } from "../run/reclaim.js";
|
|
|
8
10
|
import { runLiveness } from "../run/run-index.js";
|
|
9
11
|
import { parseSandboxReceipts, SANDBOX_RECEIPTS_ARTIFACT } from "../run/sandbox-receipts.js";
|
|
10
12
|
import { RUN_STATUS_FILE } from "../run/status.js";
|
|
13
|
+
import { plural } from "../run/text.js";
|
|
11
14
|
/** The stable code every RUN_NOT_FINISHED warning starts with. */
|
|
12
15
|
const RUN_NOT_FINISHED = "RUN_NOT_FINISHED";
|
|
13
16
|
const GONE_STATES = new Set(["killed", "already-gone"]);
|
|
14
17
|
/**
|
|
15
|
-
* One warning when the run is not finished, by the run index's
|
|
16
|
-
* has a usable one, else a participant still `running` in its
|
|
18
|
+
* One warning, and the unfinished-run facts, when the run is not finished, by the run index's
|
|
19
|
+
* rule: its status record when it has a usable one, else a participant still `running` in its
|
|
20
|
+
* bundle's `simulations[]`. Nothing for a finished run.
|
|
17
21
|
*/
|
|
18
|
-
export async function
|
|
22
|
+
export async function runNotFinished(runPaths, bundle, nowMs = Date.now()) {
|
|
19
23
|
const runId = path.basename(runPaths.absoluteRunRoot);
|
|
20
24
|
const status = await readRunJsonIfExists(runPaths, RUN_STATUS_FILE);
|
|
21
25
|
const { liveness, record } = runLiveness(runId, status, bundle, nowMs);
|
|
22
26
|
if (liveness === "finished")
|
|
23
|
-
return [];
|
|
27
|
+
return { warnings: [] };
|
|
24
28
|
const seen = [];
|
|
25
29
|
if (record === undefined) {
|
|
26
30
|
const running = bundle.simulations.filter((simulation) => simulation.status === "running");
|
|
@@ -38,38 +42,59 @@ export async function runNotFinishedWarnings(runPaths, bundle, nowMs = Date.now(
|
|
|
38
42
|
const runningStreams = bundle.streams.filter((stream) => stream.status === "running").length;
|
|
39
43
|
seen.push(`${runningStreams} of ${bundle.streams.length} streams are still running`);
|
|
40
44
|
const reclaim = await reclaimState(runPaths, runId);
|
|
41
|
-
return
|
|
42
|
-
|
|
43
|
-
|
|
45
|
+
return {
|
|
46
|
+
warnings: [
|
|
47
|
+
`${RUN_NOT_FINISHED}: ${seen.join("; ")}. ${reclaim.text} Verify ok covers the integrity of what was written; it does not mean the run finished.`,
|
|
48
|
+
],
|
|
49
|
+
unfinished: { liveness, sandboxes: reclaim.sandboxes },
|
|
50
|
+
};
|
|
44
51
|
}
|
|
45
|
-
/** What the reclaim receipt says about
|
|
52
|
+
/** What the reclaim receipt says about this run's sandboxes, and what to run. */
|
|
46
53
|
async function reclaimState(runPaths, runId) {
|
|
47
54
|
const journal = await readRunTextIfExists(runPaths, SANDBOX_RECEIPTS_ARTIFACT);
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
// Matched by digest: a reclaim receipt names each sandbox by digest, and one written before it
|
|
56
|
+
// did holds the raw id, which is digested here.
|
|
57
|
+
const journaled = new Set((journal === null ? [] : parseSandboxReceipts(journal)).map((receipt) => sandboxIdDigest(receipt.sandboxId)));
|
|
58
|
+
const command = `\`humanish reclaim --run ${runId}\``;
|
|
59
|
+
const receipt = reclaimReceipt(await readRunJsonIfExists(runPaths, RECLAIM_RECEIPT_ARTIFACT), runId);
|
|
60
|
+
if (receipt === undefined)
|
|
61
|
+
return {
|
|
62
|
+
sandboxes: "unknown",
|
|
63
|
+
text: `Sandboxes unknown: it has no ${RECLAIM_RECEIPT_ARTIFACT} and journaled ${plural(journaled.size, "sandbox", "sandboxes")}; ${command} stops those and searches E2B by this run's tags for any whose id never reached a receipt.`,
|
|
64
|
+
};
|
|
65
|
+
const { outcomes } = receipt;
|
|
66
|
+
const gone = outcomes.filter((outcome) => GONE_STATES.has(outcome.state)).length;
|
|
67
|
+
const reclaimed = new Set(outcomes.map((outcome) => outcome.digest));
|
|
68
|
+
const missing = [...journaled].filter((id) => !reclaimed.has(id)).length;
|
|
69
|
+
const recorded = `${RECLAIM_RECEIPT_ARTIFACT} records ${gone} of ${outcomes.length} sandboxes gone${missing === 0 ? "" : `, and ${missing} journaled sandboxes are not in it`}`;
|
|
70
|
+
if (gone < outcomes.length || missing > 0)
|
|
71
|
+
return {
|
|
72
|
+
sandboxes: "unconfirmed",
|
|
73
|
+
text: `Sandboxes unconfirmed: ${recorded}; ${command} retries.`,
|
|
74
|
+
};
|
|
75
|
+
if (receipt.state === "clean")
|
|
76
|
+
return {
|
|
77
|
+
sandboxes: "clean",
|
|
78
|
+
text: `Sandboxes clean: ${recorded}, and E2B listed none still tagged with this run.`,
|
|
79
|
+
};
|
|
80
|
+
return {
|
|
81
|
+
sandboxes: "unknown",
|
|
82
|
+
text: `Sandboxes unknown: ${recorded}, but it records no finished search of E2B by this run's tags; ${command} searches.`,
|
|
83
|
+
};
|
|
60
84
|
}
|
|
61
|
-
/**
|
|
62
|
-
function
|
|
85
|
+
/** A reclaim receipt written for this run, or undefined when there is none. */
|
|
86
|
+
function reclaimReceipt(receipt, runId) {
|
|
63
87
|
if (!isRecord(receipt) || receipt.runId !== runId || !Array.isArray(receipt.outcomes))
|
|
64
88
|
return undefined;
|
|
65
89
|
const outcomes = [];
|
|
66
90
|
for (const outcome of receipt.outcomes) {
|
|
67
91
|
if (!isRecord(outcome))
|
|
68
92
|
return undefined;
|
|
69
|
-
const { sandboxId, state } = outcome;
|
|
93
|
+
const { sandboxId, sandboxIdDigest: recorded, state } = outcome;
|
|
70
94
|
if (typeof sandboxId !== "string" || typeof state !== "string")
|
|
71
95
|
return undefined;
|
|
72
|
-
|
|
96
|
+
const digest = typeof recorded === "string" ? recorded : sandboxIdDigest(sandboxId);
|
|
97
|
+
outcomes.push({ digest, state });
|
|
73
98
|
}
|
|
74
|
-
return outcomes;
|
|
99
|
+
return typeof receipt.state === "string" ? { state: receipt.state, outcomes } : { outcomes };
|
|
75
100
|
}
|
package/dist/verify/verify.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type PreparedRunArtifactPaths } from "../run/paths.js";
|
|
2
|
+
import { type UnfinishedRun } from "./liveness.js";
|
|
2
3
|
export declare const VERIFY_SCHEMA = "humanish.verify-result.v1";
|
|
3
4
|
export interface VerifyResult {
|
|
4
5
|
schema: typeof VERIFY_SCHEMA;
|
|
@@ -16,11 +17,13 @@ export interface VerifyResult {
|
|
|
16
17
|
shareSafety: {
|
|
17
18
|
status: "share_ready" | "local_only" | "blocked";
|
|
18
19
|
reasons: Array<{
|
|
19
|
-
code: "VERIFY_FAILED" | "PUBLIC_SAFETY_FINDINGS" | "ANALYSIS_UNVERIFIED" | "RAW_SCREENSHOTS" | "CONTINUOUS_MEDIA" | "REAL_COMMUNICATIONS" | "UNSCANNED_ARTIFACT";
|
|
20
|
+
code: "VERIFY_FAILED" | "PUBLIC_SAFETY_FINDINGS" | "ANALYSIS_UNVERIFIED" | "RAW_SCREENSHOTS" | "RAW_SANDBOX_ID" | "CONTINUOUS_MEDIA" | "REAL_COMMUNICATIONS" | "UNSCANNED_ARTIFACT";
|
|
20
21
|
message: string;
|
|
21
22
|
}>;
|
|
22
23
|
};
|
|
23
24
|
warnings: string[];
|
|
25
|
+
/** Present when the run is not finished: its liveness and what is known of its sandboxes. */
|
|
26
|
+
unfinished?: UnfinishedRun;
|
|
24
27
|
error?: {
|
|
25
28
|
code: "HUMANISH_RUN_NOT_FOUND" | "HUMANISH_INVALID_RUN_BUNDLE";
|
|
26
29
|
message: string;
|
package/dist/verify/verify.js
CHANGED
|
@@ -2,7 +2,8 @@ import path from "node:path";
|
|
|
2
2
|
import { listAnalysisExecutions } from "../analysis/store-executions.js";
|
|
3
3
|
import { loadAnalysis } from "../analysis/load.js";
|
|
4
4
|
import { analysisSharingProblems } from "../analysis/sharing.js";
|
|
5
|
-
import { containsSensitive } from "../evidence/redaction.js";
|
|
5
|
+
import { containsSensitive, REDACTED_SANDBOX_ID } from "../evidence/redaction.js";
|
|
6
|
+
import { keyedSandboxIds, readRunSandboxIds } from "../run/sandbox-ids.js";
|
|
6
7
|
import { validatePreparedRunArtifactPaths } from "../run/paths.js";
|
|
7
8
|
import { RUN_BUNDLE_FILE, RUN_BUNDLE_SCHEMA } from "../run/bundle.js";
|
|
8
9
|
import { isCleanupResult, isRunBundle } from "../run/bundle-shape.js";
|
|
@@ -12,7 +13,7 @@ import { runNotFoundMessage } from "../run/run-not-found.js";
|
|
|
12
13
|
import { actorVerdictConsistencyFindings, noEngagementActorFindings, validateCodexAppServerEvidence, validateTerminalProductEvidence, } from "./actor.js";
|
|
13
14
|
import { invalidRunEvidenceReferences, MAX_REPORTED_FINDINGS, missingLocalEvidenceArtifacts, rawScreenshotPostureWarnings, rawScreenshotStreamIds, redactedShapeFramePaths, scanRunPublicSafetyArtifacts, streamScreenshotPaths, } from "./artifacts.js";
|
|
14
15
|
import { flatContextWarnings } from "./context-growth.js";
|
|
15
|
-
import {
|
|
16
|
+
import { runNotFinished } from "./liveness.js";
|
|
16
17
|
import { costAndReceiptFindings } from "./costs.js";
|
|
17
18
|
import { rerunLineageFindings } from "./rerun.js";
|
|
18
19
|
import { sharedWorldEvidenceFindings } from "./shared-world.js";
|
|
@@ -83,11 +84,17 @@ export async function verifyResolvedRun(cwd, runInput, runPaths) {
|
|
|
83
84
|
}));
|
|
84
85
|
const derivedPublicSafetyFindings = [];
|
|
85
86
|
const unscannedArtifacts = [];
|
|
87
|
+
const sandboxIdFiles = [];
|
|
86
88
|
const publicSafetyFindings = await scanRunPublicSafetyArtifacts(runPaths, derivedPublicSafetyFindings, {
|
|
87
89
|
recordingPaths: new Set(isRunBundle(bundle)
|
|
88
90
|
? bundle.streams.flatMap((stream) => (stream.recording ? [stream.recording.path] : []))
|
|
89
91
|
: []),
|
|
90
92
|
screenshotPaths: isRunBundle(bundle) ? streamScreenshotPaths(bundle) : new Set(),
|
|
93
|
+
// A run from before 0.110 with no receipts still names its ids in run.json.
|
|
94
|
+
sandboxIds: [
|
|
95
|
+
...new Set([...(await readRunSandboxIds(runPaths)), ...keyedSandboxIds(bundle)]),
|
|
96
|
+
],
|
|
97
|
+
sandboxIdFiles,
|
|
91
98
|
}, unscannedArtifacts);
|
|
92
99
|
// JSON escapes can hide a sensitive value from the byte scan while the
|
|
93
100
|
// decoded recording exposes it to Observer, feedback, or analysis input.
|
|
@@ -130,13 +137,16 @@ export async function verifyResolvedRun(cwd, runInput, runPaths) {
|
|
|
130
137
|
message: "Derived analysis contains sensitive text or unsafe artifact paths; sharing is blocked, original recording remains independently verifiable.",
|
|
131
138
|
});
|
|
132
139
|
const ok = checks.every((check) => check.ok);
|
|
140
|
+
const notFinished = isRunBundle(bundle)
|
|
141
|
+
? await runNotFinished(runPaths, bundle)
|
|
142
|
+
: { warnings: [], unfinished: undefined };
|
|
133
143
|
const warnings = isRunBundle(bundle)
|
|
134
144
|
? [
|
|
135
145
|
...rawScreenshotPostureWarnings(bundle, redactedShapeFrames),
|
|
136
146
|
...undeclaredSubjectStateWarnings(bundle),
|
|
137
147
|
...desktopGeometryWarnings(bundle),
|
|
138
148
|
...flatContextWarnings(bundle),
|
|
139
|
-
...
|
|
149
|
+
...notFinished.warnings,
|
|
140
150
|
]
|
|
141
151
|
: [];
|
|
142
152
|
const shareSafety = isRunBundle(bundle)
|
|
@@ -146,6 +156,7 @@ export async function verifyResolvedRun(cwd, runInput, runPaths) {
|
|
|
146
156
|
publicSafetyFindings,
|
|
147
157
|
unscannedArtifacts,
|
|
148
158
|
redactedShapeFrames,
|
|
159
|
+
sandboxIdFiles,
|
|
149
160
|
})
|
|
150
161
|
: {
|
|
151
162
|
status: "blocked",
|
|
@@ -167,6 +178,7 @@ export async function verifyResolvedRun(cwd, runInput, runPaths) {
|
|
|
167
178
|
checks,
|
|
168
179
|
shareSafety,
|
|
169
180
|
warnings,
|
|
181
|
+
...(notFinished.unfinished === undefined ? {} : { unfinished: notFinished.unfinished }),
|
|
170
182
|
...(ok
|
|
171
183
|
? {}
|
|
172
184
|
: {
|
|
@@ -366,6 +378,18 @@ function buildShareSafety(args) {
|
|
|
366
378
|
message: `The public-safety scan cannot read ${plural(paths.length, "file")} as text that ${paths.length === 1 ? "is not a stream screenshot under screenshots/ or a registered recording" : "are not stream screenshots under screenshots/ or registered recordings"}: ${shown}${more > 0 ? ` and ${more} more` : ""}. Review them before sharing.`,
|
|
367
379
|
});
|
|
368
380
|
}
|
|
381
|
+
// Runs from 0.110 keep raw sandbox ids only in sandbox-receipts.ndjson. An earlier run records
|
|
382
|
+
// them in run.json, and any file that names one is not share-ready as it is.
|
|
383
|
+
const rawResources = (args.bundle.providerResources ?? []).some((resource) => resource.id !== REDACTED_SANDBOX_ID);
|
|
384
|
+
if (rawResources || args.sandboxIdFiles.length > 0) {
|
|
385
|
+
const files = [
|
|
386
|
+
...new Set([...(rawResources ? [RUN_BUNDLE_FILE] : []), ...args.sandboxIdFiles]),
|
|
387
|
+
];
|
|
388
|
+
reasons.push({
|
|
389
|
+
code: "RAW_SANDBOX_ID",
|
|
390
|
+
message: `Raw sandbox ids appear in ${files.sort().join(", ")}. Runs from 0.110 keep them only in sandbox-receipts.ndjson; \`humanish export --format bundle --redact-screenshots\` writes a copy without them.`,
|
|
391
|
+
});
|
|
392
|
+
}
|
|
369
393
|
const rawStreamIds = rawScreenshotStreamIds(args.bundle, args.redactedShapeFrames);
|
|
370
394
|
if (rawStreamIds.length > 0) {
|
|
371
395
|
reasons.push({
|
|
@@ -444,7 +444,8 @@ traits: { patience, skill, accessibilityNeeds? }, constraints[], sourceDigest }`
|
|
|
444
444
|
receives them as thread/start `baseInstructions` (`threadStartParams` in
|
|
445
445
|
`src/actors/codex/restricted-launch.ts`), on an ephemeral read-only thread whose only
|
|
446
446
|
dynamic tool is `humanish_ui`. A local-agent Claude Code runs one `claude -p`
|
|
447
|
-
stream-json session
|
|
447
|
+
stream-json session restricted to Read in its folder (`claudeParticipantFlags` in
|
|
448
|
+
`src/actors/local-agent/claude-participant.ts`); the instructions open its first user
|
|
448
449
|
message (`promptFor` in `src/actors/local-agent/cli.ts`), and later turns rely on the
|
|
449
450
|
session's memory. `HUMANISH_LOCAL_AGENT_ONE_SHOT` instead spawns one `claude -p` per
|
|
450
451
|
turn, which resends the instructions every turn.
|
|
@@ -93,10 +93,23 @@ Served data may also include `runtime` with `state`, `observedAt`, and
|
|
|
93
93
|
`source: "local-run-status"`. This is a current observation of a contained,
|
|
94
94
|
matching `status.json`; it never changes the run's recorded verdict or participant
|
|
95
95
|
outcomes. A fresh heartbeat means running, an explicitly finalized record means
|
|
96
|
-
finished,
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
and export do not create this
|
|
96
|
+
finished, a record the CLI's signal handler wrote means interrupted, and stale or
|
|
97
|
+
invalid timing means unknown. Missing, malformed, or mismatched records omit the
|
|
98
|
+
observation. A stale heartbeat alone does not prove interruption, and stored PIDs
|
|
99
|
+
are neither probed nor returned. Static rendering and export do not create this
|
|
100
|
+
served-only observation.
|
|
101
|
+
|
|
102
|
+
`run.display` says whether the run passed: `{ state, label, tone, reason? }` from
|
|
103
|
+
`runDisplay` (`src/run/display.ts`), the function every surface that shows a run's
|
|
104
|
+
result calls. `state` is `passed` only when `run.status` (the verdict) is `pass` and
|
|
105
|
+
`run.json`'s `outcome` does not record the run as failed. A run whose execution
|
|
106
|
+
failed under a pass verdict is `failed`, with the first failure in `reason`.
|
|
107
|
+
`blocked`, `timed_out`, `no_verdict`, `interrupted`, `running`, `dry_run` and
|
|
108
|
+
`unknown` are the other states. For a `run.json` without an `outcome`, the served
|
|
109
|
+
Observer and `observe` read `status.json` for liveness and, on a run recorded
|
|
110
|
+
before `run.json` carried an outcome, for its ok. The sidebar's dot is the pass
|
|
111
|
+
accent only for `passed`. observer-data.json written before `display` existed
|
|
112
|
+
keeps the verdict reading.
|
|
100
113
|
|
|
101
114
|
Watch is deliberately distinct from `humanish observe --all`. Watch serves one
|
|
102
115
|
attached run, and the process that created it may inject runtime stream URLs
|
|
@@ -167,9 +180,11 @@ origin-dependent scripts and modules in those artifacts may require independent
|
|
|
167
180
|
hosting. See the [CSP sandbox standard](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/sandbox).
|
|
168
181
|
|
|
169
182
|
History entries may include `runtimeState` from the same contained local status
|
|
170
|
-
read as the Observer
|
|
171
|
-
|
|
172
|
-
|
|
183
|
+
read as the Observer, and `display` from the run's Observer data. Their existing
|
|
184
|
+
`status` remains the recorded verdict. The library's pip color and the sidebar's
|
|
185
|
+
label read `display`. Running filters should use runtime state when present,
|
|
186
|
+
preserving the difference between an active study and its provisional evidence
|
|
187
|
+
outcome.
|
|
173
188
|
|
|
174
189
|
## UI Shape
|
|
175
190
|
|
|
@@ -176,7 +176,7 @@ the following, on Linux x64:
|
|
|
176
176
|
runs this check; see "The qualifier" below.
|
|
177
177
|
3. One hosted participant study with the candidate first on `PATH` (for example
|
|
178
178
|
`PATH=<evidence>/npm-<version>/node_modules/.bin:$PATH pnpm humanish run <study>
|
|
179
|
-
--
|
|
179
|
+
--dotenv <file with E2B_API_KEY only>`) reached its goal in the final capture, with a
|
|
180
180
|
verified request receipt, `humanish verify --run <id>` passing and the automatic Codex
|
|
181
181
|
analysis recording the release. Keep `OPENAI_API_KEY` out of that environment so the
|
|
182
182
|
participant stays on ChatGPT-account billing.
|
|
@@ -5,20 +5,20 @@ participant; hidden success criteria go only to the observation tracker. A route
|
|
|
5
5
|
that cannot carry both halves refuses the declaration before execution. Removing
|
|
6
6
|
`tasks` is an explicit choice to run a mission-only study, not an automatic fallback.
|
|
7
7
|
|
|
8
|
-
| Execution path | Task support | Mechanism
|
|
9
|
-
| ----------------------------------------------------- | ------------ |
|
|
10
|
-
| CUA, app-url, one participant or fan-out | Supported | The participant prompt composer renders goals; the CUA loop tracks observations
|
|
11
|
-
| CUA, provisioned clone or local-tree, separate worlds | Supported | Same prompt composer and loop
|
|
12
|
-
| CUA, local-app with caller executor/provider | Supported | Same loop; the caller supplies observations
|
|
13
|
-
| CUA, desktop-cli | Supported | Same prompt and tracker; unavailable criterion inputs remain unmeasured
|
|
14
|
-
| Shared-world, provisioned or external-public | Rejected | Actor specs omit the protocol before CUA session dispatch
|
|
15
|
-
| Terminal-product | Rejected | Terminal prompt and result contract do not implement tasks
|
|
16
|
-
| Scripted-browser, local or provisioned | Rejected | Scenario steps drive the participant; no task protocol is consumed
|
|
17
|
-
|
|
|
18
|
-
| Any second or later `actors[]` entry | Rejected | Current runners consume only the first actor; declare every participant under the first actor |
|
|
8
|
+
| Execution path | Task support | Mechanism |
|
|
9
|
+
| ----------------------------------------------------- | ------------ | ------------------------------------------------------------------------------- |
|
|
10
|
+
| CUA, app-url, one participant or fan-out | Supported | The participant prompt composer renders goals; the CUA loop tracks observations |
|
|
11
|
+
| CUA, provisioned clone or local-tree, separate worlds | Supported | Same prompt composer and loop |
|
|
12
|
+
| CUA, local-app with caller executor/provider | Supported | Same loop; the caller supplies observations |
|
|
13
|
+
| CUA, desktop-cli | Supported | Same prompt and tracker; unavailable criterion inputs remain unmeasured |
|
|
14
|
+
| Shared-world, provisioned or external-public | Rejected | Actor specs omit the protocol before CUA session dispatch |
|
|
15
|
+
| Terminal-product | Rejected | Terminal prompt and result contract do not implement tasks |
|
|
16
|
+
| Scripted-browser, local or provisioned | Rejected | Scenario steps drive the participant; no task protocol is consumed |
|
|
17
|
+
| Preview (`this-repo`) | Rejected | Study dispatch does not pass task declarations to the dry-run engine |
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
A study has one `actor`, and its participants go in `participants`, so every participant
|
|
20
|
+
gets the same tasks. Both registered CUA actors (`openai-computer-use` and `local-agent`) share
|
|
21
|
+
the CUA session loop. Their task support does not depend on which provider chooses actions.
|
|
22
22
|
Custom session hooks remain caller-owned implementations of that same contract;
|
|
23
23
|
this preflight does not certify arbitrary hook behavior.
|
|
24
24
|
|
|
@@ -33,7 +33,7 @@ fail-closed cross-validation, and forward-declared warnings.
|
|
|
33
33
|
| `execution.runtimeAuth` | `openai-env` (default) or opt-in `openai-egress`; names-only durable evidence |
|
|
34
34
|
| `execution.runtime.version` | Optional exact `@openai/codex` version; observed before keyed execution |
|
|
35
35
|
| `actor.model` / `reasoningEffort` | Passed to Codex as `--model` (default `gpt-5.6-sol`) and `-c model_reasoning_effort`; declarations, not observed provider identity |
|
|
36
|
-
| `caps` | `{ maxUsd, maxJobs, maxMinutes }`: the blast-radius budget; `maxUsd > 0` is refused
|
|
36
|
+
| `caps` | `{ maxUsd, maxJobs, maxMinutes }`: the blast-radius budget; `maxUsd > 0` is refused: no adopter cost source exists until issue 347 lands |
|
|
37
37
|
| `policies` | `allowPrivateRepoAccess` / `allowProviderCredentials` / `allowPaymentCredentials` / `allowGitHubMutation`, all default false |
|
|
38
38
|
| `actor.type` | `codex-exec`: a registered terminal actor (`keyPlacement: in-sandbox-command-scoped`) |
|
|
39
39
|
| route | `terminal` → `runTerminalProductStudy` ([`src/routes/terminal/route.ts`](https://github.com/danielgwilson/humanish/blob/main/src/routes/terminal/route.ts)) |
|
|
@@ -70,6 +70,15 @@ artifacts:
|
|
|
70
70
|
review:
|
|
71
71
|
schema: humanish.review.v1
|
|
72
72
|
verdict: "contract_proof_only|pass|fail|blocked|timed_out"
|
|
73
|
+
outcome: # optional, additive: how the run ended (Run Outcome below)
|
|
74
|
+
state: "finished"
|
|
75
|
+
ok: false
|
|
76
|
+
execution:
|
|
77
|
+
succeeded: false
|
|
78
|
+
failures:
|
|
79
|
+
- kind: "harness|provider-cleanup|provider-policy|sandbox-cleanup|evidence|cap|run"
|
|
80
|
+
message: "<public-safe message>"
|
|
81
|
+
warnings: [] # omitted when empty
|
|
73
82
|
redaction:
|
|
74
83
|
status: "passed"
|
|
75
84
|
notes: "<public-safe note>"
|
|
@@ -100,6 +109,59 @@ Persisted `run.json` files must not contain absolute local target paths. Runtime
|
|
|
100
109
|
commands may return the caller's working directory in process-local JSON
|
|
101
110
|
responses, but durable run bundles use the public-safe `[target-cwd]` marker.
|
|
102
111
|
|
|
112
|
+
## Run Outcome
|
|
113
|
+
|
|
114
|
+
`outcome` records how the run ended as an execution. `review.verdict` is what the
|
|
115
|
+
participants experienced; `outcome.ok` is the route result's `ok`, which
|
|
116
|
+
`humanish run --json` prints as `runOk`. The two differ: a terminal run whose agent passed but whose sandbox
|
|
117
|
+
teardown is unproven has verdict `pass` and `ok: false`.
|
|
118
|
+
|
|
119
|
+
```yaml
|
|
120
|
+
# Run.finish published the final bundle.
|
|
121
|
+
outcome:
|
|
122
|
+
state: finished
|
|
123
|
+
ok: true
|
|
124
|
+
execution:
|
|
125
|
+
succeeded: true
|
|
126
|
+
failures: []
|
|
127
|
+
warnings: # omitted when empty
|
|
128
|
+
- kind: sandbox-cleanup
|
|
129
|
+
message: "lane-01: Sandbox release is unconfirmed. Reclaim it by recorded id with `humanish reclaim --run <id>`."
|
|
130
|
+
---
|
|
131
|
+
# The CLI's signal handler stopped the run before it finished.
|
|
132
|
+
outcome:
|
|
133
|
+
state: interrupted
|
|
134
|
+
ok: false
|
|
135
|
+
signal: SIGINT # or SIGTERM, SIGHUP
|
|
136
|
+
at: "<ISO timestamp>"
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
`Run.finish` (`src/run/run.ts`) writes `finished` with the route's `ok` and its
|
|
140
|
+
execution outcome (`judgeExecution` in `src/run/judge.ts`). Each route's policy in
|
|
141
|
+
`OUTCOME_POLICIES` decides which failure kinds fail the run and which only warn.
|
|
142
|
+
An Observer that does not render is an `evidence` failure known only after
|
|
143
|
+
`run.json` is on disk: `FinishedRun.renderObserver` adds it to `outcome` and to
|
|
144
|
+
`status.json` before the route returns, and no Observer page exists that could
|
|
145
|
+
show the run without it. Messages pass the same redaction and sandbox id scrub
|
|
146
|
+
as the rest of the bundle.
|
|
147
|
+
|
|
148
|
+
When a signal stops a live run, the handler writes `interrupted` into the last
|
|
149
|
+
flushed `run.json`, and every later write of that run keeps it. A run stopped
|
|
150
|
+
before its first flush has no `run.json`; its `status.json` is the record.
|
|
151
|
+
|
|
152
|
+
`outcome` is absent while a run is in progress, when its process died without
|
|
153
|
+
writing it, and on runs recorded before the field existed. For those, readers
|
|
154
|
+
take liveness from `status.json` and, for an older finished run, its `ok` and
|
|
155
|
+
`execution` from `status.json`'s `outcome`. That record is the only copy of an
|
|
156
|
+
older run's `ok`; for a run with `outcome` in `run.json`, `status.json` copies it.
|
|
157
|
+
|
|
158
|
+
Every surface that shows whether a run passed calls `runDisplay`
|
|
159
|
+
(`src/run/display.ts`) with the run's liveness, verdict and `ok`: the Observer
|
|
160
|
+
(`observer-data.json` `run.display`), its run library, the served Observer,
|
|
161
|
+
`humanish runs`, `humanish review`, `humanish stats`, the TUI and `review.md`'s
|
|
162
|
+
`outcome` line. A run
|
|
163
|
+
shows as `passed` only when its verdict is `pass` and its `ok` is not false.
|
|
164
|
+
|
|
103
165
|
## Recorded session stop causes
|
|
104
166
|
|
|
105
167
|
An actor trace may include `stopCause` alongside its unchanged `status`,
|
|
@@ -525,6 +587,10 @@ bundle is safe to promote into a public issue. Public promotion should branch on
|
|
|
525
587
|
|
|
526
588
|
- `share_ready`: feedback draft commands may render public issue payloads;
|
|
527
589
|
- `local_only`: keep the run local; only supported redaction-only cases can produce a shareable derivative;
|
|
590
|
+
`RAW_SANDBOX_ID` is one: a file other than `sandbox-receipts.ndjson` names one of the run's raw
|
|
591
|
+
sandbox ids, which verify reads from the receipts and from any raw `providerResources[].id` in
|
|
592
|
+
`run.json`, or a JSON file holds a raw id at a sandbox-id key. Runs from 0.110 keep raw ids only in the receipts, so it fires on runs recorded
|
|
593
|
+
before 0.110, whose `run.json` holds them, until they are exported;
|
|
528
594
|
- `blocked`: fix the verification or public-safety failure first.
|
|
529
595
|
|
|
530
596
|
`ok: true` also does not mean the run finished. A run killed mid-way leaves an
|
|
@@ -539,10 +605,19 @@ writing; an older one reads as interrupted. The warning names what verify saw:
|
|
|
539
605
|
- the record's state and its last `updatedAt`, or the running `simulations[]` entries when
|
|
540
606
|
there is no usable record;
|
|
541
607
|
- how many streams are still `running`;
|
|
542
|
-
- what `reclaim-receipt.json` records: how many of the run's
|
|
543
|
-
(killed or already gone), and how many journaled sandboxes it does
|
|
544
|
-
|
|
545
|
-
|
|
608
|
+
- the sandbox state, and what `reclaim-receipt.json` records: how many of the run's
|
|
609
|
+
sandboxes are gone (killed or already gone), and how many journaled sandboxes it does
|
|
610
|
+
not cover. It matches its outcomes to the receipts by `sandboxIdDigest`; a reclaim
|
|
611
|
+
receipt from before 0.110 names raw ids, which are digested to match;
|
|
612
|
+
- with no reclaim receipt, the command that stops the journaled sandboxes and searches
|
|
613
|
+
E2B by the run's owner tags: `humanish reclaim --run <id>`.
|
|
614
|
+
|
|
615
|
+
The result also carries `unfinished: { liveness, sandboxes }`, and the one-line output
|
|
616
|
+
leads with it, for example `verified <id> (interrupted, sandboxes unknown) · share_ready`.
|
|
617
|
+
`sandboxes` is `clean` only when the reclaim receipt records every sandbox gone and its
|
|
618
|
+
`state` is `clean`, which means the search by tag finished. It is `unconfirmed` when the
|
|
619
|
+
receipt records a sandbox not gone or misses a journaled one, and `unknown` otherwise,
|
|
620
|
+
including a receipt from humanish 0.110.0 or earlier, which did not search by tag.
|
|
546
621
|
|
|
547
622
|
The local-evidence check includes screenshots declared only by
|
|
548
623
|
`streams[].actor.items[].screenshotRef` or `streams[].liveActor.items[].screenshotRef`,
|
|
@@ -634,9 +709,17 @@ finding is true. Its own bytes are not included in its hash inventory. Feedback
|
|
|
634
709
|
commands can subsequently generate new derivative-local artifacts.
|
|
635
710
|
|
|
636
711
|
The derivative names no sandbox. In every JSON and NDJSON file it copies, the
|
|
637
|
-
values at `sandboxId`, `subjectSandboxId
|
|
638
|
-
`[redacted-sandbox-id]
|
|
639
|
-
|
|
712
|
+
values at `sandboxId`, `subjectSandboxId`, each `providerResources[].id` and each
|
|
713
|
+
cleanup.json `resources[].id` read `[redacted-sandbox-id]`, with the id's digest
|
|
714
|
+
beside them. In every text file it copies, Markdown, logs and YAML included, each
|
|
715
|
+
id the source names becomes `[redacted-sandbox-id <digest>]`: the ids in its
|
|
716
|
+
`sandbox-receipts.ndjson`, and any raw id at one of those keys, which is where a
|
|
717
|
+
run recorded before 0.110 with no receipts names them. Only those exact ids are
|
|
718
|
+
replaced, so the rest of each file is unchanged. In YAML the label is
|
|
719
|
+
`redacted-sandbox-id-<digest>`, without brackets, so a value keeps its type.
|
|
720
|
+
An id that appears only in free text, in a run with no receipts, is not known to
|
|
721
|
+
export. `sandbox-receipts.ndjson` is omitted, and the source keeps its raw ids,
|
|
722
|
+
which reclaim reads.
|
|
640
723
|
|
|
641
724
|
PNG files that actor traces reference as stream screenshots are re-encoded as
|
|
642
725
|
blurred thumbnails. A PNG that nothing in `run.json` cites is omitted and
|