@tiangong-ai/cli 0.0.61 → 0.0.62

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 (210) hide show
  1. package/AGENTS.md +17 -6
  2. package/README.md +414 -33
  3. package/dist/data/builtins.js +33 -5
  4. package/dist/data/builtins.js.map +1 -1
  5. package/dist/data/catalog.js +46 -0
  6. package/dist/data/catalog.js.map +1 -1
  7. package/dist/data/commands.js +15 -1
  8. package/dist/data/commands.js.map +1 -1
  9. package/dist/data/connectors/airnow-hourly-observations.js +60 -10
  10. package/dist/data/connectors/airnow-hourly-observations.js.map +1 -1
  11. package/dist/data/connectors/epa-eis-records.js +16 -2
  12. package/dist/data/connectors/epa-eis-records.js.map +1 -1
  13. package/dist/data/connectors/gdelt-doc-search.js +54 -18
  14. package/dist/data/connectors/gdelt-doc-search.js.map +1 -1
  15. package/dist/data/connectors/gdelt-file-feeds.js +20 -11
  16. package/dist/data/connectors/gdelt-file-feeds.js.map +1 -1
  17. package/dist/data/connectors/gdelt-web-ngrams.d.ts +2 -0
  18. package/dist/data/connectors/gdelt-web-ngrams.js +350 -0
  19. package/dist/data/connectors/gdelt-web-ngrams.js.map +1 -0
  20. package/dist/data/connectors/gdelt-web-ngrams.schemas.d.ts +188 -0
  21. package/dist/data/connectors/gdelt-web-ngrams.schemas.js +138 -0
  22. package/dist/data/connectors/gdelt-web-ngrams.schemas.js.map +1 -0
  23. package/dist/data/connectors/nasa-firms-fire.js +4 -0
  24. package/dist/data/connectors/nasa-firms-fire.js.map +1 -1
  25. package/dist/data/connectors/open-meteo-air-quality.js +27 -22
  26. package/dist/data/connectors/open-meteo-air-quality.js.map +1 -1
  27. package/dist/data/connectors/open-meteo-air-quality.schemas.d.ts +4 -1
  28. package/dist/data/connectors/open-meteo-air-quality.schemas.js +21 -1
  29. package/dist/data/connectors/open-meteo-air-quality.schemas.js.map +1 -1
  30. package/dist/data/connectors/open-meteo-flood.js +28 -23
  31. package/dist/data/connectors/open-meteo-flood.js.map +1 -1
  32. package/dist/data/connectors/open-meteo-flood.schemas.d.ts +4 -1
  33. package/dist/data/connectors/open-meteo-flood.schemas.js +21 -1
  34. package/dist/data/connectors/open-meteo-flood.schemas.js.map +1 -1
  35. package/dist/data/connectors/open-meteo-historical-weather.js +26 -21
  36. package/dist/data/connectors/open-meteo-historical-weather.js.map +1 -1
  37. package/dist/data/connectors/open-meteo-historical-weather.schemas.d.ts +4 -1
  38. package/dist/data/connectors/open-meteo-historical-weather.schemas.js +21 -1
  39. package/dist/data/connectors/open-meteo-historical-weather.schemas.js.map +1 -1
  40. package/dist/data/connectors/openaq-air-quality.js +50 -12
  41. package/dist/data/connectors/openaq-air-quality.js.map +1 -1
  42. package/dist/data/connectors/openaq-air-quality.schemas.d.ts +0 -4
  43. package/dist/data/connectors/openaq-air-quality.schemas.js +2 -2
  44. package/dist/data/connectors/openaq-air-quality.schemas.js.map +1 -1
  45. package/dist/data/connectors/youtube-public-content.js +76 -6
  46. package/dist/data/connectors/youtube-public-content.js.map +1 -1
  47. package/dist/data/connectors/youtube-public-content.schemas.d.ts +63 -3
  48. package/dist/data/connectors/youtube-public-content.schemas.js +56 -2
  49. package/dist/data/connectors/youtube-public-content.schemas.js.map +1 -1
  50. package/dist/data/contracts.d.ts +22 -1
  51. package/dist/data/runtime/bounded-http.d.ts +2 -0
  52. package/dist/data/runtime/bounded-http.js +221 -59
  53. package/dist/data/runtime/bounded-http.js.map +1 -1
  54. package/dist/data/runtime/execute.d.ts +2 -0
  55. package/dist/data/runtime/execute.js +82 -60
  56. package/dist/data/runtime/execute.js.map +1 -1
  57. package/dist/data/runtime/http-timing.d.ts +15 -0
  58. package/dist/data/runtime/http-timing.js +59 -0
  59. package/dist/data/runtime/http-timing.js.map +1 -0
  60. package/dist/data/runtime/http-transport.d.ts +1 -0
  61. package/dist/data/runtime/http-transport.js +27 -0
  62. package/dist/data/runtime/http-transport.js.map +1 -0
  63. package/dist/data/schemas/catalog.v1.json +46 -0
  64. package/dist/data/schemas/discovery.v1.json +12 -0
  65. package/dist/data/schemas/error.v1.json +1 -0
  66. package/dist/data/schemas/manifest.v1.json +19 -1
  67. package/dist/research/orchestration.js +233 -10
  68. package/dist/research/orchestration.js.map +1 -1
  69. package/dist/research/setup-command.js +37 -6
  70. package/dist/research/setup-command.js.map +1 -1
  71. package/dist/research/workspace/audit-bundle.js +28 -8
  72. package/dist/research/workspace/audit-bundle.js.map +1 -1
  73. package/dist/research/workspace/broker.js +25 -4
  74. package/dist/research/workspace/broker.js.map +1 -1
  75. package/dist/research/workspace/constants.d.ts +1 -0
  76. package/dist/research/workspace/constants.js +1 -0
  77. package/dist/research/workspace/constants.js.map +1 -1
  78. package/dist/research/workspace/content-evidence.d.ts +1 -0
  79. package/dist/research/workspace/content-evidence.js +1 -1
  80. package/dist/research/workspace/content-evidence.js.map +1 -1
  81. package/dist/research/workspace/credentials.js +2 -1
  82. package/dist/research/workspace/credentials.js.map +1 -1
  83. package/dist/research/workspace/data-evidence-adapter.d.ts +71 -0
  84. package/dist/research/workspace/data-evidence-adapter.js +269 -59
  85. package/dist/research/workspace/data-evidence-adapter.js.map +1 -1
  86. package/dist/research/workspace/data-evidence-view.d.ts +58 -0
  87. package/dist/research/workspace/data-evidence-view.js +411 -0
  88. package/dist/research/workspace/data-evidence-view.js.map +1 -0
  89. package/dist/research/workspace/evidence-ledger.js +32 -29
  90. package/dist/research/workspace/evidence-ledger.js.map +1 -1
  91. package/dist/research/workspace/evidence.d.ts +28 -0
  92. package/dist/research/workspace/evidence.js +48 -0
  93. package/dist/research/workspace/evidence.js.map +1 -1
  94. package/dist/research/workspace/executor.d.ts +27 -1
  95. package/dist/research/workspace/executor.js +180 -20
  96. package/dist/research/workspace/executor.js.map +1 -1
  97. package/dist/research/workspace/investigation-attempt.d.ts +159 -0
  98. package/dist/research/workspace/investigation-attempt.js +619 -0
  99. package/dist/research/workspace/investigation-attempt.js.map +1 -0
  100. package/dist/research/workspace/investigation-audit.d.ts +20 -0
  101. package/dist/research/workspace/investigation-audit.js +306 -0
  102. package/dist/research/workspace/investigation-audit.js.map +1 -0
  103. package/dist/research/workspace/investigation-candidate.d.ts +84 -0
  104. package/dist/research/workspace/investigation-candidate.js +156 -0
  105. package/dist/research/workspace/investigation-candidate.js.map +1 -0
  106. package/dist/research/workspace/investigation-certification.d.ts +39 -0
  107. package/dist/research/workspace/investigation-certification.js +152 -0
  108. package/dist/research/workspace/investigation-certification.js.map +1 -0
  109. package/dist/research/workspace/investigation-close.d.ts +47 -0
  110. package/dist/research/workspace/investigation-close.js +153 -0
  111. package/dist/research/workspace/investigation-close.js.map +1 -0
  112. package/dist/research/workspace/investigation-lineage.d.ts +8 -0
  113. package/dist/research/workspace/investigation-lineage.js +10 -0
  114. package/dist/research/workspace/investigation-lineage.js.map +1 -0
  115. package/dist/research/workspace/investigation-observer.d.ts +22 -0
  116. package/dist/research/workspace/investigation-observer.js +122 -0
  117. package/dist/research/workspace/investigation-observer.js.map +1 -0
  118. package/dist/research/workspace/investigation-promotion.d.ts +129 -0
  119. package/dist/research/workspace/investigation-promotion.js +316 -0
  120. package/dist/research/workspace/investigation-promotion.js.map +1 -0
  121. package/dist/research/workspace/investigation-requirements.d.ts +5 -0
  122. package/dist/research/workspace/investigation-requirements.js +32 -0
  123. package/dist/research/workspace/investigation-requirements.js.map +1 -0
  124. package/dist/research/workspace/investigation-resources.d.ts +12 -0
  125. package/dist/research/workspace/investigation-resources.js +78 -0
  126. package/dist/research/workspace/investigation-resources.js.map +1 -0
  127. package/dist/research/workspace/investigation-source-export.d.ts +6 -0
  128. package/dist/research/workspace/investigation-source-export.js +68 -0
  129. package/dist/research/workspace/investigation-source-export.js.map +1 -0
  130. package/dist/research/workspace/investigation-store.d.ts +9 -0
  131. package/dist/research/workspace/investigation-store.js +55 -0
  132. package/dist/research/workspace/investigation-store.js.map +1 -0
  133. package/dist/research/workspace/investigation.d.ts +424 -0
  134. package/dist/research/workspace/investigation.js +716 -0
  135. package/dist/research/workspace/investigation.js.map +1 -0
  136. package/dist/research/workspace/native-process-supervisor.d.ts +1 -0
  137. package/dist/research/workspace/native-process-supervisor.js +97 -0
  138. package/dist/research/workspace/native-process-supervisor.js.map +1 -0
  139. package/dist/research/workspace/native-run.d.ts +79 -6
  140. package/dist/research/workspace/native-run.js +293 -51
  141. package/dist/research/workspace/native-run.js.map +1 -1
  142. package/dist/research/workspace/preflight.d.ts +3 -0
  143. package/dist/research/workspace/preflight.js +11 -6
  144. package/dist/research/workspace/preflight.js.map +1 -1
  145. package/dist/research/workspace/project-authority.js +2 -0
  146. package/dist/research/workspace/project-authority.js.map +1 -1
  147. package/dist/research/workspace/project-budget.d.ts +41 -0
  148. package/dist/research/workspace/project-budget.js +265 -0
  149. package/dist/research/workspace/project-budget.js.map +1 -0
  150. package/dist/research/workspace/project-mutations.d.ts +1 -1
  151. package/dist/research/workspace/project-mutations.js +27 -3
  152. package/dist/research/workspace/project-mutations.js.map +1 -1
  153. package/dist/research/workspace/projects.d.ts +66 -0
  154. package/dist/research/workspace/projects.js +181 -3
  155. package/dist/research/workspace/projects.js.map +1 -1
  156. package/dist/research/workspace/provider-budget.d.ts +3 -0
  157. package/dist/research/workspace/provider-budget.js +45 -0
  158. package/dist/research/workspace/provider-budget.js.map +1 -0
  159. package/dist/research/workspace/provider-state.d.ts +1 -0
  160. package/dist/research/workspace/provider-state.js +20 -0
  161. package/dist/research/workspace/provider-state.js.map +1 -0
  162. package/dist/research/workspace/publication-lineage.d.ts +34 -0
  163. package/dist/research/workspace/publication-lineage.js +165 -0
  164. package/dist/research/workspace/publication-lineage.js.map +1 -0
  165. package/dist/research/workspace/publication-workflow.d.ts +24 -0
  166. package/dist/research/workspace/publication-workflow.js +118 -14
  167. package/dist/research/workspace/publication-workflow.js.map +1 -1
  168. package/dist/research/workspace/review-executor.d.ts +6 -1
  169. package/dist/research/workspace/review-executor.js +17 -3
  170. package/dist/research/workspace/review-executor.js.map +1 -1
  171. package/dist/research/workspace/runtime.d.ts +12 -1
  172. package/dist/research/workspace/runtime.js +143 -19
  173. package/dist/research/workspace/runtime.js.map +1 -1
  174. package/dist/research/workspace/scientific-amendment.d.ts +103 -0
  175. package/dist/research/workspace/scientific-amendment.js +537 -0
  176. package/dist/research/workspace/scientific-amendment.js.map +1 -0
  177. package/dist/research/workspace/scientific-fulfillment-audit.d.ts +20 -2
  178. package/dist/research/workspace/scientific-fulfillment-audit.js +87 -7
  179. package/dist/research/workspace/scientific-fulfillment-audit.js.map +1 -1
  180. package/dist/research/workspace/scientific-fulfillment.d.ts +10 -1
  181. package/dist/research/workspace/scientific-fulfillment.js +20 -6
  182. package/dist/research/workspace/scientific-fulfillment.js.map +1 -1
  183. package/dist/research/workspace/scientific-review-execution.js +162 -22
  184. package/dist/research/workspace/scientific-review-execution.js.map +1 -1
  185. package/dist/research/workspace/scientific-review.d.ts +2 -1
  186. package/dist/research/workspace/scientific-review.js +33 -2
  187. package/dist/research/workspace/scientific-review.js.map +1 -1
  188. package/dist/research/workspace/setup-catalog.js +3 -3
  189. package/dist/research/workspace/setup-release.d.ts +22 -0
  190. package/dist/research/workspace/setup-release.js +426 -0
  191. package/dist/research/workspace/setup-release.js.map +1 -0
  192. package/dist/research/workspace/setup-runtime-integrity.d.ts +2 -0
  193. package/dist/research/workspace/setup-runtime-integrity.js +31 -0
  194. package/dist/research/workspace/setup-runtime-integrity.js.map +1 -0
  195. package/dist/research/workspace/setup-upgrade.d.ts +17 -0
  196. package/dist/research/workspace/setup-upgrade.js +784 -0
  197. package/dist/research/workspace/setup-upgrade.js.map +1 -0
  198. package/dist/research/workspace/setup.d.ts +31 -39
  199. package/dist/research/workspace/setup.js +220 -17
  200. package/dist/research/workspace/setup.js.map +1 -1
  201. package/dist/research/workspace/task-acceptance.d.ts +8 -1
  202. package/dist/research/workspace/task-acceptance.js +37 -5
  203. package/dist/research/workspace/task-acceptance.js.map +1 -1
  204. package/dist/research/workspace/task-audit.d.ts +5 -1
  205. package/dist/research/workspace/task-audit.js +33 -18
  206. package/dist/research/workspace/task-audit.js.map +1 -1
  207. package/dist/research/workspace/types.d.ts +44 -0
  208. package/dist/research/workspace/workspace.js +34 -4
  209. package/dist/research/workspace/workspace.js.map +1 -1
  210. package/package.json +2 -1
@@ -0,0 +1,619 @@
1
+ import { investigationObserverRoute } from "./investigation-observer.js";
2
+ import { investigationWallReservations } from "./investigation-resources.js";
3
+ import { localInvestigationReadStore } from "./investigation-store.js";
4
+ import { arch, platform, tmpdir } from "node:os";
5
+ import { Ajv2020 } from "ajv/dist/2020.js";
6
+ import { lstat, mkdir, mkdtemp, readFile, realpath, rm, writeFile } from "node:fs/promises";
7
+ import { basename, dirname, isAbsolute, join, relative } from "node:path";
8
+ import { CliError } from "../../errors.js";
9
+ import { loadCurrentEvidenceSnapshot } from "./acquisition.js";
10
+ import { createCalculationSandboxInvocation } from "./executor.js";
11
+ import { loadInvestigation } from "./investigation.js";
12
+ import { appendJournalEvent, readVerifiedJournal } from "./journal.js";
13
+ import { captureProcess, copyExact, nativePacketBinding, storeRunObject, } from "./native-run.js";
14
+ import { assertProjectAuthority, projectAuthorityIndex } from "./project-authority.js";
15
+ import { loadProject } from "./projects.js";
16
+ import { beginProjectMutation, prepareProjectMutation, projectMutationBinding, settleProjectMutation, } from "./project-mutations.js";
17
+ import { assertResearchPolicyBinding } from "./research-policy.js";
18
+ import { loadScientificFulfillmentView } from "./scientific-fulfillment.js";
19
+ import { isContainedRelativePath } from "./scientific-objects.js";
20
+ import { configuredResearchSecrets, sanitizeResearchText, sanitizeResearchValue, } from "./sanitization.js";
21
+ import { canonicalJson, isObject, resolveContained, sha256File, sha256Text, workspacePaths, } from "./storage.js";
22
+ import { loadProjectTask, taskRequirementSha256, writeTaskObject } from "./task-contract.js";
23
+ import { loadWorkspaceConfig, withWorkspaceLock } from "./workspace.js";
24
+ const HASH = /^[a-f0-9]{64}$/;
25
+ const idSchema = { type: "string", pattern: "^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$" };
26
+ const inputSchema = {
27
+ type: "object",
28
+ additionalProperties: false,
29
+ required: [
30
+ "schemaVersion",
31
+ "investigationId",
32
+ "attemptId",
33
+ "programId",
34
+ "hypothesis",
35
+ "configuration",
36
+ "nativeSessionId",
37
+ "workingDirectory",
38
+ ],
39
+ properties: {
40
+ schemaVersion: { const: 1 },
41
+ investigationId: idSchema,
42
+ attemptId: idSchema,
43
+ parentAttemptId: idSchema,
44
+ programId: idSchema,
45
+ hypothesis: { type: "string", minLength: 8, maxLength: 4000 },
46
+ configuration: {
47
+ type: "object",
48
+ maxProperties: 64,
49
+ additionalProperties: { type: ["string", "number"] },
50
+ },
51
+ nativeSessionId: { type: ["string", "null"], minLength: 1, maxLength: 128 },
52
+ workingDirectory: { type: "string", minLength: 1, maxLength: 4000 },
53
+ },
54
+ };
55
+ const validateInput = new Ajv2020({ strict: false, allErrors: true }).compile(inputSchema);
56
+ export function investigationAttemptInputSchema() {
57
+ return structuredClone(inputSchema);
58
+ }
59
+ function invalid(message, code = "RESEARCH_INVESTIGATION_INVALID") {
60
+ return new CliError(message, { code, exitCode: 3 });
61
+ }
62
+ const outcomes = [
63
+ "harness-failure",
64
+ "output-limit-exceeded",
65
+ "solver-not-reached",
66
+ "numerical-failure",
67
+ "diagnostic-incomplete",
68
+ "feasible-candidate",
69
+ "stale",
70
+ "inputs-changed",
71
+ ];
72
+ function attemptChanges(programId, configuration, parent) {
73
+ return {
74
+ program: { before: parent?.programId ?? null, after: programId },
75
+ configuration: Object.keys(configuration)
76
+ .sort()
77
+ .filter((id) => !parent || parent.configuration[id] !== configuration[id])
78
+ .map((id) => ({ id, before: parent?.configuration[id] ?? null, after: configuration[id] })),
79
+ };
80
+ }
81
+ function configuredProgram(definition, programId, configuration) {
82
+ const program = definition.plan.programs.find((p) => p.id === programId);
83
+ if (!program ||
84
+ !isObject(configuration) ||
85
+ Object.keys(configuration).sort().join("\0") !==
86
+ definition.plan.options
87
+ .map((o) => o.id)
88
+ .sort()
89
+ .join("\0"))
90
+ throw invalid("Attempt does not match approved program/options.");
91
+ for (const option of definition.plan.options) {
92
+ const setting = configuration[option.id];
93
+ if (option.kind === "enum"
94
+ ? typeof setting !== "string" || !option.values.includes(setting)
95
+ : typeof setting !== "number" ||
96
+ !Number.isFinite(setting) ||
97
+ setting < option.minimum ||
98
+ setting > option.maximum ||
99
+ (option.kind === "integer" && !Number.isSafeInteger(setting)))
100
+ throw invalid("Attempt option is outside the approved numerical scope.");
101
+ }
102
+ return program;
103
+ }
104
+ function matching(events, projectId, id, type) {
105
+ return events.filter((e) => e.scope === projectId && e.type === type && e.payload.investigationId === id);
106
+ }
107
+ async function readStart(root, projectId, event, store = localInvestigationReadStore(root)) {
108
+ const record = await store.readTask(projectId, "investigation-starts", String(event.payload.recordSha256), "recordSha256");
109
+ if (record.kind !== "tiangong-investigation-attempt-start" ||
110
+ record.projectId !== projectId ||
111
+ record.investigationId !== event.payload.investigationId ||
112
+ record.attemptId !== event.payload.attemptId ||
113
+ record.requestSha256 !== event.payload.requestSha256 ||
114
+ !HASH.test(record.definitionSha256) ||
115
+ !Number.isFinite(record.timeoutSeconds) ||
116
+ record.timeoutSeconds <= 0 ||
117
+ !Number.isFinite(record.maxCostUsd) ||
118
+ record.maxCostUsd < 0 ||
119
+ !Number.isSafeInteger(record.maxOutputBytes) ||
120
+ record.maxOutputBytes < 1)
121
+ throw invalid("Investigation attempt start binding changed.");
122
+ return record;
123
+ }
124
+ export async function investigationAttemptHistory(root, projectId, definition, events, store = localInvestigationReadStore(root)) {
125
+ const starts = matching(events, projectId, definition.investigationId, "investigation.attempt.started");
126
+ const completed = matching(events, projectId, definition.investigationId, "investigation.attempt.completed");
127
+ const attempts = [];
128
+ const seen = new Set();
129
+ const previous = new Map();
130
+ for (const event of starts) {
131
+ const start = await readStart(root, projectId, event, store);
132
+ if (seen.has(start.attemptId) || start.definitionSha256 !== definition.recordSha256)
133
+ throw invalid("Investigation attempt identity is duplicated or mixed.");
134
+ seen.add(start.attemptId);
135
+ configuredProgram(definition, start.programId, start.configuration);
136
+ const parent = start.parentAttemptSha256 === null ? null : previous.get(start.parentAttemptSha256);
137
+ if (parent === undefined ||
138
+ canonicalJson(start.changes) !==
139
+ canonicalJson(attemptChanges(start.programId, start.configuration, parent)) ||
140
+ canonicalJson(start.inputs.map((i) => ({ id: i.id, artifactId: i.artifactId, sha256: i.sha256 }))) !== canonicalJson(definition.plan.canonicalInputs) ||
141
+ start.maxCostUsd !== definition.plan.limits.maxRunCostUsd ||
142
+ start.timeoutSeconds > definition.plan.limits.maxRunSeconds ||
143
+ start.maxOutputBytes > definition.plan.limits.maxOutputBytes)
144
+ throw invalid("Attempt start differs from its approved inputs, configuration or parent.");
145
+ for (const input of start.inputs)
146
+ await store.verifyBlob(projectId, input);
147
+ const matches = completed.filter((e) => e.payload.attemptId === start.attemptId);
148
+ if (matches.length > 1)
149
+ throw invalid("Investigation has duplicate committed results.");
150
+ const done = matches[0];
151
+ let record = null;
152
+ if (done) {
153
+ record = await store.readTask(projectId, "investigation-attempts", String(done.payload.recordSha256), "recordSha256");
154
+ if (done.sequence <= event.sequence ||
155
+ record.startSha256 !== start.recordSha256 ||
156
+ record.definitionSha256 !== definition.recordSha256 ||
157
+ record.projectId !== projectId ||
158
+ record.investigationId !== definition.investigationId ||
159
+ record.attemptId !== start.attemptId ||
160
+ record.purpose !== "diagnostic-candidate-only" ||
161
+ !outcomes.includes(record.outcome) ||
162
+ !Number.isFinite(record.process.wallSeconds) ||
163
+ record.process.wallSeconds < 0 ||
164
+ !Number.isSafeInteger(record.process.observedOutputBytes) ||
165
+ Number(record.process.observedOutputBytes) < 0 ||
166
+ typeof record.process.outputLimitExceeded !== "boolean" ||
167
+ (!record.process.outputLimitExceeded &&
168
+ Number(record.process.observedOutputBytes) > start.maxOutputBytes) ||
169
+ record.programId !== start.programId ||
170
+ canonicalJson(record.configuration) !== canonicalJson(start.configuration) ||
171
+ record.hypothesis !== start.hypothesis ||
172
+ record.parentAttemptSha256 !== start.parentAttemptSha256 ||
173
+ canonicalJson(record.changes) !== canonicalJson(start.changes) ||
174
+ record.actualCostUsd !== null ||
175
+ record.accountedCostUpperBoundUsd !== start.maxCostUsd)
176
+ throw invalid("Investigation result does not match its committed attempt.");
177
+ for (const object of [...Object.values(record.logs), ...record.outputs]) {
178
+ if (object.path !== `task/run-objects/${object.sha256}` || !HASH.test(object.sha256))
179
+ throw invalid("Investigation object address changed.");
180
+ await store.verifyBlob(projectId, object);
181
+ }
182
+ }
183
+ if (record)
184
+ previous.set(record.recordSha256, record);
185
+ attempts.push({ start, record });
186
+ }
187
+ if (completed.some((e) => !seen.has(String(e.payload.attemptId))))
188
+ throw invalid("Investigation result has no committed start.");
189
+ return attempts;
190
+ }
191
+ export function investigationRemaining(definition, attempts) {
192
+ return {
193
+ runs: Math.max(0, definition.plan.limits.maxRuns - attempts.length),
194
+ wallSeconds: Math.max(0, definition.plan.limits.maxWallSeconds -
195
+ attempts.reduce((sum, a) => sum + (a.record?.process.wallSeconds ?? a.start.timeoutSeconds), 0)),
196
+ outputBytes: Math.max(0, definition.plan.limits.maxTotalOutputBytes -
197
+ attempts.reduce((sum, a) => sum + (a.record?.process.observedOutputBytes ?? a.start.maxOutputBytes), 0)),
198
+ costUpperBoundUsd: Math.max(0, definition.plan.limits.maxCostUsd - attempts.reduce((sum, a) => sum + a.start.maxCostUsd, 0)),
199
+ };
200
+ }
201
+ export async function assertInvestigationCurrent(root, project, definition, events) {
202
+ assertProjectAuthority(project, projectAuthorityIndex(events));
203
+ if (events.some((e) => e.scope === project.id &&
204
+ e.type === "investigation.closed" &&
205
+ e.payload.investigationId === definition.investigationId))
206
+ throw invalid("This investigation is closed. Preserve its history and approve a new envelope for new work.", "RESEARCH_INVESTIGATION_CLOSED");
207
+ const plan = definition.plan;
208
+ const task = await loadProjectTask(root, project.id, events);
209
+ if (!task?.current.requirements.some((r) => r.id === plan.requirementId &&
210
+ r.checkKind === "computation" &&
211
+ taskRequirementSha256(r) === plan.requirementSha256) ||
212
+ sha256Text(project.question) !== plan.questionSha256 ||
213
+ project.handoff.state !== "agent-actionable" ||
214
+ project.status === "complete" ||
215
+ project.packages.find((p) => p.stage === "review")?.status === "complete" ||
216
+ project.packages.find((p) => p.stage === "acquire")?.status !== "complete")
217
+ throw invalid("The approved requirement is no longer actionable.", "RESEARCH_INVESTIGATION_STALE");
218
+ if (project.publicationPolicy)
219
+ await assertResearchPolicyBinding(root, project.publicationPolicy);
220
+ const design = project.scientificDesign
221
+ ? await loadScientificFulfillmentView(root, project, undefined, events)
222
+ : null;
223
+ const snapshot = await loadCurrentEvidenceSnapshot(root, project.id);
224
+ if ((project.scientificDesign?.designSha256 ?? null) !== plan.designSha256 ||
225
+ (design?.effectiveSha256 ?? null) !== plan.effectiveDesignSha256 ||
226
+ (project.publicationPolicy?.resolvedPolicySha256 ?? null) !== plan.policySha256 ||
227
+ snapshot.snapshotSha256 !== plan.acquisitionSnapshotSha256)
228
+ throw invalid("Investigation scientific inputs changed; prepare a new exact authorization.", "RESEARCH_INVESTIGATION_STALE");
229
+ const reserved = project.budget?.entries.find((e) => e.id === `investigation-${definition.recordSha256}`);
230
+ if (!reserved || reserved.status !== "reserved" || reserved.maxCostUsd !== plan.limits.maxCostUsd)
231
+ throw invalid("The investigation budget reservation is no longer available.");
232
+ return snapshot;
233
+ }
234
+ export function parseInvestigationDiagnostic(value) {
235
+ if (!isObject(value) ||
236
+ Object.keys(value).sort().join(",") !==
237
+ "conclusion,feasible,metrics,schemaVersion,solverReached,statuses" ||
238
+ value.schemaVersion !== 1 ||
239
+ typeof value.solverReached !== "boolean" ||
240
+ typeof value.feasible !== "boolean" ||
241
+ (value.feasible && !value.solverReached) ||
242
+ !isObject(value.metrics) ||
243
+ Object.keys(value.metrics).length > 128 ||
244
+ !Object.values(value.metrics).every((v) => typeof v === "number" && Number.isFinite(v)) ||
245
+ !isObject(value.statuses) ||
246
+ Object.keys(value.statuses).length > 128 ||
247
+ !Object.values(value.statuses).every((v) => typeof v === "string" && v.trim().length > 0 && v.length <= 512) ||
248
+ typeof value.conclusion !== "string" ||
249
+ value.conclusion.trim().length < 8 ||
250
+ value.conclusion.length > 4000 ||
251
+ canonicalJson(sanitizeResearchValue(value, configuredResearchSecrets(process.env))) !==
252
+ canonicalJson(value))
253
+ return null;
254
+ return value;
255
+ }
256
+ function safeLog(value, truncated) {
257
+ if (truncated)
258
+ return "Capture exceeded the bounded log buffer; full-stream hashes and byte counts remain in the process record.\n";
259
+ return sanitizeResearchText(value, configuredResearchSecrets(process.env)).replace(/\/(?:Users|home|private\/tmp|private\/var|tmp)\/[^\s"':)]+/gu, "[host-path]");
260
+ }
261
+ async function observeInvestigationAttemptInternal(root, projectId, value) {
262
+ if (!validateInput(value) ||
263
+ canonicalJson(sanitizeResearchValue(value, configuredResearchSecrets(process.env))) !==
264
+ canonicalJson(value))
265
+ throw invalid("Attempt must match the closed secret-free schema.");
266
+ const input = value;
267
+ const requestSha256 = sha256Text(canonicalJson(input));
268
+ const prepared = await withWorkspaceLock(root, "research.investigation.attempt.prepare", async () => {
269
+ const project = await loadProject(root, projectId);
270
+ const events = await readVerifiedJournal(workspacePaths(root).journal);
271
+ const definition = await loadInvestigation(root, projectId, input.investigationId, events);
272
+ const history = await investigationAttemptHistory(root, projectId, definition, events);
273
+ const known = history.find((a) => a.start.attemptId === input.attemptId);
274
+ if (known) {
275
+ if (known.start.requestSha256 !== requestSha256)
276
+ throw invalid("Attempt ID already binds another exact request.", "RESEARCH_INVESTIGATION_CONFLICT");
277
+ if (!known.record)
278
+ throw invalid("Attempt has no committed result. Inspect the unresolved process; it will not be rerun automatically.", "RESEARCH_INVESTIGATION_INCOMPLETE");
279
+ return { replay: known.record };
280
+ }
281
+ const snapshot = await assertInvestigationCurrent(root, project, definition, events);
282
+ if (history.some((a) => !a.record))
283
+ throw invalid("Resolve the unfinished attempt before another investigation calculation.", "RESEARCH_INVESTIGATION_INCOMPLETE");
284
+ const plan = definition.plan;
285
+ const program = configuredProgram(definition, input.programId, input.configuration);
286
+ const programObjects = definition.programs.find((p) => p.id === input.programId);
287
+ if (!programObjects)
288
+ throw invalid("Approved program objects are missing.");
289
+ const parent = input.parentAttemptId
290
+ ? history.find((a) => a.start.attemptId === input.parentAttemptId)?.record
291
+ : (history.at(-1)?.record ?? null);
292
+ if (parent === undefined)
293
+ throw invalid("The requested parent must be a committed attempt in this investigation.");
294
+ const parentAttemptSha256 = parent?.recordSha256 ?? null;
295
+ const changes = attemptChanges(input.programId, input.configuration, parent);
296
+ const remaining = investigationRemaining(definition, history);
297
+ const config = await loadWorkspaceConfig(root);
298
+ const { wallSeconds: reservedWall } = await investigationWallReservations(root, projectId, events);
299
+ const timeoutSeconds = Math.floor(Math.min(plan.limits.maxRunSeconds, remaining.wallSeconds, config.budget.packageMaxWallSeconds.analyze, config.budget.maxWallSeconds - project.usage.wallSeconds - reservedWall));
300
+ if (remaining.runs < 1 ||
301
+ timeoutSeconds < 1 ||
302
+ remaining.costUpperBoundUsd + 1e-9 < plan.limits.maxRunCostUsd ||
303
+ remaining.outputBytes < 1 ||
304
+ history.some((a) => a.record?.process.outputLimitExceeded))
305
+ throw invalid("The remaining finite investigation budget cannot admit another attempt.", "RESEARCH_INVESTIGATION_BUDGET_EXCEEDED");
306
+ if (!isAbsolute(input.workingDirectory))
307
+ throw invalid("Attempt staging needs an absolute directory.");
308
+ const info = await lstat(input.workingDirectory);
309
+ const working = await realpath(input.workingDirectory);
310
+ if (!info.isDirectory() ||
311
+ info.isSymbolicLink() ||
312
+ isContainedRelativePath(relative(await realpath(workspacePaths(root).control), working)))
313
+ throw invalid("Attempt staging must be a regular directory outside the control store.");
314
+ const routePath = join(workspacePaths(root).projects, projectId, "native/investigation-routing", `${definition.routingSha256}.json`);
315
+ const routeBytes = await readFile(routePath, "utf8");
316
+ if (sha256Text(routeBytes) !== definition.routingSha256)
317
+ throw invalid("Local investigation routing changed.");
318
+ const routing = JSON.parse(routeBytes);
319
+ const selected = routing.programs.find((p) => p.id === input.programId);
320
+ if (!selected || sha256Text(selected.runtimePath) !== program.runtime.pathSha256)
321
+ throw invalid("Selected runtime routing changed.");
322
+ const runtimePath = await realpath(selected.runtimePath);
323
+ if ((await sha256File(runtimePath)) !== program.runtime.binarySha256)
324
+ throw invalid("Approved runtime bytes changed.");
325
+ const nativePacketSha256 = await nativePacketBinding(root, project, input.nativeSessionId);
326
+ const staging = await mkdtemp(join(working, ".tiangong-investigation-"));
327
+ const privateHome = join(staging, "home");
328
+ await mkdir(privateHome, { mode: 0o700 });
329
+ const script = join(staging, program.runtime.kind === "node" ? "program.mjs" : "program.py");
330
+ await copyExact(join(workspacePaths(root).projects, projectId, programObjects.script.path), script, program.scriptSha256);
331
+ const inputs = [];
332
+ const inputPaths = new Map();
333
+ for (const item of plan.canonicalInputs) {
334
+ const artifact = snapshot.artifacts.find((a) => a.artifactId === item.artifactId && a.sha256 === item.sha256);
335
+ if (!artifact)
336
+ throw invalid("Approved input is missing from acquisition.");
337
+ const path = join(staging, `input-${item.id}`);
338
+ await copyExact(resolveContained(workspacePaths(root).control, artifact.locator), path, item.sha256);
339
+ inputs.push({
340
+ ...(await storeRunObject(root, projectId, path)),
341
+ id: item.id,
342
+ artifactId: item.artifactId,
343
+ });
344
+ inputPaths.set(item.id, path);
345
+ }
346
+ const outputPaths = new Map(program.outputs.map((o) => [o.id, join(staging, o.fileName)]));
347
+ if (new Set([
348
+ script,
349
+ privateHome,
350
+ join(staging, "execution.sb"),
351
+ join(staging, "calculation.sb"),
352
+ ...inputPaths.values(),
353
+ ...outputPaths.values(),
354
+ ]).size !==
355
+ 4 + inputPaths.size + outputPaths.size)
356
+ throw invalid("Attempt outputs collide with protected files.");
357
+ const args = program.arguments.map((a) => a.replace(/\{(input|output|option):([^}]+)\}/gu, (_m, kind, id) => kind === "input"
358
+ ? inputPaths.get(id)
359
+ : kind === "output"
360
+ ? outputPaths.get(id)
361
+ : String(input.configuration[id])));
362
+ const invocation = await createCalculationSandboxInvocation({
363
+ binary: runtimePath,
364
+ args: [script, ...args],
365
+ capsuleRoot: staging,
366
+ workspaceRoot: root,
367
+ });
368
+ const versionInvocation = await createCalculationSandboxInvocation({
369
+ binary: runtimePath,
370
+ args: ["--version"],
371
+ capsuleRoot: staging,
372
+ workspaceRoot: root,
373
+ });
374
+ const core = {
375
+ schemaVersion: 1,
376
+ kind: "tiangong-investigation-attempt-start",
377
+ projectId,
378
+ investigationId: input.investigationId,
379
+ attemptId: input.attemptId,
380
+ definitionSha256: definition.recordSha256,
381
+ requestSha256,
382
+ programId: input.programId,
383
+ hypothesis: input.hypothesis,
384
+ configuration: input.configuration,
385
+ parentAttemptSha256,
386
+ changes,
387
+ nativePacketSha256,
388
+ inputs,
389
+ timeoutSeconds,
390
+ maxCostUsd: plan.limits.maxRunCostUsd,
391
+ maxOutputBytes: Math.min(plan.limits.maxOutputBytes, remaining.outputBytes),
392
+ startedAt: new Date().toISOString(),
393
+ };
394
+ const start = { ...core, recordSha256: sha256Text(canonicalJson(core)) };
395
+ await writeTaskObject(root, projectId, "investigation-starts", start.recordSha256, start);
396
+ await appendJournalEvent(workspacePaths(root).journal, "investigation.attempt.started", projectId, {
397
+ investigationId: input.investigationId,
398
+ attemptId: input.attemptId,
399
+ recordSha256: start.recordSha256,
400
+ requestSha256,
401
+ });
402
+ return {
403
+ project,
404
+ definition,
405
+ program,
406
+ start,
407
+ staging,
408
+ privateHome,
409
+ script,
410
+ inputPaths,
411
+ outputPaths,
412
+ runtimePath,
413
+ invocation,
414
+ versionInvocation,
415
+ };
416
+ });
417
+ if ("replay" in prepared)
418
+ return { record: prepared.replay, replayed: true };
419
+ const { start, program } = prepared;
420
+ const env = {
421
+ PATH: dirname(prepared.runtimePath),
422
+ HOME: prepared.privateHome,
423
+ TMPDIR: prepared.staging,
424
+ LANG: "C.UTF-8",
425
+ TZ: "UTC",
426
+ PYTHONDONTWRITEBYTECODE: "1",
427
+ PYTHONNOUSERSITE: "1",
428
+ };
429
+ const probe = await captureProcess(prepared.versionInvocation.binary, prepared.versionInvocation.args, prepared.staging, env, Math.min(5, start.timeoutSeconds), { maxBytes: start.maxOutputBytes }, investigationObserverRoute(root, projectId, start.recordSha256, "runtime-probe", prepared.staging));
430
+ const version = probe.stdout.trim() || probe.stderr.trim();
431
+ const validRuntime = probe.exitCode === 0 &&
432
+ !probe.outputLimitExceeded &&
433
+ !probe.timedOut &&
434
+ !probe.cancelled &&
435
+ (program.runtime.kind === "node" ? /^v\d+\.\d+\.\d+$/u : /^Python \d+\.\d+\.\d+$/u).test(version);
436
+ const available = start.timeoutSeconds - probe.wallSeconds;
437
+ const availableOutput = start.maxOutputBytes - (probe.observedOutputBytes ?? 0);
438
+ const observed = validRuntime && available > 0 && availableOutput > 0
439
+ ? await captureProcess(prepared.invocation.binary, prepared.invocation.args, prepared.staging, env, available, { maxBytes: availableOutput, paths: [...prepared.outputPaths.values()] }, investigationObserverRoute(root, projectId, start.recordSha256, "calculation", prepared.staging))
440
+ : probe;
441
+ return withWorkspaceLock(root, "research.investigation.attempt.commit", async () => {
442
+ const project = await loadProject(root, projectId);
443
+ const events = await readVerifiedJournal(workspacePaths(root).journal);
444
+ let stale = false;
445
+ try {
446
+ await assertInvestigationCurrent(root, project, prepared.definition, events);
447
+ if ((await nativePacketBinding(root, project, input.nativeSessionId)) !==
448
+ start.nativePacketSha256)
449
+ stale = true;
450
+ }
451
+ catch {
452
+ stale = true;
453
+ }
454
+ const outputLimitExceeded = Boolean(probe.outputLimitExceeded || observed.outputLimitExceeded || availableOutput <= 0);
455
+ const observedOutputBytes = (observed.observedOutputBytes ?? 0) +
456
+ (observed === probe ? 0 : (probe.observedOutputBytes ?? 0));
457
+ const outputs = [];
458
+ let diagnostic = null;
459
+ for (const output of outputLimitExceeded ? [] : program.outputs) {
460
+ const path = prepared.outputPaths.get(output.id);
461
+ try {
462
+ const info = await lstat(path);
463
+ if (!info.isFile() || info.isSymbolicLink() || info.size > start.maxOutputBytes)
464
+ continue;
465
+ const object = await storeRunObject(root, projectId, path);
466
+ outputs.push({ ...object, id: output.id, mediaType: output.mediaType });
467
+ if (output.id === program.diagnosticOutputId && info.size <= 65536)
468
+ diagnostic = parseInvestigationDiagnostic(JSON.parse(await readFile(join(workspacePaths(root).projects, projectId, object.path), "utf8")));
469
+ }
470
+ catch {
471
+ /* Missing, unsafe or invalid diagnostics cannot establish a candidate. */
472
+ }
473
+ }
474
+ let stable = true;
475
+ for (const file of [
476
+ { path: prepared.runtimePath, sha256: program.runtime.binarySha256 },
477
+ { path: prepared.script, sha256: program.scriptSha256 },
478
+ ...start.inputs.map((i) => ({ path: prepared.inputPaths.get(i.id), sha256: i.sha256 })),
479
+ ]) {
480
+ try {
481
+ const info = await lstat(file.path);
482
+ if (!info.isFile() ||
483
+ info.isSymbolicLink() ||
484
+ (await sha256File(file.path)) !== file.sha256)
485
+ stable = false;
486
+ }
487
+ catch {
488
+ stable = false;
489
+ }
490
+ }
491
+ const missingTelemetry = diagnostic?.solverReached
492
+ ? [
493
+ ...program.telemetry.requiredMetrics
494
+ .filter((id) => !Object.hasOwn(diagnostic.metrics, id))
495
+ .map((id) => `metrics.${id}`),
496
+ ...program.telemetry.requiredStatuses
497
+ .filter((id) => !Object.hasOwn(diagnostic.statuses, id))
498
+ .map((id) => `statuses.${id}`),
499
+ ].sort()
500
+ : [];
501
+ const outcome = outputLimitExceeded
502
+ ? "output-limit-exceeded"
503
+ : stale
504
+ ? "stale"
505
+ : !stable
506
+ ? "inputs-changed"
507
+ : !validRuntime
508
+ ? "harness-failure"
509
+ : !diagnostic
510
+ ? observed.exitCode === 0
511
+ ? "diagnostic-incomplete"
512
+ : "harness-failure"
513
+ : !diagnostic.solverReached
514
+ ? "solver-not-reached"
515
+ : missingTelemetry.length
516
+ ? "diagnostic-incomplete"
517
+ : !diagnostic.feasible
518
+ ? "numerical-failure"
519
+ : observed.exitCode === 0 &&
520
+ !observed.timedOut &&
521
+ !observed.cancelled &&
522
+ outputs.length === program.outputs.length
523
+ ? "feasible-candidate"
524
+ : "diagnostic-incomplete";
525
+ const logs = {};
526
+ // Parent-created log storage is outside the child-writable capsule. Never
527
+ // follow a program-created output/symlink while persisting capture bytes.
528
+ const logDirectory = await mkdtemp(join(tmpdir(), "tiangong-investigation-capture-"));
529
+ try {
530
+ for (const stream of ["stdout", "stderr"]) {
531
+ const path = join(logDirectory, stream);
532
+ await writeFile(path, safeLog(observed[stream], observed.truncated), {
533
+ mode: 0o600,
534
+ flag: "wx",
535
+ });
536
+ logs[stream] = await storeRunObject(root, projectId, path);
537
+ }
538
+ }
539
+ finally {
540
+ await rm(logDirectory, { recursive: true, force: true });
541
+ }
542
+ const { stdout: _out, stderr: _err, ...processRecord } = observed;
543
+ const { stdout: _probeOut, stderr: _probeErr, ...runtimeProbe } = probe;
544
+ const wallSeconds = observed === probe ? probe.wallSeconds : probe.wallSeconds + observed.wallSeconds;
545
+ const core = {
546
+ schemaVersion: 1,
547
+ kind: "tiangong-investigation-attempt",
548
+ projectId,
549
+ investigationId: input.investigationId,
550
+ attemptId: input.attemptId,
551
+ startSha256: start.recordSha256,
552
+ definitionSha256: prepared.definition.recordSha256,
553
+ programId: program.id,
554
+ configuration: input.configuration,
555
+ hypothesis: input.hypothesis,
556
+ purpose: "diagnostic-candidate-only",
557
+ parentAttemptSha256: start.parentAttemptSha256,
558
+ changes: start.changes,
559
+ outcome,
560
+ diagnostic,
561
+ diagnosticTrust: "program-reported",
562
+ missingTelemetry,
563
+ actualCostUsd: null,
564
+ accountedCostUpperBoundUsd: start.maxCostUsd,
565
+ process: {
566
+ ...processRecord,
567
+ startedAt: probe.startedAt,
568
+ wallSeconds,
569
+ outputLimitExceeded,
570
+ observedOutputBytes,
571
+ },
572
+ runtimeProbe,
573
+ runtime: {
574
+ kind: program.runtime.kind,
575
+ version,
576
+ binarySha256: program.runtime.binarySha256,
577
+ platform: platform(),
578
+ architecture: arch(),
579
+ },
580
+ isolation: prepared.invocation.isolation,
581
+ logs,
582
+ outputs,
583
+ };
584
+ const record = { ...core, recordSha256: sha256Text(canonicalJson(core)) };
585
+ await writeTaskObject(root, projectId, "investigation-attempts", record.recordSha256, record);
586
+ let mutation = await beginProjectMutation(root, "investigation-attempt", project, start.recordSha256);
587
+ try {
588
+ project.usage.wallSeconds += wallSeconds;
589
+ project.updatedAt = new Date().toISOString();
590
+ mutation = await prepareProjectMutation(root, mutation, project);
591
+ await appendJournalEvent(workspacePaths(root).journal, "investigation.attempt.completed", projectId, {
592
+ projectId,
593
+ investigationId: input.investigationId,
594
+ attemptId: input.attemptId,
595
+ recordSha256: record.recordSha256,
596
+ startSha256: start.recordSha256,
597
+ mutation: projectMutationBinding(mutation),
598
+ });
599
+ await settleProjectMutation(root, mutation);
600
+ }
601
+ catch (error) {
602
+ await settleProjectMutation(root, mutation);
603
+ throw error;
604
+ }
605
+ return { record, replayed: false, stagingDirectoryName: basename(prepared.staging) };
606
+ });
607
+ }
608
+ /** Preserve an unresolved start on unexpected storage/runtime failure. */
609
+ export async function observeInvestigationAttempt(root, projectId, value) {
610
+ try {
611
+ return await observeInvestigationAttemptInternal(root, projectId, value);
612
+ }
613
+ catch (error) {
614
+ if (error instanceof CliError)
615
+ throw error;
616
+ throw invalid("The investigation attempt could not commit its observation. Preserve its files and inspect the investigation status before any further execution; unresolved attempts are never retried automatically.", "RESEARCH_INVESTIGATION_OBSERVATION_FAILED");
617
+ }
618
+ }
619
+ //# sourceMappingURL=investigation-attempt.js.map