humanish 0.89.0 → 0.90.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.
Files changed (90) hide show
  1. package/README.md +15 -0
  2. package/dist/automatic-analysis-completion.d.ts +25 -0
  3. package/dist/automatic-analysis-completion.js +54 -0
  4. package/dist/automatic-analysis-completion.js.map +1 -0
  5. package/dist/automatic-analysis-config.d.ts +17 -0
  6. package/dist/automatic-analysis-config.js +31 -0
  7. package/dist/automatic-analysis-config.js.map +1 -0
  8. package/dist/automatic-study-analysis.d.ts +12 -0
  9. package/dist/automatic-study-analysis.js +168 -0
  10. package/dist/automatic-study-analysis.js.map +1 -0
  11. package/dist/concurrent-shared-world-lab.d.ts +4 -2
  12. package/dist/concurrent-shared-world-lab.js +10 -3
  13. package/dist/concurrent-shared-world-lab.js.map +1 -1
  14. package/dist/cua-actor-lab.d.ts +4 -2
  15. package/dist/cua-actor-lab.js +10 -5
  16. package/dist/cua-actor-lab.js.map +1 -1
  17. package/dist/e2b-terminal-lab.d.ts +4 -2
  18. package/dist/e2b-terminal-lab.js +10 -3
  19. package/dist/e2b-terminal-lab.js.map +1 -1
  20. package/dist/export-bundle.js +2 -0
  21. package/dist/export-bundle.js.map +1 -1
  22. package/dist/index.d.ts +4 -0
  23. package/dist/index.js +1 -0
  24. package/dist/index.js.map +1 -1
  25. package/dist/lab-config.d.ts +5 -0
  26. package/dist/lab-config.js +21 -2
  27. package/dist/lab-config.js.map +1 -1
  28. package/dist/lab-engine.d.ts +2 -0
  29. package/dist/lab-engine.js +12 -3
  30. package/dist/lab-engine.js.map +1 -1
  31. package/dist/lab-summary.d.ts +4 -0
  32. package/dist/lab-summary.js +3 -0
  33. package/dist/lab-summary.js.map +1 -1
  34. package/dist/observer-app.html +9 -9
  35. package/dist/observer.d.ts +3 -0
  36. package/dist/observer.js +23 -6
  37. package/dist/observer.js.map +1 -1
  38. package/dist/oss-lab.d.ts +1 -1
  39. package/dist/oss-lab.js.map +1 -1
  40. package/dist/oss-meta-lab.d.ts +1 -1
  41. package/dist/oss-meta-lab.js.map +1 -1
  42. package/dist/program.d.ts +5 -0
  43. package/dist/program.js +42 -7
  44. package/dist/program.js.map +1 -1
  45. package/dist/run-detail.d.ts +2 -0
  46. package/dist/run-detail.js +3 -0
  47. package/dist/run-detail.js.map +1 -1
  48. package/dist/run.d.ts +1 -1
  49. package/dist/run.js.map +1 -1
  50. package/dist/scripted-browser-lab.d.ts +4 -2
  51. package/dist/scripted-browser-lab.js +10 -5
  52. package/dist/scripted-browser-lab.js.map +1 -1
  53. package/dist/shared-world-lab.d.ts +4 -2
  54. package/dist/shared-world-lab.js +10 -3
  55. package/dist/shared-world-lab.js.map +1 -1
  56. package/dist/study-analysis-engine.d.ts +6 -1
  57. package/dist/study-analysis-engine.js +17 -3
  58. package/dist/study-analysis-engine.js.map +1 -1
  59. package/dist/study-analysis-evidence.js +45 -17
  60. package/dist/study-analysis-evidence.js.map +1 -1
  61. package/dist/study-analysis-job.d.ts +87 -0
  62. package/dist/study-analysis-job.js +197 -0
  63. package/dist/study-analysis-job.js.map +1 -0
  64. package/dist/study-analysis-service.d.ts +10 -1
  65. package/dist/study-analysis-service.js +31 -8
  66. package/dist/study-analysis-service.js.map +1 -1
  67. package/dist/study-analysis-sharing.js +13 -5
  68. package/dist/study-analysis-sharing.js.map +1 -1
  69. package/dist/study-analysis-store.d.ts +4 -0
  70. package/dist/study-analysis-store.js +40 -1
  71. package/dist/study-analysis-store.js.map +1 -1
  72. package/dist/study-analysis-validation.d.ts +2 -1
  73. package/dist/study-analysis-validation.js +12 -5
  74. package/dist/study-analysis-validation.js.map +1 -1
  75. package/dist/study-analysis.d.ts +6 -0
  76. package/dist/study-analysis.js.map +1 -1
  77. package/dist/tui-actions.d.ts +1 -1
  78. package/dist/tui-actions.js +15 -1
  79. package/dist/tui-actions.js.map +1 -1
  80. package/dist/tui-app.js +116 -116
  81. package/dist/tui-contract.d.ts +3 -2
  82. package/dist/tui-contract.js.map +1 -1
  83. package/docs/contracts/schemas.md +1 -1
  84. package/docs/contracts/study-analysis.md +18 -0
  85. package/docs/goals/current.md +4 -4
  86. package/docs/product/automatic-analysis.md +63 -0
  87. package/docs/ramp/README.md +8 -1
  88. package/docs/release/0.89.1-analysis-finished-notice.md +24 -0
  89. package/docs/release/0.90.0-automatic-analysis.md +21 -0
  90. package/package.json +1 -1
package/README.md CHANGED
@@ -104,6 +104,21 @@ beside Participants in Observer, with evidence links and separate participant
104
104
  feedback. See the [analysis contract](docs/contracts/study-analysis.md) for
105
105
  coverage limits, estimated cost controls, corrections and sharing behavior.
106
106
 
107
+ To generate findings automatically after each live run of a lab, add:
108
+
109
+ ```yaml
110
+ review:
111
+ analysis:
112
+ maxCostUsd: 3
113
+ ```
114
+
115
+ This is a separate analysis budget, in addition to participant execution costs.
116
+ `humanish run <lab>`, `humanish lab run <lab>`, `humanish watch <lab>`, and live
117
+ starts in the TUI all honor it. Dry runs skip analysis. The TUI and Observer show
118
+ its progress after participants finish; opening either view never spends money.
119
+ See [automatic analysis](docs/product/automatic-analysis.md) for configuration,
120
+ cancellation, and failure behavior.
121
+
107
122
  ## Public-Safety Boundary
108
123
 
109
124
  Humanish is designed for public repositories and public issue queues. The
@@ -0,0 +1,25 @@
1
+ import { type PreparedRunArtifactPaths } from "./run-paths.js";
2
+ import type { StudyAnalysisConfig } from "./study-analysis.js";
3
+ import { runAutomaticStudyAnalysis, type AutomaticStudyAnalysisDeps } from "./automatic-study-analysis.js";
4
+ import type { AutomaticStudyAnalysisOutcome } from "./study-analysis-job.js";
5
+ export interface AutomaticAnalysisHooks {
6
+ /** Provider/test dependencies apply only to analysis, never to the participant. */
7
+ deps?: AutomaticStudyAnalysisDeps;
8
+ /** Called after the source is finalized; the returned cleanup always runs. */
9
+ onStart?: () => void | (() => void);
10
+ run?: typeof runAutomaticStudyAnalysis;
11
+ }
12
+ export interface AutomaticAnalysisResult {
13
+ automaticAnalysis?: AutomaticStudyAnalysisOutcome;
14
+ }
15
+ /** Internal producer receipt: call only after final source publication, never for an early refusal. */
16
+ export declare function markFinalizedStudyResult<T extends object>(result: T, prepared: PreparedRunArtifactPaths): T;
17
+ /** One post-completion boundary shared by all live recording producers. */
18
+ export declare function completeAutomaticAnalysis<T extends {
19
+ cwd: string;
20
+ runId: string;
21
+ dryRun: boolean;
22
+ }>(result: T, config: StudyAnalysisConfig | undefined, hooks?: AutomaticAnalysisHooks): Promise<T & AutomaticAnalysisResult>;
23
+ export declare function automaticAnalysisSucceeded(result: {
24
+ automaticAnalysis?: AutomaticStudyAnalysisOutcome;
25
+ }): boolean;
@@ -0,0 +1,54 @@
1
+ import path from "node:path";
2
+ import { validatePreparedRunRootIdentity } from "./run-paths.js";
3
+ import { runAutomaticStudyAnalysis } from "./automatic-study-analysis.js";
4
+ const finalizedResults = new WeakMap();
5
+ /** Internal producer receipt: call only after final source publication, never for an early refusal. */
6
+ export function markFinalizedStudyResult(result, prepared) {
7
+ finalizedResults.set(result, prepared);
8
+ return result;
9
+ }
10
+ /** One post-completion boundary shared by all live recording producers. */
11
+ export async function completeAutomaticAnalysis(result, config, hooks) {
12
+ if (config === undefined)
13
+ return result;
14
+ if (result.dryRun)
15
+ return { ...result, automaticAnalysis: { state: "skipped", reason: "analysis_dry_run" } };
16
+ // An early refusal can echo a caller-supplied ID belonging to an older run.
17
+ // Require this invocation's internal final-publication receipt before reading that ID.
18
+ const prepared = finalizedResults.get(result);
19
+ if (!result.runId || result.runId === "not-created" || prepared === undefined) {
20
+ return { ...result, automaticAnalysis: { state: "skipped", reason: "analysis_source_unavailable" } };
21
+ }
22
+ let cleanup = undefined;
23
+ try {
24
+ cleanup = hooks?.onStart?.();
25
+ try {
26
+ await validatePreparedRunRootIdentity(prepared);
27
+ }
28
+ catch {
29
+ return { ...result, automaticAnalysis: { state: "failed", reason: "analysis_source_changed" } };
30
+ }
31
+ const sourceCwd = path.dirname(path.dirname(prepared.physicalRunsRoot));
32
+ const automaticAnalysis = await (hooks?.run ?? runAutomaticStudyAnalysis)(sourceCwd, result.runId, config, { ...hooks?.deps, expectedRun: prepared });
33
+ return { ...result, automaticAnalysis };
34
+ }
35
+ catch {
36
+ // Preserve the producer result. Never put a hook exception or provider response in the envelope.
37
+ return { ...result, automaticAnalysis: { state: "failed", reason: "analysis_automatic_failed" } };
38
+ }
39
+ finally {
40
+ try {
41
+ cleanup?.();
42
+ }
43
+ catch { /* Cleanup cannot erase accounting or the completed recording. */ }
44
+ }
45
+ }
46
+ export function automaticAnalysisSucceeded(result) {
47
+ const value = result.automaticAnalysis;
48
+ if (value === undefined)
49
+ return true;
50
+ if (value.state === "skipped")
51
+ return value.reason === "analysis_dry_run";
52
+ return (value.state === "complete" || value.state === "partial") && value.result?.ok === true;
53
+ }
54
+ //# sourceMappingURL=automatic-analysis-completion.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"automatic-analysis-completion.js","sourceRoot":"","sources":["../src/automatic-analysis-completion.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { StudyAnalysisConfig } from "./study-analysis.js";
2
+ /** A separate, explicitly priced review after a live participant study. */
3
+ export interface LabAnalysis {
4
+ maxCostUsd: number;
5
+ model?: string;
6
+ question?: string;
7
+ timeoutMs?: number;
8
+ maxOutputTokens?: number;
9
+ }
10
+ /** Called for parsed manifests AND direct library configs, before participant execution. */
11
+ export declare function resolveAutomaticAnalysis(raw: unknown): {
12
+ ok: true;
13
+ config: StudyAnalysisConfig | undefined;
14
+ } | {
15
+ ok: false;
16
+ message: string;
17
+ };
@@ -0,0 +1,31 @@
1
+ import { SUPPORTED_STUDY_ANALYSIS_MODELS } from "./study-analysis-engine.js";
2
+ import { containsSensitive } from "./redaction.js";
3
+ const MODELS = new Set(SUPPORTED_STUDY_ANALYSIS_MODELS);
4
+ const FIELDS = new Set(["maxCostUsd", "model", "question", "timeoutMs", "maxOutputTokens"]);
5
+ /** Called for parsed manifests AND direct library configs, before participant execution. */
6
+ export function resolveAutomaticAnalysis(raw) {
7
+ if (raw === undefined)
8
+ return { ok: true, config: undefined };
9
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)
10
+ || Object.keys(raw).some(key => !FIELDS.has(key))) {
11
+ return { ok: false, message: "review.analysis must be a mapping containing only maxCostUsd, model, question, timeoutMs and maxOutputTokens." };
12
+ }
13
+ const value = raw;
14
+ const { maxCostUsd } = value;
15
+ const model = value.model === undefined ? "gpt-6-astra" : value.model;
16
+ const question = value.question === undefined ? null : value.question;
17
+ const timeoutMs = value.timeoutMs === undefined ? 300_000 : value.timeoutMs;
18
+ const maxOutputTokens = value.maxOutputTokens === undefined ? 16_384 : value.maxOutputTokens;
19
+ if (typeof maxCostUsd !== "number" || !Number.isFinite(maxCostUsd) || maxCostUsd <= 0 || maxCostUsd > 1000
20
+ || typeof model !== "string" || !MODELS.has(model)
21
+ || typeof timeoutMs !== "number" || !Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 600_000
22
+ || typeof maxOutputTokens !== "number" || !Number.isSafeInteger(maxOutputTokens) || maxOutputTokens < 256 || maxOutputTokens > 32_768
23
+ || (value.question !== undefined && (typeof question !== "string" || question.length > 4000))) {
24
+ return { ok: false, message: "review.analysis requires a positive maxCostUsd up to 1000, a supported analysis model, timeoutMs 1–600000, maxOutputTokens 256–32768, and an optional question of at most 4000 characters." };
25
+ }
26
+ if (question !== null && containsSensitive(question)) {
27
+ return { ok: false, message: "review.analysis.question contains sensitive text and cannot be sent for analysis." };
28
+ }
29
+ return { ok: true, config: { model, maxCostUsd, timeoutMs, maxOutputTokens, question: question } };
30
+ }
31
+ //# sourceMappingURL=automatic-analysis-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"automatic-analysis-config.js","sourceRoot":"","sources":["../src/automatic-analysis-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,+BAA+B,EAAE,MAAM,4BAA4B,CAAC;AAC7E,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAYnD,MAAM,MAAM,GAAG,IAAI,GAAG,CAAS,+BAA+B,CAAC,CAAC;AAChE,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,CAAC,YAAY,EAAE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,iBAAiB,CAAC,CAAC,CAAC;AAE5F,4FAA4F;AAC5F,MAAM,UAAU,wBAAwB,CAAC,GAAY;IAGnD,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IAC9D,IAAI,GAAG,KAAK,IAAI,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;WAC5D,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;QACpD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,+GAA+G,EAAE,CAAC;IACjJ,CAAC;IACD,MAAM,KAAK,GAAG,GAA8B,CAAC;IAC7C,MAAM,EAAE,UAAU,EAAE,GAAG,KAAK,CAAC;IAC7B,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC;IACtE,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC;IACtE,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC;IAC5E,MAAM,eAAe,GAAG,KAAK,CAAC,eAAe,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,eAAe,CAAC;IAC7F,IAAI,OAAO,UAAU,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,UAAU,IAAI,CAAC,IAAI,UAAU,GAAG,IAAI;WACrG,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC;WAC/C,OAAO,SAAS,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,SAAS,CAAC,IAAI,SAAS,GAAG,CAAC,IAAI,SAAS,GAAG,OAAO;WACzG,OAAO,eAAe,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,eAAe,CAAC,IAAI,eAAe,GAAG,GAAG,IAAI,eAAe,GAAG,MAAM;WAClI,CAAC,KAAK,CAAC,QAAQ,KAAK,SAAS,IAAI,CAAC,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC,EAAE,CAAC;QAChG,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,4LAA4L,EAAE,CAAC;IAC9N,CAAC;IACD,IAAI,QAAQ,KAAK,IAAI,IAAI,iBAAiB,CAAC,QAAkB,CAAC,EAAE,CAAC;QAC/D,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,mFAAmF,EAAE,CAAC;IACrH,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,eAAe,EAAE,QAAQ,EAAE,QAAyB,EAAE,EAAE,CAAC;AACtH,CAAC"}
@@ -0,0 +1,12 @@
1
+ import { type AnalyzeDeps } from "./study-analysis-service.js";
2
+ import { type AutomaticStudyAnalysisView, type AutomaticStudyAnalysisOutcome, type AutomaticStudyAnalysisCancellation } from "./study-analysis-job.js";
3
+ import type { StudyAnalysisConfig } from "./study-analysis.js";
4
+ export type { AutomaticStudyAnalysisView, AutomaticStudyAnalysisOutcome, AutomaticStudyAnalysisCancellation } from "./study-analysis-job.js";
5
+ export type AutomaticStudyAnalysisDeps = Omit<AnalyzeDeps, "analysisId" | "beforeDispatch">;
6
+ /** Small read-only TUI/CLI projection. Neither this nor Observer can resume a job. */
7
+ export declare function readAutomaticStudyAnalysis(cwd: string, runId: string): Promise<AutomaticStudyAnalysisView | undefined>;
8
+ export declare function requestAutomaticStudyAnalysisCancellation(cwd: string, runId: string): Promise<AutomaticStudyAnalysisCancellation>;
9
+ /** One permanent claim per completed run, consumed even when preparation/cancellation fails.
10
+ * Only the original opted-in producer calls this after all recording writes return.
11
+ * No file reader, restart recovery, Observer poll or export calls this function. */
12
+ export declare function runAutomaticStudyAnalysis(cwdInput: string, runId: string, configInput: StudyAnalysisConfig, deps?: AutomaticStudyAnalysisDeps): Promise<AutomaticStudyAnalysisOutcome>;
@@ -0,0 +1,168 @@
1
+ import path from "node:path";
2
+ import { renderObserver } from "./observer.js";
3
+ import { resolveRunPath } from "./run.js";
4
+ import { analyzeStudy, readCompletedStudyAnalysisSource, resolveStudyAnalysisRun } from "./study-analysis-service.js";
5
+ import { STUDY_ANALYSIS_PROMPT_VERSION } from "./study-analysis-engine.js";
6
+ import { hashStudyAnalysisValue } from "./study-analysis-validation.js";
7
+ import { claimAutomaticStudyAnalysis, readAutomaticStudyAnalysisPrepared, requestAutomaticStudyAnalysisCancellationPrepared } from "./study-analysis-job.js";
8
+ import { readStudyAnalysisExecution, readStudyAnalysisVersion } from "./study-analysis-store.js";
9
+ const exactId = (runId) => runId !== "latest" && /^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/.test(runId);
10
+ const skipped = (reason) => ({ state: "skipped", reason });
11
+ /** Small read-only TUI/CLI projection. Neither this nor Observer can resume a job. */
12
+ export async function readAutomaticStudyAnalysis(cwd, runId) {
13
+ if (!exactId(runId))
14
+ return undefined;
15
+ const prepared = await resolveRunPath(path.resolve(cwd), runId).catch(() => null);
16
+ return prepared ? readAutomaticStudyAnalysisPrepared(prepared) : undefined;
17
+ }
18
+ export async function requestAutomaticStudyAnalysisCancellation(cwd, runId) {
19
+ if (!exactId(runId))
20
+ return { requested: false, reason: "AUTOMATIC_ANALYSIS_SOURCE_UNAVAILABLE" };
21
+ const prepared = await resolveRunPath(path.resolve(cwd), runId).catch(() => null);
22
+ return prepared ? requestAutomaticStudyAnalysisCancellationPrepared(prepared)
23
+ : { requested: false, reason: "AUTOMATIC_ANALYSIS_SOURCE_UNAVAILABLE" };
24
+ }
25
+ function outcomeOf(result) {
26
+ if (result.error?.code === "ANALYSIS_PUBLICATION_FAILED")
27
+ return { state: "failed", reason: "AUTOMATIC_ANALYSIS_PUBLICATION_FAILED", result };
28
+ if (result.status)
29
+ return { state: result.status, result, reason: result.reused ? "AUTOMATIC_ANALYSIS_REUSED"
30
+ : result.status === "partial" ? (result.error?.code === "analysis_admission_estimate_exceeded"
31
+ ? "AUTOMATIC_ANALYSIS_ADMISSION_EXCEEDED" : result.ok ? "AUTOMATIC_ANALYSIS_LIMITATIONS" : "AUTOMATIC_ANALYSIS_FAILED")
32
+ : result.status === "failed" ? "AUTOMATIC_ANALYSIS_FAILED"
33
+ : result.status === "cancelled" ? "AUTOMATIC_ANALYSIS_CANCELLED" : null };
34
+ const code = result.error?.code;
35
+ if (code === "ANALYSIS_CANCELLED")
36
+ return { state: "cancelled", reason: "AUTOMATIC_ANALYSIS_CANCELLED", result };
37
+ if (code === "ANALYSIS_API_KEY_MISSING")
38
+ return { ...skipped("AUTOMATIC_ANALYSIS_KEY_MISSING"), result };
39
+ if (code === "ANALYSIS_BUSY")
40
+ return { ...skipped("AUTOMATIC_ANALYSIS_BUSY"), result };
41
+ if (code === "ANALYSIS_HISTORY_UNAVAILABLE")
42
+ return { ...skipped("AUTOMATIC_ANALYSIS_STORAGE_UNAVAILABLE"), result };
43
+ if (code === "ANALYSIS_CONFIG_INVALID" || code === "ANALYSIS_QUESTION_UNSAFE" || code?.startsWith("analysis_")) {
44
+ return { ...skipped("AUTOMATIC_ANALYSIS_ADMISSION_REFUSED"), result };
45
+ }
46
+ return { state: "unknown", reason: "AUTOMATIC_ANALYSIS_OUTCOME_UNKNOWN", result };
47
+ }
48
+ /** One permanent claim per completed run, consumed even when preparation/cancellation fails.
49
+ * Only the original opted-in producer calls this after all recording writes return.
50
+ * No file reader, restart recovery, Observer poll or export calls this function. */
51
+ export async function runAutomaticStudyAnalysis(cwdInput, runId, configInput, deps = {}) {
52
+ if (!exactId(runId))
53
+ return skipped("AUTOMATIC_ANALYSIS_SOURCE_UNAVAILABLE");
54
+ let cwd = path.resolve(cwdInput);
55
+ const prepared = await resolveStudyAnalysisRun(cwd, runId, deps.expectedRun).catch(() => null);
56
+ if (!prepared)
57
+ return skipped("AUTOMATIC_ANALYSIS_SOURCE_UNAVAILABLE");
58
+ cwd = path.dirname(path.dirname(prepared.physicalRunsRoot));
59
+ // Do not consume a future run's one claim while a producer is still writing it.
60
+ try {
61
+ const bytes = await readCompletedStudyAnalysisSource(cwd, prepared);
62
+ const bundle = JSON.parse(bytes.toString("utf8"));
63
+ if (bundle.streams.some(stream => stream.actor?.stopCause === "harness_aborted"))
64
+ return skipped("AUTOMATIC_ANALYSIS_ACTOR_CANCELLED");
65
+ }
66
+ catch {
67
+ return skipped("AUTOMATIC_ANALYSIS_SOURCE_UNAVAILABLE");
68
+ }
69
+ const config = structuredClone(configInput);
70
+ let job;
71
+ try {
72
+ const claimed = await claimAutomaticStudyAnalysis(prepared, { configDigest: hashStudyAnalysisValue(config), promptVersion: STUDY_ANALYSIS_PROMPT_VERSION });
73
+ if (!claimed)
74
+ return skipped("AUTOMATIC_ANALYSIS_ALREADY_REQUESTED");
75
+ job = claimed;
76
+ }
77
+ catch {
78
+ return { state: "unknown", reason: "AUTOMATIC_ANALYSIS_STORAGE_UNAVAILABLE" };
79
+ }
80
+ const controller = new AbortController();
81
+ const signal = deps.signal ? AbortSignal.any([deps.signal, controller.signal]) : controller.signal;
82
+ let polling = false;
83
+ let storageFailed = false;
84
+ let cancellationFailed = false;
85
+ const poll = async () => {
86
+ if (polling)
87
+ return;
88
+ polling = true;
89
+ try {
90
+ if (await job.cancellationRequested())
91
+ controller.abort();
92
+ }
93
+ catch {
94
+ cancellationFailed = true;
95
+ controller.abort();
96
+ }
97
+ finally {
98
+ polling = false;
99
+ }
100
+ };
101
+ const cancellationTimer = setInterval(() => { void poll(); }, 250);
102
+ cancellationTimer.unref();
103
+ const heartbeat = setInterval(() => {
104
+ void job.touch().catch(() => { storageFailed = true; controller.abort(); });
105
+ }, 5000);
106
+ heartbeat.unref();
107
+ let outcome;
108
+ try {
109
+ await poll();
110
+ const result = await analyzeStudy(cwd, runId, { config }, {
111
+ ...deps, signal, expectedRun: prepared, analysisId: job.attemptId,
112
+ beforeDispatch: async (context) => {
113
+ if (await job.cancellationRequested())
114
+ controller.abort();
115
+ if (signal.aborted)
116
+ return;
117
+ await job.update({ state: "running", analysisId: context.id, startedAt: new Date().toISOString(),
118
+ sourceRunSha256: context.sourceRunSha256, inputDigest: context.inputDigest });
119
+ }
120
+ });
121
+ outcome = outcomeOf(result);
122
+ if (cancellationFailed)
123
+ outcome = { state: "unknown", reason: "AUTOMATIC_ANALYSIS_CANCELLATION_UNAVAILABLE", result };
124
+ if (storageFailed)
125
+ outcome = { state: "unknown", reason: "AUTOMATIC_ANALYSIS_STORAGE_UNAVAILABLE", result };
126
+ }
127
+ catch {
128
+ outcome = { state: "unknown", reason: "AUTOMATIC_ANALYSIS_OUTCOME_UNKNOWN" };
129
+ }
130
+ finally {
131
+ clearInterval(cancellationTimer);
132
+ clearInterval(heartbeat);
133
+ }
134
+ try {
135
+ // Bind terminal metadata to the exact safely published execution. Reads never
136
+ // promote a completed-looking sidecar without rechecking these bindings.
137
+ const analysisId = outcome.result?.analysisId;
138
+ const [entry, receipt] = analysisId ? await Promise.all([
139
+ readStudyAnalysisVersion(prepared, analysisId), readStudyAnalysisExecution(prepared, analysisId)
140
+ ]) : [null, null];
141
+ await job.update({ state: outcome.state, reason: outcome.reason,
142
+ analysisId: analysisId ?? null,
143
+ ...(receipt === null ? {} : { sourceRunSha256: receipt.sourceRunSha256, inputDigest: receipt.inputDigest,
144
+ receiptSha256: hashStudyAnalysisValue(receipt) }),
145
+ ...(entry?.analysis ? { analysisSha256: hashStudyAnalysisValue(entry.analysis) } : {}) });
146
+ const persisted = await readAutomaticStudyAnalysisPrepared(prepared);
147
+ if (persisted?.state === "unknown")
148
+ outcome = { ...outcome, state: "unknown", reason: persisted.reason };
149
+ }
150
+ catch {
151
+ return { ...outcome, state: "unknown", reason: "AUTOMATIC_ANALYSIS_STORAGE_UNAVAILABLE" };
152
+ }
153
+ // The service may render while this owner is still running. Freeze the final
154
+ // job projection for direct file opening too, including no-request outcomes.
155
+ // A failed refresh must never erase the durable outcome or measured usage.
156
+ try {
157
+ const rendered = await renderObserver(cwd, runId, { open: false, expectedRun: prepared });
158
+ if (!rendered.ok)
159
+ throw new Error("AUTOMATIC_ANALYSIS_OBSERVER_UNAVAILABLE");
160
+ }
161
+ catch {
162
+ if (outcome.result)
163
+ outcome = { ...outcome, result: { ...outcome.result,
164
+ warnings: [...outcome.result.warnings, "Automatic analysis status was saved, but Observer could not be refreshed. Run humanish observe again."] } };
165
+ }
166
+ return outcome;
167
+ }
168
+ //# sourceMappingURL=automatic-study-analysis.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"automatic-study-analysis.js","sourceRoot":"","sources":["../src/automatic-study-analysis.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAkB,MAAM,UAAU,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,gCAAgC,EAAE,uBAAuB,EAAwC,MAAM,6BAA6B,CAAC;AAC5J,OAAO,EAAE,6BAA6B,EAAE,MAAM,4BAA4B,CAAC;AAC3E,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,EAAE,2BAA2B,EAAE,kCAAkC,EAAE,iDAAiD,EAEzF,MAAM,yBAAyB,CAAC;AAElE,OAAO,EAAE,0BAA0B,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAKjG,MAAM,OAAO,GAAG,CAAC,KAAa,EAAW,EAAE,CAAC,KAAK,KAAK,QAAQ,IAAI,oCAAoC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AACnH,MAAM,OAAO,GAAG,CAAC,MAAc,EAAiC,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC;AAElG,sFAAsF;AACtF,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAAC,GAAW,EAAE,KAAa;IACzE,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IACtC,MAAM,QAAQ,GAAG,MAAM,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;IAClF,OAAO,QAAQ,CAAC,CAAC,CAAC,kCAAkC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAC7E,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,yCAAyC,CAAC,GAAW,EAAE,KAAa;IACxF,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,uCAAuC,EAAE,CAAC;IAClG,MAAM,QAAQ,GAAG,MAAM,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;IAClF,OAAO,QAAQ,CAAC,CAAC,CAAC,iDAAiD,CAAC,QAAQ,CAAC;QAC3E,CAAC,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,uCAAuC,EAAE,CAAC;AAC5E,CAAC;AAED,SAAS,SAAS,CAAC,MAAqB;IACtC,IAAI,MAAM,CAAC,KAAK,EAAE,IAAI,KAAK,6BAA6B;QAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,uCAAuC,EAAE,MAAM,EAAE,CAAC;IAC9I,IAAI,MAAM,CAAC,MAAM;QAAE,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,2BAA2B;gBAC3G,CAAC,CAAC,MAAM,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,KAAK,sCAAsC;oBAC5F,CAAC,CAAC,uCAAuC,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,gCAAgC,CAAC,CAAC,CAAC,2BAA2B,CAAC;oBACzH,CAAC,CAAC,MAAM,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,2BAA2B;wBAC1D,CAAC,CAAC,MAAM,CAAC,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,8BAA8B,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC5E,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC;IAChC,IAAI,IAAI,KAAK,oBAAoB;QAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,8BAA8B,EAAE,MAAM,EAAE,CAAC;IACjH,IAAI,IAAI,KAAK,0BAA0B;QAAE,OAAO,EAAE,GAAG,OAAO,CAAC,gCAAgC,CAAC,EAAE,MAAM,EAAE,CAAC;IACzG,IAAI,IAAI,KAAK,eAAe;QAAE,OAAO,EAAE,GAAG,OAAO,CAAC,yBAAyB,CAAC,EAAE,MAAM,EAAE,CAAC;IACvF,IAAI,IAAI,KAAK,8BAA8B;QAAE,OAAO,EAAE,GAAG,OAAO,CAAC,wCAAwC,CAAC,EAAE,MAAM,EAAE,CAAC;IACrH,IAAI,IAAI,KAAK,yBAAyB,IAAI,IAAI,KAAK,0BAA0B,IAAI,IAAI,EAAE,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;QAC/G,OAAO,EAAE,GAAG,OAAO,CAAC,sCAAsC,CAAC,EAAE,MAAM,EAAE,CAAC;IACxE,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,oCAAoC,EAAE,MAAM,EAAE,CAAC;AACpF,CAAC;AAED;;oFAEoF;AACpF,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAAC,QAAgB,EAAE,KAAa,EAAE,WAAgC,EAC/G,OAAmC,EAAE;IACrC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,OAAO,CAAC,uCAAuC,CAAC,CAAC;IAC7E,IAAI,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACjC,MAAM,QAAQ,GAAG,MAAM,uBAAuB,CAAC,GAAG,EAAE,KAAK,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;IAC/F,IAAI,CAAC,QAAQ;QAAE,OAAO,OAAO,CAAC,uCAAuC,CAAC,CAAC;IACvE,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAC,CAAC;IAC5D,gFAAgF;IAChF,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,gCAAgC,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;QACpE,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAc,CAAC;QAC/D,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE,SAAS,KAAK,iBAAiB,CAAC;YAAE,OAAO,OAAO,CAAC,oCAAoC,CAAC,CAAC;IACzI,CAAC;IACD,MAAM,CAAC;QAAC,OAAO,OAAO,CAAC,uCAAuC,CAAC,CAAC;IAAC,CAAC;IAClE,MAAM,MAAM,GAAG,eAAe,CAAC,WAAW,CAAC,CAAC;IAC5C,IAAI,GAA8B,CAAC;IACnC,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,2BAA2B,CAAC,QAAQ,EAAE,EAAE,YAAY,EAAE,sBAAsB,CAAC,MAAM,CAAC,EAAE,aAAa,EAAE,6BAA6B,EAAE,CAAC,CAAC;QAC5J,IAAI,CAAC,OAAO;YAAE,OAAO,OAAO,CAAC,sCAAsC,CAAC,CAAC;QACrE,GAAG,GAAG,OAAO,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,wCAAwC,EAAE,CAAC;IAAC,CAAC;IAE1F,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC;IACnG,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,IAAI,aAAa,GAAG,KAAK,CAAC;IAC1B,IAAI,kBAAkB,GAAG,KAAK,CAAC;IAC/B,MAAM,IAAI,GAAG,KAAK,IAAmB,EAAE;QACrC,IAAI,OAAO;YAAE,OAAO;QACpB,OAAO,GAAG,IAAI,CAAC;QACf,IAAI,CAAC;YAAC,IAAI,MAAM,GAAG,CAAC,qBAAqB,EAAE;gBAAE,UAAU,CAAC,KAAK,EAAE,CAAC;QAAC,CAAC;QAClE,MAAM,CAAC;YAAC,kBAAkB,GAAG,IAAI,CAAC;YAAC,UAAU,CAAC,KAAK,EAAE,CAAC;QAAC,CAAC;gBAChD,CAAC;YAAC,OAAO,GAAG,KAAK,CAAC;QAAC,CAAC;IAC9B,CAAC,CAAC;IACF,MAAM,iBAAiB,GAAG,WAAW,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACnE,iBAAiB,CAAC,KAAK,EAAE,CAAC;IAC1B,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,EAAE;QACjC,KAAK,GAAG,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,aAAa,GAAG,IAAI,CAAC,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9E,CAAC,EAAE,IAAI,CAAC,CAAC;IACT,SAAS,CAAC,KAAK,EAAE,CAAC;IAClB,IAAI,OAAsC,CAAC;IAC3C,IAAI,CAAC;QACH,MAAM,IAAI,EAAE,CAAC;QACb,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE;YACxD,GAAG,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,UAAU,EAAE,GAAG,CAAC,SAAS;YACjE,cAAc,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;gBAChC,IAAI,MAAM,GAAG,CAAC,qBAAqB,EAAE;oBAAE,UAAU,CAAC,KAAK,EAAE,CAAC;gBAC1D,IAAI,MAAM,CAAC,OAAO;oBAAE,OAAO;gBAC3B,MAAM,GAAG,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;oBAC9F,eAAe,EAAE,OAAO,CAAC,eAAe,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;YAClF,CAAC;SACF,CAAC,CAAC;QACH,OAAO,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;QAC5B,IAAI,kBAAkB;YAAE,OAAO,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,6CAA6C,EAAE,MAAM,EAAE,CAAC;QACtH,IAAI,aAAa;YAAE,OAAO,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,wCAAwC,EAAE,MAAM,EAAE,CAAC;IAC9G,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,oCAAoC,EAAE,CAAC;IAAC,CAAC;YACjF,CAAC;QAAC,aAAa,CAAC,iBAAiB,CAAC,CAAC;QAAC,aAAa,CAAC,SAAS,CAAC,CAAC;IAAC,CAAC;IACvE,IAAI,CAAC;QACH,8EAA8E;QAC9E,yEAAyE;QACzE,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,EAAE,UAAU,CAAC;QAC9C,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,MAAM,OAAO,CAAC,GAAG,CAAC;YACtD,wBAAwB,CAAC,QAAQ,EAAE,UAAU,CAAC,EAAE,0BAA0B,CAAC,QAAQ,EAAE,UAAU,CAAC;SACjG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAClB,MAAM,GAAG,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,MAA0F;YACjJ,UAAU,EAAE,UAAU,IAAI,IAAI;YAC9B,GAAG,CAAC,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,OAAO,CAAC,eAAe,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW;gBACtG,aAAa,EAAE,sBAAsB,CAAC,OAAO,CAAC,EAAE,CAAC;YACnD,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,sBAAsB,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QAC5F,MAAM,SAAS,GAAG,MAAM,kCAAkC,CAAC,QAAQ,CAAC,CAAC;QACrE,IAAI,SAAS,EAAE,KAAK,KAAK,SAAS;YAAE,OAAO,GAAG,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,MAAM,EAAE,CAAC;IAC3G,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,wCAAwC,EAAE,CAAC;IAAC,CAAC;IACtG,6EAA6E;IAC7E,6EAA6E;IAC7E,2EAA2E;IAC3E,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,cAAc,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC1F,IAAI,CAAC,QAAQ,CAAC,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;IAC/E,CAAC;IAAC,MAAM,CAAC;QACP,IAAI,OAAO,CAAC,MAAM;YAAE,OAAO,GAAG,EAAE,GAAG,OAAO,EAAE,MAAM,EAAE,EAAE,GAAG,OAAO,CAAC,MAAM;oBACrE,QAAQ,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,uGAAuG,CAAC,EAAE,EAAE,CAAC;IACxJ,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -1,3 +1,4 @@
1
+ import { type AutomaticAnalysisHooks, type AutomaticAnalysisResult } from "./automatic-analysis-completion.js";
1
2
  import { type RunLabProvenance } from "./run-status.js";
2
3
  import { type CuaActorDescriptor } from "./actor-registry.js";
3
4
  import { type CuaLaneSpec, type LaneRunOutcome } from "./cua-actor-lab.js";
@@ -12,6 +13,7 @@ export declare const CONCURRENT_SHARED_WORLD_PROVIDER_METADATA: {
12
13
  };
13
14
  export declare const CONCURRENT_ATTRIBUTION_LIMITS: readonly ["concurrent", "best-effort-causal-attribution", "non-deterministic-shared-state", "window-and-snapshot-granularity", "contention-observed-not-proven-safe", "state-change-not-isolated-to-actors"];
14
15
  export interface RunConcurrentSharedWorldLabOptions {
16
+ automaticAnalysis?: AutomaticAnalysisHooks;
15
17
  /** Which manifest produced this run (#455); threaded into the status record + bundle. */
16
18
  lab?: RunLabProvenance;
17
19
  cwd: string;
@@ -28,7 +30,7 @@ export interface RunConcurrentSharedWorldLabOptions {
28
30
  * (#316); core-stamped onto the bundle as evidence. Absent for library callers. */
29
31
  scorerProvenance?: RunScorerProvenance;
30
32
  }
31
- export type ConcurrentSharedWorldLabErrorCode = "HUMANISH_LAB_TASKS_UNSUPPORTED" | "HUMANISH_CONCURRENT_SHARED_WORLD_LAB_FAILED" | "HUMANISH_CONCURRENT_SHARED_WORLD_LAB_ACTOR_UNSUPPORTED" | "HUMANISH_CONCURRENT_SHARED_WORLD_LAB_INVALID" | "HUMANISH_CONCURRENT_SHARED_WORLD_LAB_KEYS_MISSING" | "HUMANISH_CONCURRENT_SHARED_WORLD_LAB_SUBJECT_ENV_MISSING" | "HUMANISH_CONCURRENT_SHARED_WORLD_LAB_GETHOST_UNAVAILABLE" | "HUMANISH_CONCURRENT_SHARED_WORLD_LAB_HANDOFF_TIMEOUT"
33
+ export type ConcurrentSharedWorldLabErrorCode = "HUMANISH_LAB_ANALYSIS_INVALID" | "HUMANISH_LAB_TASKS_UNSUPPORTED" | "HUMANISH_CONCURRENT_SHARED_WORLD_LAB_FAILED" | "HUMANISH_CONCURRENT_SHARED_WORLD_LAB_ACTOR_UNSUPPORTED" | "HUMANISH_CONCURRENT_SHARED_WORLD_LAB_INVALID" | "HUMANISH_CONCURRENT_SHARED_WORLD_LAB_KEYS_MISSING" | "HUMANISH_CONCURRENT_SHARED_WORLD_LAB_SUBJECT_ENV_MISSING" | "HUMANISH_CONCURRENT_SHARED_WORLD_LAB_GETHOST_UNAVAILABLE" | "HUMANISH_CONCURRENT_SHARED_WORLD_LAB_HANDOFF_TIMEOUT"
32
34
  /** A declared adopter-hosted comms catch (#328) did not answer as a humanish catch — fail closed
33
35
  * BEFORE any actor spend, since the funnel would silently collect nothing. */
34
36
  | "HUMANISH_CONCURRENT_SHARED_WORLD_LAB_COMMS_CATCH_UNREACHABLE";
@@ -94,7 +96,7 @@ export interface ConcurrentSharedWorldRoleResult {
94
96
  message: string;
95
97
  };
96
98
  }
97
- export interface ConcurrentSharedWorldLabResult {
99
+ export interface ConcurrentSharedWorldLabResult extends AutomaticAnalysisResult {
98
100
  schema: typeof CONCURRENT_SHARED_WORLD_LAB_SCHEMA;
99
101
  ok: boolean;
100
102
  cwd: string;
@@ -32,6 +32,8 @@
32
32
  // synthetic-seeded-subjects ONLY. Verify fail-closes on subject.state.provenance != "seeded" and
33
33
  // requires the author attestation subject.exposure: synthetic. This is author-trust + a provenance
34
34
  // gate, NOT a no-real-data guarantee (Humanish cannot tell synthetic from real data).
35
+ import { resolveAutomaticAnalysis } from "./automatic-analysis-config.js";
36
+ import { completeAutomaticAnalysis, markFinalizedStudyResult } from "./automatic-analysis-completion.js";
35
37
  import { taskProtocolValidationReason } from "./lab-config.js";
36
38
  import { randomBytes } from "node:crypto";
37
39
  import { describeMissingKeys } from "./key-resolution.js";
@@ -500,7 +502,9 @@ function observerResultForConcurrentArtifacts(cwd, runId, artifactRoot, warnings
500
502
  * ticking into a directory something else is deleting, which surfaces as an unrelated ENOTEMPTY.
501
503
  */
502
504
  export async function runConcurrentSharedWorld(options) {
503
- return withRunStatusScope(() => runConcurrentSharedWorldInScope(options));
505
+ const analysis = resolveAutomaticAnalysis(options.config.review?.analysis);
506
+ const result = await withRunStatusScope(() => runConcurrentSharedWorldInScope(options));
507
+ return completeAutomaticAnalysis(result, analysis.ok ? analysis.config : undefined, options.automaticAnalysis);
504
508
  }
505
509
  async function runConcurrentSharedWorldInScope(options) {
506
510
  const { config, dryRun } = options;
@@ -530,6 +534,9 @@ async function runConcurrentSharedWorldInScope(options) {
530
534
  warnings: [],
531
535
  error: { code, message }
532
536
  });
537
+ const analysis = resolveAutomaticAnalysis(config.review?.analysis);
538
+ if (!analysis.ok)
539
+ return fail("HUMANISH_LAB_ANALYSIS_INVALID", analysis.message);
533
540
  const tasksReason = taskProtocolValidationReason(config, false);
534
541
  if (tasksReason)
535
542
  return fail("HUMANISH_LAB_TASKS_UNSUPPORTED", tasksReason);
@@ -1595,7 +1602,7 @@ async function runConcurrentSharedWorldInScope(options) {
1595
1602
  const passed = roleResults.filter((role) => role.ok).length;
1596
1603
  return { code: "HUMANISH_CONCURRENT_SHARED_WORLD_LAB_FAILED", message: `Concurrent shared-world run did not run coherently: ${passed}/${roles.length} actor(s) reached a terminal, engaged passed session.` };
1597
1604
  })();
1598
- return {
1605
+ return markFinalizedStudyResult({
1599
1606
  schema: CONCURRENT_SHARED_WORLD_LAB_SCHEMA,
1600
1607
  ok,
1601
1608
  cwd,
@@ -1616,7 +1623,7 @@ async function runConcurrentSharedWorldInScope(options) {
1616
1623
  observer,
1617
1624
  warnings: [...warnings, ...adapterWarnings, ...observer.warnings],
1618
1625
  ...(errorResult === undefined ? {} : { error: errorResult })
1619
- };
1626
+ }, runPaths);
1620
1627
  }
1621
1628
  /** Max windows live at the same instant (sweep over start/end points). The honest simultaneity
1622
1629
  * count: lane COUNT says how many seats existed; this says how many ever ran at once. */