@tiangong-ai/cli 0.0.61 → 0.0.63
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/AGENTS.md +17 -6
- package/CONTRIBUTING.md +5 -5
- package/README.md +417 -36
- package/dist/cli.js +2 -2
- package/dist/data/builtins.js +33 -5
- package/dist/data/builtins.js.map +1 -1
- package/dist/data/catalog.js +46 -0
- package/dist/data/catalog.js.map +1 -1
- package/dist/data/commands.js +15 -1
- package/dist/data/commands.js.map +1 -1
- package/dist/data/connectors/airnow-hourly-observations.js +60 -10
- package/dist/data/connectors/airnow-hourly-observations.js.map +1 -1
- package/dist/data/connectors/epa-eis-records.js +16 -2
- package/dist/data/connectors/epa-eis-records.js.map +1 -1
- package/dist/data/connectors/gdelt-doc-search.js +54 -18
- package/dist/data/connectors/gdelt-doc-search.js.map +1 -1
- package/dist/data/connectors/gdelt-file-feeds.js +20 -11
- package/dist/data/connectors/gdelt-file-feeds.js.map +1 -1
- package/dist/data/connectors/gdelt-web-ngrams.d.ts +2 -0
- package/dist/data/connectors/gdelt-web-ngrams.js +350 -0
- package/dist/data/connectors/gdelt-web-ngrams.js.map +1 -0
- package/dist/data/connectors/gdelt-web-ngrams.schemas.d.ts +188 -0
- package/dist/data/connectors/gdelt-web-ngrams.schemas.js +138 -0
- package/dist/data/connectors/gdelt-web-ngrams.schemas.js.map +1 -0
- package/dist/data/connectors/nasa-firms-fire.js +4 -0
- package/dist/data/connectors/nasa-firms-fire.js.map +1 -1
- package/dist/data/connectors/open-meteo-air-quality.js +27 -22
- package/dist/data/connectors/open-meteo-air-quality.js.map +1 -1
- package/dist/data/connectors/open-meteo-air-quality.schemas.d.ts +4 -1
- package/dist/data/connectors/open-meteo-air-quality.schemas.js +21 -1
- package/dist/data/connectors/open-meteo-air-quality.schemas.js.map +1 -1
- package/dist/data/connectors/open-meteo-flood.js +28 -23
- package/dist/data/connectors/open-meteo-flood.js.map +1 -1
- package/dist/data/connectors/open-meteo-flood.schemas.d.ts +4 -1
- package/dist/data/connectors/open-meteo-flood.schemas.js +21 -1
- package/dist/data/connectors/open-meteo-flood.schemas.js.map +1 -1
- package/dist/data/connectors/open-meteo-historical-weather.js +26 -21
- package/dist/data/connectors/open-meteo-historical-weather.js.map +1 -1
- package/dist/data/connectors/open-meteo-historical-weather.schemas.d.ts +4 -1
- package/dist/data/connectors/open-meteo-historical-weather.schemas.js +21 -1
- package/dist/data/connectors/open-meteo-historical-weather.schemas.js.map +1 -1
- package/dist/data/connectors/openaq-air-quality.js +50 -12
- package/dist/data/connectors/openaq-air-quality.js.map +1 -1
- package/dist/data/connectors/openaq-air-quality.schemas.d.ts +0 -4
- package/dist/data/connectors/openaq-air-quality.schemas.js +2 -2
- package/dist/data/connectors/openaq-air-quality.schemas.js.map +1 -1
- package/dist/data/connectors/youtube-public-content.js +76 -6
- package/dist/data/connectors/youtube-public-content.js.map +1 -1
- package/dist/data/connectors/youtube-public-content.schemas.d.ts +63 -3
- package/dist/data/connectors/youtube-public-content.schemas.js +56 -2
- package/dist/data/connectors/youtube-public-content.schemas.js.map +1 -1
- package/dist/data/contracts.d.ts +22 -1
- package/dist/data/runtime/bounded-http.d.ts +2 -0
- package/dist/data/runtime/bounded-http.js +221 -59
- package/dist/data/runtime/bounded-http.js.map +1 -1
- package/dist/data/runtime/execute.d.ts +2 -0
- package/dist/data/runtime/execute.js +82 -60
- package/dist/data/runtime/execute.js.map +1 -1
- package/dist/data/runtime/http-timing.d.ts +15 -0
- package/dist/data/runtime/http-timing.js +59 -0
- package/dist/data/runtime/http-timing.js.map +1 -0
- package/dist/data/runtime/http-transport.d.ts +1 -0
- package/dist/data/runtime/http-transport.js +27 -0
- package/dist/data/runtime/http-transport.js.map +1 -0
- package/dist/data/schemas/catalog.v1.json +46 -0
- package/dist/data/schemas/discovery.v1.json +12 -0
- package/dist/data/schemas/error.v1.json +1 -0
- package/dist/data/schemas/manifest.v1.json +19 -1
- package/dist/research/orchestration.js +233 -10
- package/dist/research/orchestration.js.map +1 -1
- package/dist/research/setup-command.js +37 -6
- package/dist/research/setup-command.js.map +1 -1
- package/dist/research/workspace/audit-bundle.js +28 -8
- package/dist/research/workspace/audit-bundle.js.map +1 -1
- package/dist/research/workspace/broker.js +25 -4
- package/dist/research/workspace/broker.js.map +1 -1
- package/dist/research/workspace/constants.d.ts +1 -0
- package/dist/research/workspace/constants.js +1 -0
- package/dist/research/workspace/constants.js.map +1 -1
- package/dist/research/workspace/content-evidence.d.ts +1 -0
- package/dist/research/workspace/content-evidence.js +1 -1
- package/dist/research/workspace/content-evidence.js.map +1 -1
- package/dist/research/workspace/credentials.js +2 -1
- package/dist/research/workspace/credentials.js.map +1 -1
- package/dist/research/workspace/data-evidence-adapter.d.ts +71 -0
- package/dist/research/workspace/data-evidence-adapter.js +269 -59
- package/dist/research/workspace/data-evidence-adapter.js.map +1 -1
- package/dist/research/workspace/data-evidence-view.d.ts +58 -0
- package/dist/research/workspace/data-evidence-view.js +411 -0
- package/dist/research/workspace/data-evidence-view.js.map +1 -0
- package/dist/research/workspace/evidence-ledger.js +32 -29
- package/dist/research/workspace/evidence-ledger.js.map +1 -1
- package/dist/research/workspace/evidence.d.ts +28 -0
- package/dist/research/workspace/evidence.js +48 -0
- package/dist/research/workspace/evidence.js.map +1 -1
- package/dist/research/workspace/executor.d.ts +27 -1
- package/dist/research/workspace/executor.js +180 -20
- package/dist/research/workspace/executor.js.map +1 -1
- package/dist/research/workspace/external-skills.d.ts +1 -0
- package/dist/research/workspace/external-skills.js +4 -2
- package/dist/research/workspace/external-skills.js.map +1 -1
- package/dist/research/workspace/investigation-attempt.d.ts +159 -0
- package/dist/research/workspace/investigation-attempt.js +619 -0
- package/dist/research/workspace/investigation-attempt.js.map +1 -0
- package/dist/research/workspace/investigation-audit.d.ts +20 -0
- package/dist/research/workspace/investigation-audit.js +306 -0
- package/dist/research/workspace/investigation-audit.js.map +1 -0
- package/dist/research/workspace/investigation-candidate.d.ts +84 -0
- package/dist/research/workspace/investigation-candidate.js +156 -0
- package/dist/research/workspace/investigation-candidate.js.map +1 -0
- package/dist/research/workspace/investigation-certification.d.ts +39 -0
- package/dist/research/workspace/investigation-certification.js +152 -0
- package/dist/research/workspace/investigation-certification.js.map +1 -0
- package/dist/research/workspace/investigation-close.d.ts +47 -0
- package/dist/research/workspace/investigation-close.js +153 -0
- package/dist/research/workspace/investigation-close.js.map +1 -0
- package/dist/research/workspace/investigation-lineage.d.ts +8 -0
- package/dist/research/workspace/investigation-lineage.js +10 -0
- package/dist/research/workspace/investigation-lineage.js.map +1 -0
- package/dist/research/workspace/investigation-observer.d.ts +22 -0
- package/dist/research/workspace/investigation-observer.js +122 -0
- package/dist/research/workspace/investigation-observer.js.map +1 -0
- package/dist/research/workspace/investigation-promotion.d.ts +129 -0
- package/dist/research/workspace/investigation-promotion.js +316 -0
- package/dist/research/workspace/investigation-promotion.js.map +1 -0
- package/dist/research/workspace/investigation-requirements.d.ts +5 -0
- package/dist/research/workspace/investigation-requirements.js +32 -0
- package/dist/research/workspace/investigation-requirements.js.map +1 -0
- package/dist/research/workspace/investigation-resources.d.ts +12 -0
- package/dist/research/workspace/investigation-resources.js +78 -0
- package/dist/research/workspace/investigation-resources.js.map +1 -0
- package/dist/research/workspace/investigation-source-export.d.ts +6 -0
- package/dist/research/workspace/investigation-source-export.js +68 -0
- package/dist/research/workspace/investigation-source-export.js.map +1 -0
- package/dist/research/workspace/investigation-store.d.ts +9 -0
- package/dist/research/workspace/investigation-store.js +55 -0
- package/dist/research/workspace/investigation-store.js.map +1 -0
- package/dist/research/workspace/investigation.d.ts +424 -0
- package/dist/research/workspace/investigation.js +716 -0
- package/dist/research/workspace/investigation.js.map +1 -0
- package/dist/research/workspace/native-process-supervisor.d.ts +1 -0
- package/dist/research/workspace/native-process-supervisor.js +97 -0
- package/dist/research/workspace/native-process-supervisor.js.map +1 -0
- package/dist/research/workspace/native-run.d.ts +79 -6
- package/dist/research/workspace/native-run.js +293 -51
- package/dist/research/workspace/native-run.js.map +1 -1
- package/dist/research/workspace/preflight.d.ts +3 -0
- package/dist/research/workspace/preflight.js +11 -6
- package/dist/research/workspace/preflight.js.map +1 -1
- package/dist/research/workspace/project-authority.js +2 -0
- package/dist/research/workspace/project-authority.js.map +1 -1
- package/dist/research/workspace/project-budget.d.ts +41 -0
- package/dist/research/workspace/project-budget.js +265 -0
- package/dist/research/workspace/project-budget.js.map +1 -0
- package/dist/research/workspace/project-mutations.d.ts +1 -1
- package/dist/research/workspace/project-mutations.js +27 -3
- package/dist/research/workspace/project-mutations.js.map +1 -1
- package/dist/research/workspace/projects.d.ts +66 -0
- package/dist/research/workspace/projects.js +181 -3
- package/dist/research/workspace/projects.js.map +1 -1
- package/dist/research/workspace/provider-budget.d.ts +3 -0
- package/dist/research/workspace/provider-budget.js +45 -0
- package/dist/research/workspace/provider-budget.js.map +1 -0
- package/dist/research/workspace/provider-state.d.ts +1 -0
- package/dist/research/workspace/provider-state.js +20 -0
- package/dist/research/workspace/provider-state.js.map +1 -0
- package/dist/research/workspace/publication-lineage.d.ts +34 -0
- package/dist/research/workspace/publication-lineage.js +165 -0
- package/dist/research/workspace/publication-lineage.js.map +1 -0
- package/dist/research/workspace/publication-workflow.d.ts +24 -0
- package/dist/research/workspace/publication-workflow.js +118 -14
- package/dist/research/workspace/publication-workflow.js.map +1 -1
- package/dist/research/workspace/review-executor.d.ts +6 -1
- package/dist/research/workspace/review-executor.js +17 -3
- package/dist/research/workspace/review-executor.js.map +1 -1
- package/dist/research/workspace/runtime.d.ts +12 -1
- package/dist/research/workspace/runtime.js +143 -19
- package/dist/research/workspace/runtime.js.map +1 -1
- package/dist/research/workspace/scientific-amendment.d.ts +103 -0
- package/dist/research/workspace/scientific-amendment.js +537 -0
- package/dist/research/workspace/scientific-amendment.js.map +1 -0
- package/dist/research/workspace/scientific-fulfillment-audit.d.ts +20 -2
- package/dist/research/workspace/scientific-fulfillment-audit.js +87 -7
- package/dist/research/workspace/scientific-fulfillment-audit.js.map +1 -1
- package/dist/research/workspace/scientific-fulfillment.d.ts +10 -1
- package/dist/research/workspace/scientific-fulfillment.js +20 -6
- package/dist/research/workspace/scientific-fulfillment.js.map +1 -1
- package/dist/research/workspace/scientific-review-execution.js +162 -22
- package/dist/research/workspace/scientific-review-execution.js.map +1 -1
- package/dist/research/workspace/scientific-review.d.ts +2 -1
- package/dist/research/workspace/scientific-review.js +33 -2
- package/dist/research/workspace/scientific-review.js.map +1 -1
- package/dist/research/workspace/setup-catalog.js +13 -13
- package/dist/research/workspace/setup-catalog.js.map +1 -1
- package/dist/research/workspace/setup-release.d.ts +22 -0
- package/dist/research/workspace/setup-release.js +426 -0
- package/dist/research/workspace/setup-release.js.map +1 -0
- package/dist/research/workspace/setup-runtime-integrity.d.ts +2 -0
- package/dist/research/workspace/setup-runtime-integrity.js +31 -0
- package/dist/research/workspace/setup-runtime-integrity.js.map +1 -0
- package/dist/research/workspace/setup-upgrade.d.ts +17 -0
- package/dist/research/workspace/setup-upgrade.js +784 -0
- package/dist/research/workspace/setup-upgrade.js.map +1 -0
- package/dist/research/workspace/setup.d.ts +31 -39
- package/dist/research/workspace/setup.js +228 -19
- package/dist/research/workspace/setup.js.map +1 -1
- package/dist/research/workspace/task-acceptance.d.ts +8 -1
- package/dist/research/workspace/task-acceptance.js +37 -5
- package/dist/research/workspace/task-acceptance.js.map +1 -1
- package/dist/research/workspace/task-audit.d.ts +5 -1
- package/dist/research/workspace/task-audit.js +33 -18
- package/dist/research/workspace/task-audit.js.map +1 -1
- package/dist/research/workspace/types.d.ts +44 -0
- package/dist/research/workspace/workspace.js +34 -4
- package/dist/research/workspace/workspace.js.map +1 -1
- package/package.json +5 -4
|
@@ -0,0 +1,716 @@
|
|
|
1
|
+
import { localInvestigationReadStore } from "./investigation-store.js";
|
|
2
|
+
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
3
|
+
import { lstat, mkdir, readFile } from "node:fs/promises";
|
|
4
|
+
import { isAbsolute, join } from "node:path";
|
|
5
|
+
import { realpath } from "node:fs/promises";
|
|
6
|
+
import { CliError } from "../../errors.js";
|
|
7
|
+
import { loadCurrentEvidenceSnapshot } from "./acquisition.js";
|
|
8
|
+
import { appendJournalEvent, readVerifiedJournal } from "./journal.js";
|
|
9
|
+
import { assertProjectAuthority, projectAuthorityIndex, projectAuthority, } from "./project-authority.js";
|
|
10
|
+
import { loadProject } from "./projects.js";
|
|
11
|
+
import { reserveProjectCost, remainingProjectCostUsd } from "./project-budget.js";
|
|
12
|
+
import { beginProjectMutation, prepareProjectMutation, projectMutationBinding, settleProjectMutation, } from "./project-mutations.js";
|
|
13
|
+
import { assertResearchPolicyBinding } from "./research-policy.js";
|
|
14
|
+
import { loadScientificFulfillmentView } from "./scientific-fulfillment.js";
|
|
15
|
+
import { storeRunObject } from "./native-run.js";
|
|
16
|
+
import { loadProjectTask, taskRequirementSha256, writeTaskObject } from "./task-contract.js";
|
|
17
|
+
import { configuredResearchSecrets, sanitizeResearchValue } from "./sanitization.js";
|
|
18
|
+
import { canonicalJson, isObject, sha256File, sha256Text, workspacePaths, writeTextAtomic, } from "./storage.js";
|
|
19
|
+
import { loadWorkspaceConfig, withWorkspaceLock } from "./workspace.js";
|
|
20
|
+
const HASH = /^[a-f0-9]{64}$/;
|
|
21
|
+
const ID = "^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$";
|
|
22
|
+
const deniedEffects = ["dependency-install", "external-write", "holdout", "network"];
|
|
23
|
+
const idSchema = { type: "string", pattern: ID };
|
|
24
|
+
const textSchema = { type: "string", minLength: 1, maxLength: 4000 };
|
|
25
|
+
const inputSchema = {
|
|
26
|
+
type: "object",
|
|
27
|
+
additionalProperties: false,
|
|
28
|
+
required: [
|
|
29
|
+
"schemaVersion",
|
|
30
|
+
"investigationId",
|
|
31
|
+
"requirementId",
|
|
32
|
+
"requirementSha256",
|
|
33
|
+
"objective",
|
|
34
|
+
"canonicalInputs",
|
|
35
|
+
"programs",
|
|
36
|
+
"options",
|
|
37
|
+
"limits",
|
|
38
|
+
"deniedEffects",
|
|
39
|
+
],
|
|
40
|
+
properties: {
|
|
41
|
+
schemaVersion: { const: 1 },
|
|
42
|
+
investigationId: idSchema,
|
|
43
|
+
requirementId: { type: "string", pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$" },
|
|
44
|
+
requirementSha256: { type: "string", pattern: HASH.source },
|
|
45
|
+
objective: { type: "string", minLength: 8, maxLength: 4000 },
|
|
46
|
+
canonicalInputs: {
|
|
47
|
+
type: "array",
|
|
48
|
+
minItems: 1,
|
|
49
|
+
maxItems: 256,
|
|
50
|
+
items: {
|
|
51
|
+
type: "object",
|
|
52
|
+
additionalProperties: false,
|
|
53
|
+
required: ["id", "artifactId", "sha256"],
|
|
54
|
+
properties: {
|
|
55
|
+
id: idSchema,
|
|
56
|
+
artifactId: idSchema,
|
|
57
|
+
sha256: { type: "string", pattern: HASH.source },
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
programs: {
|
|
62
|
+
type: "array",
|
|
63
|
+
minItems: 1,
|
|
64
|
+
maxItems: 16,
|
|
65
|
+
items: {
|
|
66
|
+
type: "object",
|
|
67
|
+
additionalProperties: false,
|
|
68
|
+
required: [
|
|
69
|
+
"id",
|
|
70
|
+
"runtime",
|
|
71
|
+
"scriptPath",
|
|
72
|
+
"environmentLockPath",
|
|
73
|
+
"arguments",
|
|
74
|
+
"outputs",
|
|
75
|
+
"diagnosticOutputId",
|
|
76
|
+
"telemetry",
|
|
77
|
+
],
|
|
78
|
+
properties: {
|
|
79
|
+
id: idSchema,
|
|
80
|
+
runtime: {
|
|
81
|
+
type: "object",
|
|
82
|
+
additionalProperties: false,
|
|
83
|
+
required: ["kind", "path"],
|
|
84
|
+
properties: { kind: { enum: ["node", "python"] }, path: textSchema },
|
|
85
|
+
},
|
|
86
|
+
scriptPath: textSchema,
|
|
87
|
+
environmentLockPath: textSchema,
|
|
88
|
+
arguments: { type: "array", maxItems: 256, items: textSchema },
|
|
89
|
+
outputs: {
|
|
90
|
+
type: "array",
|
|
91
|
+
minItems: 1,
|
|
92
|
+
maxItems: 32,
|
|
93
|
+
items: {
|
|
94
|
+
type: "object",
|
|
95
|
+
additionalProperties: false,
|
|
96
|
+
required: ["id", "fileName", "mediaType"],
|
|
97
|
+
properties: {
|
|
98
|
+
id: idSchema,
|
|
99
|
+
fileName: idSchema,
|
|
100
|
+
mediaType: { type: "string", pattern: "^(?:text|application|image)/[a-z0-9.+-]+$" },
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
diagnosticOutputId: idSchema,
|
|
105
|
+
telemetry: {
|
|
106
|
+
type: "object",
|
|
107
|
+
additionalProperties: false,
|
|
108
|
+
required: ["requiredMetrics", "requiredStatuses"],
|
|
109
|
+
properties: {
|
|
110
|
+
requiredMetrics: { type: "array", maxItems: 128, uniqueItems: true, items: idSchema },
|
|
111
|
+
requiredStatuses: {
|
|
112
|
+
type: "array",
|
|
113
|
+
maxItems: 128,
|
|
114
|
+
uniqueItems: true,
|
|
115
|
+
items: idSchema,
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
options: {
|
|
123
|
+
type: "array",
|
|
124
|
+
maxItems: 64,
|
|
125
|
+
items: {
|
|
126
|
+
oneOf: [
|
|
127
|
+
{
|
|
128
|
+
type: "object",
|
|
129
|
+
additionalProperties: false,
|
|
130
|
+
required: ["id", "kind", "minimum", "maximum"],
|
|
131
|
+
properties: {
|
|
132
|
+
id: idSchema,
|
|
133
|
+
kind: { enum: ["integer", "number"] },
|
|
134
|
+
minimum: { type: "number" },
|
|
135
|
+
maximum: { type: "number" },
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
type: "object",
|
|
140
|
+
additionalProperties: false,
|
|
141
|
+
required: ["id", "kind", "values"],
|
|
142
|
+
properties: {
|
|
143
|
+
id: idSchema,
|
|
144
|
+
kind: { const: "enum" },
|
|
145
|
+
values: {
|
|
146
|
+
type: "array",
|
|
147
|
+
minItems: 1,
|
|
148
|
+
maxItems: 128,
|
|
149
|
+
uniqueItems: true,
|
|
150
|
+
items: { type: "string", minLength: 1, maxLength: 256 },
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
limits: {
|
|
158
|
+
type: "object",
|
|
159
|
+
additionalProperties: false,
|
|
160
|
+
required: [
|
|
161
|
+
"maxRuns",
|
|
162
|
+
"maxWallSeconds",
|
|
163
|
+
"maxRunSeconds",
|
|
164
|
+
"maxCostUsd",
|
|
165
|
+
"maxRunCostUsd",
|
|
166
|
+
"maxOutputBytes",
|
|
167
|
+
"maxTotalOutputBytes",
|
|
168
|
+
],
|
|
169
|
+
properties: {
|
|
170
|
+
maxRuns: { type: "integer", minimum: 1, maximum: 10000 },
|
|
171
|
+
maxWallSeconds: { type: "integer", minimum: 1, maximum: 172800 },
|
|
172
|
+
maxRunSeconds: { type: "integer", minimum: 1, maximum: 172800 },
|
|
173
|
+
maxCostUsd: { type: "number", minimum: 0 },
|
|
174
|
+
maxRunCostUsd: { type: "number", minimum: 0 },
|
|
175
|
+
maxOutputBytes: { type: "integer", minimum: 1, maximum: 536870912 },
|
|
176
|
+
maxTotalOutputBytes: { type: "integer", minimum: 1, maximum: Number.MAX_SAFE_INTEGER },
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
deniedEffects: {
|
|
180
|
+
type: "array",
|
|
181
|
+
minItems: 4,
|
|
182
|
+
maxItems: 4,
|
|
183
|
+
uniqueItems: true,
|
|
184
|
+
items: { enum: deniedEffects },
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
const validateInput = new Ajv2020({ strict: false, allErrors: true }).compile(inputSchema);
|
|
189
|
+
export function investigationInputSchema() {
|
|
190
|
+
return structuredClone(inputSchema);
|
|
191
|
+
}
|
|
192
|
+
function failure(message, code = "RESEARCH_INVESTIGATION_INVALID", exitCode = 2) {
|
|
193
|
+
return new CliError(message, { code, exitCode });
|
|
194
|
+
}
|
|
195
|
+
function conflict() {
|
|
196
|
+
return failure("Investigation identity, authorization or exact inputs changed. Preserve history and inspect the current envelope before preparing a separately approved change.", "RESEARCH_INVESTIGATION_CONFLICT", 3);
|
|
197
|
+
}
|
|
198
|
+
function parseInput(value) {
|
|
199
|
+
if (!validateInput(value) ||
|
|
200
|
+
canonicalJson(sanitizeResearchValue(value, configuredResearchSecrets(process.env))) !==
|
|
201
|
+
canonicalJson(value))
|
|
202
|
+
throw failure("Investigation must match the closed, secret-free envelope schema.");
|
|
203
|
+
const input = structuredClone(value);
|
|
204
|
+
for (const items of [input.canonicalInputs, input.programs, input.options])
|
|
205
|
+
if (new Set(items.map((item) => item.id)).size !== items.length)
|
|
206
|
+
throw failure("Investigation input, program and option IDs must be unique.");
|
|
207
|
+
if (input.objective.trim().length < 8 ||
|
|
208
|
+
input.limits.maxRunSeconds > input.limits.maxWallSeconds ||
|
|
209
|
+
input.limits.maxRunCostUsd > input.limits.maxCostUsd ||
|
|
210
|
+
input.limits.maxOutputBytes > input.limits.maxTotalOutputBytes)
|
|
211
|
+
throw failure("The per-run limits must fit the finite investigation envelope.");
|
|
212
|
+
for (const option of input.options)
|
|
213
|
+
if (option.kind !== "enum" &&
|
|
214
|
+
(option.minimum > option.maximum ||
|
|
215
|
+
(option.kind === "integer" &&
|
|
216
|
+
(!Number.isSafeInteger(option.minimum) || !Number.isSafeInteger(option.maximum)))))
|
|
217
|
+
throw failure("Numerical option bounds are invalid.");
|
|
218
|
+
for (const option of input.options)
|
|
219
|
+
if (option.kind === "enum" && option.values.some((value) => /[{}\r\n\0]/.test(value)))
|
|
220
|
+
throw failure("Enum options must be literal bounded numerical configuration choices.");
|
|
221
|
+
for (const program of input.programs) {
|
|
222
|
+
if (new Set(program.outputs.map((output) => output.id)).size !== program.outputs.length ||
|
|
223
|
+
new Set(program.outputs.map((output) => output.fileName)).size !== program.outputs.length ||
|
|
224
|
+
!program.outputs.some((output) => output.id === program.diagnosticOutputId && output.mediaType === "application/json"))
|
|
225
|
+
throw failure("Each program needs unique outputs and one declared JSON diagnostic output.");
|
|
226
|
+
for (const argument of program.arguments) {
|
|
227
|
+
if (/[\r\n\0]/.test(argument) ||
|
|
228
|
+
/(?:^|=)(?:\/|[A-Za-z]:[\\/])/.test(argument) ||
|
|
229
|
+
/--(?:api-key|auth-token|password|cookie)(?:=|$)/i.test(argument) ||
|
|
230
|
+
/[{}]/.test(argument.replace(/\{(?:input|output|option):[^{}]+\}/g, "")))
|
|
231
|
+
throw failure("Program arguments must be bounded single argv values.");
|
|
232
|
+
for (const match of argument.matchAll(/\{([^{}]+)\}/g)) {
|
|
233
|
+
const [kind, id, ...rest] = match[1].split(":");
|
|
234
|
+
const items = kind === "input"
|
|
235
|
+
? input.canonicalInputs
|
|
236
|
+
: kind === "output"
|
|
237
|
+
? program.outputs
|
|
238
|
+
: kind === "option"
|
|
239
|
+
? input.options
|
|
240
|
+
: [];
|
|
241
|
+
if (rest.length || !items.some((item) => item.id === id))
|
|
242
|
+
throw failure("Program arguments reference an undeclared input, output or numerical option.");
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
input.deniedEffects.sort();
|
|
247
|
+
return input;
|
|
248
|
+
}
|
|
249
|
+
async function regularSource(path, maxBytes = 16 * 1024 * 1024) {
|
|
250
|
+
if (!isAbsolute(path))
|
|
251
|
+
throw failure("Program, runtime and source paths must be absolute.");
|
|
252
|
+
const info = await lstat(path).catch(() => null);
|
|
253
|
+
if (!info?.isFile() || info.isSymbolicLink() || info.size > maxBytes)
|
|
254
|
+
throw failure("Program and confirmation sources must be bounded regular files.");
|
|
255
|
+
return { path, sha256: await sha256File(path), bytes: info.size };
|
|
256
|
+
}
|
|
257
|
+
export async function planInvestigation(root, projectId, value) {
|
|
258
|
+
const input = parseInput(value);
|
|
259
|
+
const project = await loadProject(root, projectId);
|
|
260
|
+
const events = await readVerifiedJournal(workspacePaths(root).journal);
|
|
261
|
+
assertProjectAuthority(project, projectAuthorityIndex(events));
|
|
262
|
+
const task = await loadProjectTask(root, projectId, events);
|
|
263
|
+
const requirement = task?.current.requirements.find((item) => item.id === input.requirementId && taskRequirementSha256(item) === input.requirementSha256);
|
|
264
|
+
if (!requirement ||
|
|
265
|
+
requirement.checkKind !== "computation" ||
|
|
266
|
+
project.handoff.state !== "agent-actionable" ||
|
|
267
|
+
project.status === "complete" ||
|
|
268
|
+
project.packages.find((item) => item.stage === "acquire")?.status !== "complete" ||
|
|
269
|
+
project.packages.find((item) => item.stage === "review")?.status === "complete")
|
|
270
|
+
throw failure("An investigation needs an actionable computational requirement and frozen acquisition before final review.", "RESEARCH_INVESTIGATION_UNAVAILABLE", 3);
|
|
271
|
+
if (project.publicationPolicy)
|
|
272
|
+
await assertResearchPolicyBinding(root, project.publicationPolicy);
|
|
273
|
+
const design = project.scientificDesign
|
|
274
|
+
? await loadScientificFulfillmentView(root, project, undefined, events)
|
|
275
|
+
: null;
|
|
276
|
+
const snapshot = await loadCurrentEvidenceSnapshot(root, projectId);
|
|
277
|
+
for (const inputBinding of input.canonicalInputs)
|
|
278
|
+
if (!snapshot.artifacts.some((artifact) => artifact.artifactId === inputBinding.artifactId &&
|
|
279
|
+
artifact.sha256 === inputBinding.sha256))
|
|
280
|
+
throw conflict();
|
|
281
|
+
const programs = [];
|
|
282
|
+
const runtimeHashes = new Map();
|
|
283
|
+
for (const program of input.programs) {
|
|
284
|
+
if (!isAbsolute(program.runtime.path))
|
|
285
|
+
throw failure("Select an absolute existing runtime.");
|
|
286
|
+
const runtimePath = await realpath(program.runtime.path);
|
|
287
|
+
if (!(await lstat(runtimePath)).isFile())
|
|
288
|
+
throw failure("The runtime is not a regular executable.");
|
|
289
|
+
const runtimeSha = runtimeHashes.get(runtimePath) ?? (await sha256File(runtimePath));
|
|
290
|
+
runtimeHashes.set(runtimePath, runtimeSha);
|
|
291
|
+
const script = await regularSource(program.scriptPath);
|
|
292
|
+
const environment = await regularSource(program.environmentLockPath);
|
|
293
|
+
programs.push({
|
|
294
|
+
id: program.id,
|
|
295
|
+
runtime: {
|
|
296
|
+
kind: program.runtime.kind,
|
|
297
|
+
binarySha256: runtimeSha,
|
|
298
|
+
pathSha256: sha256Text(program.runtime.path),
|
|
299
|
+
},
|
|
300
|
+
scriptSha256: script.sha256,
|
|
301
|
+
environmentLockSha256: environment.sha256,
|
|
302
|
+
arguments: program.arguments,
|
|
303
|
+
outputs: program.outputs,
|
|
304
|
+
diagnosticOutputId: program.diagnosticOutputId,
|
|
305
|
+
telemetry: program.telemetry,
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
const config = await loadWorkspaceConfig(root);
|
|
309
|
+
if (input.limits.maxCostUsd > remainingProjectCostUsd(project, config) + 1e-9)
|
|
310
|
+
throw failure("The investigation does not fit the remaining project cost ceiling.", "RESEARCH_INVESTIGATION_BUDGET_EXCEEDED", 3);
|
|
311
|
+
const core = {
|
|
312
|
+
schemaVersion: 1,
|
|
313
|
+
kind: "tiangong-investigation-plan",
|
|
314
|
+
projectId,
|
|
315
|
+
investigationId: input.investigationId,
|
|
316
|
+
requestSha256: sha256Text(canonicalJson(input)),
|
|
317
|
+
requirementId: input.requirementId,
|
|
318
|
+
requirementSha256: input.requirementSha256,
|
|
319
|
+
questionSha256: sha256Text(project.question),
|
|
320
|
+
designSha256: project.scientificDesign?.designSha256 ?? null,
|
|
321
|
+
effectiveDesignSha256: design?.effectiveSha256 ?? null,
|
|
322
|
+
policySha256: project.publicationPolicy?.resolvedPolicySha256 ?? null,
|
|
323
|
+
acquisitionSnapshotSha256: snapshot.snapshotSha256,
|
|
324
|
+
objective: input.objective,
|
|
325
|
+
canonicalInputs: input.canonicalInputs,
|
|
326
|
+
programs,
|
|
327
|
+
options: input.options,
|
|
328
|
+
limits: input.limits,
|
|
329
|
+
deniedEffects: input.deniedEffects,
|
|
330
|
+
needsProjectBudget: !project.budget,
|
|
331
|
+
purpose: "diagnostic-candidate-only",
|
|
332
|
+
};
|
|
333
|
+
return { ...core, planSha256: sha256Text(canonicalJson(core)) };
|
|
334
|
+
}
|
|
335
|
+
async function routingPath(root, projectId, hash) {
|
|
336
|
+
if (!/^[a-z0-9][a-z0-9-]{2,63}$/.test(projectId) || !HASH.test(hash))
|
|
337
|
+
throw conflict();
|
|
338
|
+
let directory = workspacePaths(root).control;
|
|
339
|
+
for (const part of ["projects", projectId, "native", "investigation-routing"]) {
|
|
340
|
+
directory = join(directory, part);
|
|
341
|
+
let info = await lstat(directory).catch(() => null);
|
|
342
|
+
if (!info) {
|
|
343
|
+
await mkdir(directory, { mode: 0o700 });
|
|
344
|
+
info = await lstat(directory);
|
|
345
|
+
}
|
|
346
|
+
if (!info.isDirectory() || info.isSymbolicLink())
|
|
347
|
+
throw conflict();
|
|
348
|
+
}
|
|
349
|
+
return join(directory, `${hash}.json`);
|
|
350
|
+
}
|
|
351
|
+
export async function loadInvestigation(root, projectId, id, knownEvents, store = localInvestigationReadStore(root)) {
|
|
352
|
+
const events = knownEvents ?? (await readVerifiedJournal(workspacePaths(root).journal));
|
|
353
|
+
const event = events.findLast((item) => item.scope === projectId &&
|
|
354
|
+
item.type === "investigation.approved" &&
|
|
355
|
+
item.payload.investigationId === id);
|
|
356
|
+
if (!event)
|
|
357
|
+
throw failure("Investigation has no committed approval.", "RESEARCH_INVESTIGATION_NOT_AUTHORIZED", 3);
|
|
358
|
+
const record = await store.readTask(projectId, "investigations", String(event.payload.recordSha256), "recordSha256");
|
|
359
|
+
if (!isObject(record) ||
|
|
360
|
+
!isObject(record.plan) ||
|
|
361
|
+
!isObject(record.scopeAuthorization) ||
|
|
362
|
+
!Array.isArray(record.programs) ||
|
|
363
|
+
!Array.isArray(record.plan.programs) ||
|
|
364
|
+
record.kind !== "tiangong-investigation" ||
|
|
365
|
+
record.schemaVersion !== 1 ||
|
|
366
|
+
record.projectId !== projectId ||
|
|
367
|
+
record.investigationId !== id ||
|
|
368
|
+
record.plan.planSha256 !== event.payload.planSha256 ||
|
|
369
|
+
record.scopeAuthorization.planSha256 !== record.plan.planSha256)
|
|
370
|
+
throw conflict();
|
|
371
|
+
if (record.plan.kind !== "tiangong-investigation-plan" ||
|
|
372
|
+
record.plan.schemaVersion !== 1 ||
|
|
373
|
+
record.plan.projectId !== projectId ||
|
|
374
|
+
record.plan.investigationId !== id ||
|
|
375
|
+
record.plan.purpose !== "diagnostic-candidate-only" ||
|
|
376
|
+
record.scopeAuthorization.kind !== "operator-confirmation" ||
|
|
377
|
+
record.scopeAuthorization.sourceSha256 !== record.scopeAuthorization.source?.sha256 ||
|
|
378
|
+
!HASH.test(record.routingSha256) ||
|
|
379
|
+
!HASH.test(record.plan.requestSha256) ||
|
|
380
|
+
record.programs.length !== record.plan.programs.length ||
|
|
381
|
+
new Set(record.programs.map((p) => p.id)).size !== record.programs.length ||
|
|
382
|
+
record.programs.some((p) => {
|
|
383
|
+
const planned = record.plan.programs.find((item) => item.id === p.id);
|
|
384
|
+
return (!planned ||
|
|
385
|
+
planned.scriptSha256 !== p.script?.sha256 ||
|
|
386
|
+
planned.environmentLockSha256 !== p.environmentLock?.sha256 ||
|
|
387
|
+
!HASH.test(planned.runtime?.binarySha256) ||
|
|
388
|
+
!HASH.test(planned.runtime?.pathSha256));
|
|
389
|
+
}))
|
|
390
|
+
throw conflict();
|
|
391
|
+
// Validate the portable envelope with the same closed input schema. Host paths
|
|
392
|
+
// are deliberately absent from the definition; only local routing contains them.
|
|
393
|
+
parseInput({
|
|
394
|
+
schemaVersion: 1,
|
|
395
|
+
investigationId: id,
|
|
396
|
+
requirementId: record.plan.requirementId,
|
|
397
|
+
requirementSha256: record.plan.requirementSha256,
|
|
398
|
+
objective: record.plan.objective,
|
|
399
|
+
canonicalInputs: record.plan.canonicalInputs,
|
|
400
|
+
options: record.plan.options,
|
|
401
|
+
limits: record.plan.limits,
|
|
402
|
+
deniedEffects: record.plan.deniedEffects,
|
|
403
|
+
programs: record.plan.programs.map((p) => ({
|
|
404
|
+
id: p.id,
|
|
405
|
+
runtime: { kind: p.runtime.kind, path: "/runtime" },
|
|
406
|
+
scriptPath: "/script",
|
|
407
|
+
environmentLockPath: "/environment",
|
|
408
|
+
arguments: p.arguments,
|
|
409
|
+
outputs: p.outputs,
|
|
410
|
+
diagnosticOutputId: p.diagnosticOutputId,
|
|
411
|
+
telemetry: p.telemetry,
|
|
412
|
+
})),
|
|
413
|
+
});
|
|
414
|
+
const { planSha256, ...planCore } = record.plan;
|
|
415
|
+
if (sha256Text(canonicalJson(planCore)) !== planSha256)
|
|
416
|
+
throw conflict();
|
|
417
|
+
for (const object of [
|
|
418
|
+
record.scopeAuthorization.source,
|
|
419
|
+
...record.programs.flatMap((program) => [program.script, program.environmentLock]),
|
|
420
|
+
]) {
|
|
421
|
+
if (object.path !== `task/run-objects/${object.sha256}`)
|
|
422
|
+
throw conflict();
|
|
423
|
+
await store.verifyBlob(projectId, object);
|
|
424
|
+
}
|
|
425
|
+
return record;
|
|
426
|
+
}
|
|
427
|
+
export async function approveInvestigation(root, projectId, value, confirmation, sourcePath) {
|
|
428
|
+
const input = parseInput(value);
|
|
429
|
+
if (!confirmation || !HASH.test(confirmation) || !sourcePath)
|
|
430
|
+
throw failure("Approve the exact plan with --confirm and its actual --authorization-source.", "RESEARCH_INVESTIGATION_CONFIRMATION_REQUIRED");
|
|
431
|
+
await regularSource(sourcePath, 65536);
|
|
432
|
+
const sourceBytes = await readFile(sourcePath);
|
|
433
|
+
let sourceText;
|
|
434
|
+
try {
|
|
435
|
+
sourceText = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(sourceBytes);
|
|
436
|
+
}
|
|
437
|
+
catch {
|
|
438
|
+
throw failure("Confirmation must be UTF-8 text.");
|
|
439
|
+
}
|
|
440
|
+
if (sourceText.trim().length < 8 ||
|
|
441
|
+
sanitizeResearchValue(sourceText, configuredResearchSecrets(process.env)) !== sourceText)
|
|
442
|
+
throw failure("Confirmation must be bounded non-secret supplied text.");
|
|
443
|
+
return withWorkspaceLock(root, "research.investigation.approve", async () => {
|
|
444
|
+
const project = await loadProject(root, projectId);
|
|
445
|
+
const events = await readVerifiedJournal(workspacePaths(root).journal);
|
|
446
|
+
assertProjectAuthority(project, projectAuthorityIndex(events));
|
|
447
|
+
const existing = events.find((event) => event.scope === projectId &&
|
|
448
|
+
event.type === "investigation.approved" &&
|
|
449
|
+
event.payload.investigationId === input.investigationId);
|
|
450
|
+
if (existing) {
|
|
451
|
+
const record = await loadInvestigation(root, projectId, input.investigationId, events);
|
|
452
|
+
if (record.plan.requestSha256 !== sha256Text(canonicalJson(input)) ||
|
|
453
|
+
record.plan.planSha256 !== confirmation ||
|
|
454
|
+
record.scopeAuthorization.sourceSha256 !== sha256Text(sourceText))
|
|
455
|
+
throw conflict();
|
|
456
|
+
return record;
|
|
457
|
+
}
|
|
458
|
+
const plan = await planInvestigation(root, projectId, input);
|
|
459
|
+
if (plan.planSha256 !== confirmation)
|
|
460
|
+
throw conflict();
|
|
461
|
+
if (!project.budget)
|
|
462
|
+
throw failure("Configure the existing numeric project budget before approving an investigation.", "RESEARCH_PROJECT_BUDGET_REQUIRED", 3);
|
|
463
|
+
const programs = [];
|
|
464
|
+
for (const program of input.programs) {
|
|
465
|
+
const script = await storeRunObject(root, projectId, program.scriptPath);
|
|
466
|
+
const environmentLock = await storeRunObject(root, projectId, program.environmentLockPath);
|
|
467
|
+
const expected = plan.programs.find((item) => item.id === program.id);
|
|
468
|
+
if (script.sha256 !== expected.scriptSha256 ||
|
|
469
|
+
environmentLock.sha256 !== expected.environmentLockSha256)
|
|
470
|
+
throw conflict();
|
|
471
|
+
programs.push({ id: program.id, script, environmentLock });
|
|
472
|
+
}
|
|
473
|
+
const source = await storeRunObject(root, projectId, sourcePath);
|
|
474
|
+
if (source.sha256 !== sha256Text(sourceText))
|
|
475
|
+
throw conflict();
|
|
476
|
+
const routing = {
|
|
477
|
+
programs: input.programs.map((program) => ({
|
|
478
|
+
id: program.id,
|
|
479
|
+
runtimePath: program.runtime.path,
|
|
480
|
+
})),
|
|
481
|
+
};
|
|
482
|
+
const routingText = canonicalJson(routing), routingSha256 = sha256Text(routingText);
|
|
483
|
+
const path = await routingPath(root, projectId, routingSha256);
|
|
484
|
+
const info = await lstat(path).catch(() => null);
|
|
485
|
+
if (info) {
|
|
486
|
+
if (!info.isFile() || info.isSymbolicLink() || (await readFile(path, "utf8")) !== routingText)
|
|
487
|
+
throw conflict();
|
|
488
|
+
}
|
|
489
|
+
else
|
|
490
|
+
await writeTextAtomic(path, routingText, 0o600);
|
|
491
|
+
const core = {
|
|
492
|
+
schemaVersion: 1,
|
|
493
|
+
kind: "tiangong-investigation",
|
|
494
|
+
projectId,
|
|
495
|
+
investigationId: input.investigationId,
|
|
496
|
+
plan,
|
|
497
|
+
programs,
|
|
498
|
+
scopeAuthorization: {
|
|
499
|
+
kind: "operator-confirmation",
|
|
500
|
+
planSha256: confirmation,
|
|
501
|
+
sourceSha256: source.sha256,
|
|
502
|
+
source,
|
|
503
|
+
},
|
|
504
|
+
routingSha256,
|
|
505
|
+
approvedAt: new Date().toISOString(),
|
|
506
|
+
};
|
|
507
|
+
const record = {
|
|
508
|
+
...core,
|
|
509
|
+
recordSha256: sha256Text(canonicalJson(core)),
|
|
510
|
+
};
|
|
511
|
+
await writeTaskObject(root, projectId, "investigations", record.recordSha256, record);
|
|
512
|
+
let mutation = await beginProjectMutation(root, "investigation-approval", project, record.recordSha256);
|
|
513
|
+
try {
|
|
514
|
+
reserveProjectCost(project, await loadWorkspaceConfig(root), {
|
|
515
|
+
id: `investigation-${record.recordSha256}`,
|
|
516
|
+
kind: "investigation",
|
|
517
|
+
reference: input.investigationId,
|
|
518
|
+
maxCostUsd: plan.limits.maxCostUsd,
|
|
519
|
+
});
|
|
520
|
+
project.updatedAt = new Date().toISOString();
|
|
521
|
+
mutation = await prepareProjectMutation(root, mutation, project);
|
|
522
|
+
await appendJournalEvent(workspacePaths(root).journal, "investigation.approved", projectId, {
|
|
523
|
+
projectId,
|
|
524
|
+
investigationId: input.investigationId,
|
|
525
|
+
recordSha256: record.recordSha256,
|
|
526
|
+
planSha256: plan.planSha256,
|
|
527
|
+
mutation: projectMutationBinding(mutation),
|
|
528
|
+
});
|
|
529
|
+
await settleProjectMutation(root, mutation);
|
|
530
|
+
}
|
|
531
|
+
catch (error) {
|
|
532
|
+
await settleProjectMutation(root, mutation);
|
|
533
|
+
throw error;
|
|
534
|
+
}
|
|
535
|
+
return record;
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
export async function inspectInvestigation(root, projectId, id) {
|
|
539
|
+
const project = await loadProject(root, projectId);
|
|
540
|
+
const events = await readVerifiedJournal(workspacePaths(root).journal);
|
|
541
|
+
const authority = projectAuthority(project, projectAuthorityIndex(events));
|
|
542
|
+
if (authority.state === "invalid")
|
|
543
|
+
throw conflict();
|
|
544
|
+
const definition = await loadInvestigation(root, projectId, id, events);
|
|
545
|
+
const { investigationAttemptHistory, investigationRemaining, assertInvestigationCurrent } = await import("./investigation-attempt.js");
|
|
546
|
+
const { loadInvestigationClosure } = await import("./investigation-close.js");
|
|
547
|
+
const { loadInvestigationCandidates } = await import("./investigation-candidate.js");
|
|
548
|
+
const { loadInvestigationPromotion } = await import("./investigation-promotion.js");
|
|
549
|
+
const { frozenPromotionView } = await import("./investigation-certification.js");
|
|
550
|
+
const { readNativeRun } = await import("./native-run.js");
|
|
551
|
+
const { investigationWallReservations } = await import("./investigation-resources.js");
|
|
552
|
+
const { inspectInvestigationObserver } = await import("./investigation-observer.js");
|
|
553
|
+
const history = await investigationAttemptHistory(root, projectId, definition, events);
|
|
554
|
+
const remaining = investigationRemaining(definition, history);
|
|
555
|
+
const candidates = await loadInvestigationCandidates(root, projectId, definition, events, history);
|
|
556
|
+
const candidate = candidates.at(-1);
|
|
557
|
+
const closure = await loadInvestigationClosure(root, projectId, definition, history, events);
|
|
558
|
+
const promotions = [];
|
|
559
|
+
for (const event of events) {
|
|
560
|
+
if (event.type !== "investigation.promotion.approved" ||
|
|
561
|
+
event.payload.sourceProjectId !== projectId ||
|
|
562
|
+
event.payload.investigationId !== id)
|
|
563
|
+
continue;
|
|
564
|
+
const promotion = await loadInvestigationPromotion(root, event.scope, String(event.payload.recordSha256), events);
|
|
565
|
+
if (promotion.plan.candidateSha256 !== candidate?.recordSha256)
|
|
566
|
+
continue;
|
|
567
|
+
const started = events.find((e) => e.scope === event.scope &&
|
|
568
|
+
e.type === "project.task.run.started" &&
|
|
569
|
+
e.payload.investigationPromotionSha256 === promotion.recordSha256);
|
|
570
|
+
const completed = started
|
|
571
|
+
? events.find((e) => e.scope === event.scope &&
|
|
572
|
+
e.type === "project.task.run.completed" &&
|
|
573
|
+
e.payload.runId === started.payload.runId)
|
|
574
|
+
: undefined;
|
|
575
|
+
const run = completed
|
|
576
|
+
? await readNativeRun(root, event.scope, String(completed.payload.recordSha256), events)
|
|
577
|
+
: null;
|
|
578
|
+
let frozen = false, scopeCurrent = false;
|
|
579
|
+
const target = event.scope === projectId ? project : await loadProject(root, event.scope);
|
|
580
|
+
try {
|
|
581
|
+
await frozenPromotionView(root, target, promotion, events);
|
|
582
|
+
frozen = true;
|
|
583
|
+
scopeCurrent =
|
|
584
|
+
projectAuthority(target, projectAuthorityIndex(events)).state === "authoritative";
|
|
585
|
+
}
|
|
586
|
+
catch {
|
|
587
|
+
/* The verified historical approval remains visible even when its live scope moved. */
|
|
588
|
+
}
|
|
589
|
+
promotions.push({
|
|
590
|
+
projectId: event.scope,
|
|
591
|
+
recordSha256: promotion.recordSha256,
|
|
592
|
+
runId: started ? String(started.payload.runId) : null,
|
|
593
|
+
runSha256: run?.recordSha256 ?? null,
|
|
594
|
+
certification: run
|
|
595
|
+
? (run.investigationCertification?.status ?? "failed")
|
|
596
|
+
: started
|
|
597
|
+
? "incomplete"
|
|
598
|
+
: "not-started",
|
|
599
|
+
scopeCurrent,
|
|
600
|
+
frozen,
|
|
601
|
+
...(started && !completed
|
|
602
|
+
? {
|
|
603
|
+
observer: await inspectInvestigationObserver(root, event.scope, String(started.payload.requestSha256)),
|
|
604
|
+
}
|
|
605
|
+
: {}),
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
const observers = new Map();
|
|
609
|
+
for (const attempt of history)
|
|
610
|
+
if (!attempt.record)
|
|
611
|
+
observers.set(attempt.start.recordSha256, await inspectInvestigationObserver(root, projectId, attempt.start.recordSha256));
|
|
612
|
+
const passed = promotions.findLast((p) => p.certification === "passed" && p.scopeCurrent);
|
|
613
|
+
const pending = promotions.findLast((p) => p.certification === "incomplete");
|
|
614
|
+
const lastPromotion = promotions.at(-1);
|
|
615
|
+
const reservations = await investigationWallReservations(root, projectId, events);
|
|
616
|
+
const config = await loadWorkspaceConfig(root);
|
|
617
|
+
const availableRunSeconds = Math.max(0, Math.floor(Math.min(definition.plan.limits.maxRunSeconds, remaining.wallSeconds, config.budget.packageMaxWallSeconds.analyze, config.budget.maxWallSeconds - project.usage.wallSeconds - reservations.wallSeconds)));
|
|
618
|
+
let authorizationValid = false, scopeReason = null;
|
|
619
|
+
if (!closure && authority.state === "authoritative") {
|
|
620
|
+
try {
|
|
621
|
+
await assertInvestigationCurrent(root, project, definition, events);
|
|
622
|
+
authorizationValid = true;
|
|
623
|
+
}
|
|
624
|
+
catch (error) {
|
|
625
|
+
scopeReason = error instanceof CliError ? error.code : "RESEARCH_INVESTIGATION_UNAVAILABLE";
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
const exhausted = remaining.runs === 0 ||
|
|
629
|
+
availableRunSeconds < 1 ||
|
|
630
|
+
remaining.costUpperBoundUsd + 1e-9 < definition.plan.limits.maxRunCostUsd ||
|
|
631
|
+
remaining.outputBytes < 1 ||
|
|
632
|
+
history.some((a) => a.record?.process.outputLimitExceeded);
|
|
633
|
+
const unresolved = history.some((a) => !a.record);
|
|
634
|
+
const allowedNextAction = authority.state !== "authoritative"
|
|
635
|
+
? "inspect-authoritative-project"
|
|
636
|
+
: unresolved
|
|
637
|
+
? "inspect-unresolved-attempt"
|
|
638
|
+
: pending
|
|
639
|
+
? "inspect-unresolved-certification"
|
|
640
|
+
: passed
|
|
641
|
+
? project.packages.find((p) => p.stage === "review")?.status === "complete"
|
|
642
|
+
? "inspect-task-acceptance"
|
|
643
|
+
: "independent-review"
|
|
644
|
+
: lastPromotion?.certification === "failed"
|
|
645
|
+
? "new-promotion-approval"
|
|
646
|
+
: lastPromotion?.certification === "not-started"
|
|
647
|
+
? lastPromotion.frozen
|
|
648
|
+
? "certification"
|
|
649
|
+
: "scientific-fulfillment"
|
|
650
|
+
: candidate
|
|
651
|
+
? "promotion-approval"
|
|
652
|
+
: closure
|
|
653
|
+
? "new-investigation-approval"
|
|
654
|
+
: !authorizationValid
|
|
655
|
+
? "new-investigation-approval"
|
|
656
|
+
: exhausted
|
|
657
|
+
? "close-investigation"
|
|
658
|
+
: "attempt";
|
|
659
|
+
return {
|
|
660
|
+
projectId,
|
|
661
|
+
investigationId: id,
|
|
662
|
+
definitionSha256: definition.recordSha256,
|
|
663
|
+
authority,
|
|
664
|
+
status: closure
|
|
665
|
+
? "closed"
|
|
666
|
+
: unresolved
|
|
667
|
+
? "incomplete"
|
|
668
|
+
: candidate
|
|
669
|
+
? "candidate-ready"
|
|
670
|
+
: exhausted
|
|
671
|
+
? "exhausted"
|
|
672
|
+
: history.length
|
|
673
|
+
? "investigating"
|
|
674
|
+
: "authorized",
|
|
675
|
+
remaining,
|
|
676
|
+
availableRunSeconds: authorizationValid && !closure ? availableRunSeconds : 0,
|
|
677
|
+
projectWallReservations: reservations,
|
|
678
|
+
authorizationValid,
|
|
679
|
+
scopeReason,
|
|
680
|
+
allowedNextAction,
|
|
681
|
+
candidate: candidate
|
|
682
|
+
? {
|
|
683
|
+
recordSha256: candidate.recordSha256,
|
|
684
|
+
attemptSha256: candidate.attemptSha256,
|
|
685
|
+
recipeSha256: candidate.recipeSha256,
|
|
686
|
+
certification: passed
|
|
687
|
+
? "passed"
|
|
688
|
+
: pending
|
|
689
|
+
? "incomplete"
|
|
690
|
+
: lastPromotion?.certification === "failed"
|
|
691
|
+
? "failed"
|
|
692
|
+
: "not-certified",
|
|
693
|
+
}
|
|
694
|
+
: null,
|
|
695
|
+
promotions,
|
|
696
|
+
closure,
|
|
697
|
+
actualCostUsd: null,
|
|
698
|
+
attempts: history.map((a) => ({
|
|
699
|
+
attemptId: a.start.attemptId,
|
|
700
|
+
startSha256: a.start.recordSha256,
|
|
701
|
+
recordSha256: a.record?.recordSha256 ?? null,
|
|
702
|
+
outcome: a.record?.outcome ?? "incomplete",
|
|
703
|
+
hypothesis: a.start.hypothesis,
|
|
704
|
+
configuration: a.start.configuration,
|
|
705
|
+
parentAttemptSha256: a.start.parentAttemptSha256,
|
|
706
|
+
changes: a.start.changes,
|
|
707
|
+
...(observers.has(a.start.recordSha256)
|
|
708
|
+
? { observer: observers.get(a.start.recordSha256) }
|
|
709
|
+
: {}),
|
|
710
|
+
conclusion: a.record?.diagnostic?.conclusion ?? a.record?.outcome ?? "unresolved",
|
|
711
|
+
})),
|
|
712
|
+
executionBoundary: "calculation-sandbox-required",
|
|
713
|
+
purpose: definition.plan.purpose,
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
//# sourceMappingURL=investigation.js.map
|