@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.
Files changed (216) hide show
  1. package/AGENTS.md +17 -6
  2. package/CONTRIBUTING.md +5 -5
  3. package/README.md +417 -36
  4. package/dist/cli.js +2 -2
  5. package/dist/data/builtins.js +33 -5
  6. package/dist/data/builtins.js.map +1 -1
  7. package/dist/data/catalog.js +46 -0
  8. package/dist/data/catalog.js.map +1 -1
  9. package/dist/data/commands.js +15 -1
  10. package/dist/data/commands.js.map +1 -1
  11. package/dist/data/connectors/airnow-hourly-observations.js +60 -10
  12. package/dist/data/connectors/airnow-hourly-observations.js.map +1 -1
  13. package/dist/data/connectors/epa-eis-records.js +16 -2
  14. package/dist/data/connectors/epa-eis-records.js.map +1 -1
  15. package/dist/data/connectors/gdelt-doc-search.js +54 -18
  16. package/dist/data/connectors/gdelt-doc-search.js.map +1 -1
  17. package/dist/data/connectors/gdelt-file-feeds.js +20 -11
  18. package/dist/data/connectors/gdelt-file-feeds.js.map +1 -1
  19. package/dist/data/connectors/gdelt-web-ngrams.d.ts +2 -0
  20. package/dist/data/connectors/gdelt-web-ngrams.js +350 -0
  21. package/dist/data/connectors/gdelt-web-ngrams.js.map +1 -0
  22. package/dist/data/connectors/gdelt-web-ngrams.schemas.d.ts +188 -0
  23. package/dist/data/connectors/gdelt-web-ngrams.schemas.js +138 -0
  24. package/dist/data/connectors/gdelt-web-ngrams.schemas.js.map +1 -0
  25. package/dist/data/connectors/nasa-firms-fire.js +4 -0
  26. package/dist/data/connectors/nasa-firms-fire.js.map +1 -1
  27. package/dist/data/connectors/open-meteo-air-quality.js +27 -22
  28. package/dist/data/connectors/open-meteo-air-quality.js.map +1 -1
  29. package/dist/data/connectors/open-meteo-air-quality.schemas.d.ts +4 -1
  30. package/dist/data/connectors/open-meteo-air-quality.schemas.js +21 -1
  31. package/dist/data/connectors/open-meteo-air-quality.schemas.js.map +1 -1
  32. package/dist/data/connectors/open-meteo-flood.js +28 -23
  33. package/dist/data/connectors/open-meteo-flood.js.map +1 -1
  34. package/dist/data/connectors/open-meteo-flood.schemas.d.ts +4 -1
  35. package/dist/data/connectors/open-meteo-flood.schemas.js +21 -1
  36. package/dist/data/connectors/open-meteo-flood.schemas.js.map +1 -1
  37. package/dist/data/connectors/open-meteo-historical-weather.js +26 -21
  38. package/dist/data/connectors/open-meteo-historical-weather.js.map +1 -1
  39. package/dist/data/connectors/open-meteo-historical-weather.schemas.d.ts +4 -1
  40. package/dist/data/connectors/open-meteo-historical-weather.schemas.js +21 -1
  41. package/dist/data/connectors/open-meteo-historical-weather.schemas.js.map +1 -1
  42. package/dist/data/connectors/openaq-air-quality.js +50 -12
  43. package/dist/data/connectors/openaq-air-quality.js.map +1 -1
  44. package/dist/data/connectors/openaq-air-quality.schemas.d.ts +0 -4
  45. package/dist/data/connectors/openaq-air-quality.schemas.js +2 -2
  46. package/dist/data/connectors/openaq-air-quality.schemas.js.map +1 -1
  47. package/dist/data/connectors/youtube-public-content.js +76 -6
  48. package/dist/data/connectors/youtube-public-content.js.map +1 -1
  49. package/dist/data/connectors/youtube-public-content.schemas.d.ts +63 -3
  50. package/dist/data/connectors/youtube-public-content.schemas.js +56 -2
  51. package/dist/data/connectors/youtube-public-content.schemas.js.map +1 -1
  52. package/dist/data/contracts.d.ts +22 -1
  53. package/dist/data/runtime/bounded-http.d.ts +2 -0
  54. package/dist/data/runtime/bounded-http.js +221 -59
  55. package/dist/data/runtime/bounded-http.js.map +1 -1
  56. package/dist/data/runtime/execute.d.ts +2 -0
  57. package/dist/data/runtime/execute.js +82 -60
  58. package/dist/data/runtime/execute.js.map +1 -1
  59. package/dist/data/runtime/http-timing.d.ts +15 -0
  60. package/dist/data/runtime/http-timing.js +59 -0
  61. package/dist/data/runtime/http-timing.js.map +1 -0
  62. package/dist/data/runtime/http-transport.d.ts +1 -0
  63. package/dist/data/runtime/http-transport.js +27 -0
  64. package/dist/data/runtime/http-transport.js.map +1 -0
  65. package/dist/data/schemas/catalog.v1.json +46 -0
  66. package/dist/data/schemas/discovery.v1.json +12 -0
  67. package/dist/data/schemas/error.v1.json +1 -0
  68. package/dist/data/schemas/manifest.v1.json +19 -1
  69. package/dist/research/orchestration.js +233 -10
  70. package/dist/research/orchestration.js.map +1 -1
  71. package/dist/research/setup-command.js +37 -6
  72. package/dist/research/setup-command.js.map +1 -1
  73. package/dist/research/workspace/audit-bundle.js +28 -8
  74. package/dist/research/workspace/audit-bundle.js.map +1 -1
  75. package/dist/research/workspace/broker.js +25 -4
  76. package/dist/research/workspace/broker.js.map +1 -1
  77. package/dist/research/workspace/constants.d.ts +1 -0
  78. package/dist/research/workspace/constants.js +1 -0
  79. package/dist/research/workspace/constants.js.map +1 -1
  80. package/dist/research/workspace/content-evidence.d.ts +1 -0
  81. package/dist/research/workspace/content-evidence.js +1 -1
  82. package/dist/research/workspace/content-evidence.js.map +1 -1
  83. package/dist/research/workspace/credentials.js +2 -1
  84. package/dist/research/workspace/credentials.js.map +1 -1
  85. package/dist/research/workspace/data-evidence-adapter.d.ts +71 -0
  86. package/dist/research/workspace/data-evidence-adapter.js +269 -59
  87. package/dist/research/workspace/data-evidence-adapter.js.map +1 -1
  88. package/dist/research/workspace/data-evidence-view.d.ts +58 -0
  89. package/dist/research/workspace/data-evidence-view.js +411 -0
  90. package/dist/research/workspace/data-evidence-view.js.map +1 -0
  91. package/dist/research/workspace/evidence-ledger.js +32 -29
  92. package/dist/research/workspace/evidence-ledger.js.map +1 -1
  93. package/dist/research/workspace/evidence.d.ts +28 -0
  94. package/dist/research/workspace/evidence.js +48 -0
  95. package/dist/research/workspace/evidence.js.map +1 -1
  96. package/dist/research/workspace/executor.d.ts +27 -1
  97. package/dist/research/workspace/executor.js +180 -20
  98. package/dist/research/workspace/executor.js.map +1 -1
  99. package/dist/research/workspace/external-skills.d.ts +1 -0
  100. package/dist/research/workspace/external-skills.js +4 -2
  101. package/dist/research/workspace/external-skills.js.map +1 -1
  102. package/dist/research/workspace/investigation-attempt.d.ts +159 -0
  103. package/dist/research/workspace/investigation-attempt.js +619 -0
  104. package/dist/research/workspace/investigation-attempt.js.map +1 -0
  105. package/dist/research/workspace/investigation-audit.d.ts +20 -0
  106. package/dist/research/workspace/investigation-audit.js +306 -0
  107. package/dist/research/workspace/investigation-audit.js.map +1 -0
  108. package/dist/research/workspace/investigation-candidate.d.ts +84 -0
  109. package/dist/research/workspace/investigation-candidate.js +156 -0
  110. package/dist/research/workspace/investigation-candidate.js.map +1 -0
  111. package/dist/research/workspace/investigation-certification.d.ts +39 -0
  112. package/dist/research/workspace/investigation-certification.js +152 -0
  113. package/dist/research/workspace/investigation-certification.js.map +1 -0
  114. package/dist/research/workspace/investigation-close.d.ts +47 -0
  115. package/dist/research/workspace/investigation-close.js +153 -0
  116. package/dist/research/workspace/investigation-close.js.map +1 -0
  117. package/dist/research/workspace/investigation-lineage.d.ts +8 -0
  118. package/dist/research/workspace/investigation-lineage.js +10 -0
  119. package/dist/research/workspace/investigation-lineage.js.map +1 -0
  120. package/dist/research/workspace/investigation-observer.d.ts +22 -0
  121. package/dist/research/workspace/investigation-observer.js +122 -0
  122. package/dist/research/workspace/investigation-observer.js.map +1 -0
  123. package/dist/research/workspace/investigation-promotion.d.ts +129 -0
  124. package/dist/research/workspace/investigation-promotion.js +316 -0
  125. package/dist/research/workspace/investigation-promotion.js.map +1 -0
  126. package/dist/research/workspace/investigation-requirements.d.ts +5 -0
  127. package/dist/research/workspace/investigation-requirements.js +32 -0
  128. package/dist/research/workspace/investigation-requirements.js.map +1 -0
  129. package/dist/research/workspace/investigation-resources.d.ts +12 -0
  130. package/dist/research/workspace/investigation-resources.js +78 -0
  131. package/dist/research/workspace/investigation-resources.js.map +1 -0
  132. package/dist/research/workspace/investigation-source-export.d.ts +6 -0
  133. package/dist/research/workspace/investigation-source-export.js +68 -0
  134. package/dist/research/workspace/investigation-source-export.js.map +1 -0
  135. package/dist/research/workspace/investigation-store.d.ts +9 -0
  136. package/dist/research/workspace/investigation-store.js +55 -0
  137. package/dist/research/workspace/investigation-store.js.map +1 -0
  138. package/dist/research/workspace/investigation.d.ts +424 -0
  139. package/dist/research/workspace/investigation.js +716 -0
  140. package/dist/research/workspace/investigation.js.map +1 -0
  141. package/dist/research/workspace/native-process-supervisor.d.ts +1 -0
  142. package/dist/research/workspace/native-process-supervisor.js +97 -0
  143. package/dist/research/workspace/native-process-supervisor.js.map +1 -0
  144. package/dist/research/workspace/native-run.d.ts +79 -6
  145. package/dist/research/workspace/native-run.js +293 -51
  146. package/dist/research/workspace/native-run.js.map +1 -1
  147. package/dist/research/workspace/preflight.d.ts +3 -0
  148. package/dist/research/workspace/preflight.js +11 -6
  149. package/dist/research/workspace/preflight.js.map +1 -1
  150. package/dist/research/workspace/project-authority.js +2 -0
  151. package/dist/research/workspace/project-authority.js.map +1 -1
  152. package/dist/research/workspace/project-budget.d.ts +41 -0
  153. package/dist/research/workspace/project-budget.js +265 -0
  154. package/dist/research/workspace/project-budget.js.map +1 -0
  155. package/dist/research/workspace/project-mutations.d.ts +1 -1
  156. package/dist/research/workspace/project-mutations.js +27 -3
  157. package/dist/research/workspace/project-mutations.js.map +1 -1
  158. package/dist/research/workspace/projects.d.ts +66 -0
  159. package/dist/research/workspace/projects.js +181 -3
  160. package/dist/research/workspace/projects.js.map +1 -1
  161. package/dist/research/workspace/provider-budget.d.ts +3 -0
  162. package/dist/research/workspace/provider-budget.js +45 -0
  163. package/dist/research/workspace/provider-budget.js.map +1 -0
  164. package/dist/research/workspace/provider-state.d.ts +1 -0
  165. package/dist/research/workspace/provider-state.js +20 -0
  166. package/dist/research/workspace/provider-state.js.map +1 -0
  167. package/dist/research/workspace/publication-lineage.d.ts +34 -0
  168. package/dist/research/workspace/publication-lineage.js +165 -0
  169. package/dist/research/workspace/publication-lineage.js.map +1 -0
  170. package/dist/research/workspace/publication-workflow.d.ts +24 -0
  171. package/dist/research/workspace/publication-workflow.js +118 -14
  172. package/dist/research/workspace/publication-workflow.js.map +1 -1
  173. package/dist/research/workspace/review-executor.d.ts +6 -1
  174. package/dist/research/workspace/review-executor.js +17 -3
  175. package/dist/research/workspace/review-executor.js.map +1 -1
  176. package/dist/research/workspace/runtime.d.ts +12 -1
  177. package/dist/research/workspace/runtime.js +143 -19
  178. package/dist/research/workspace/runtime.js.map +1 -1
  179. package/dist/research/workspace/scientific-amendment.d.ts +103 -0
  180. package/dist/research/workspace/scientific-amendment.js +537 -0
  181. package/dist/research/workspace/scientific-amendment.js.map +1 -0
  182. package/dist/research/workspace/scientific-fulfillment-audit.d.ts +20 -2
  183. package/dist/research/workspace/scientific-fulfillment-audit.js +87 -7
  184. package/dist/research/workspace/scientific-fulfillment-audit.js.map +1 -1
  185. package/dist/research/workspace/scientific-fulfillment.d.ts +10 -1
  186. package/dist/research/workspace/scientific-fulfillment.js +20 -6
  187. package/dist/research/workspace/scientific-fulfillment.js.map +1 -1
  188. package/dist/research/workspace/scientific-review-execution.js +162 -22
  189. package/dist/research/workspace/scientific-review-execution.js.map +1 -1
  190. package/dist/research/workspace/scientific-review.d.ts +2 -1
  191. package/dist/research/workspace/scientific-review.js +33 -2
  192. package/dist/research/workspace/scientific-review.js.map +1 -1
  193. package/dist/research/workspace/setup-catalog.js +13 -13
  194. package/dist/research/workspace/setup-catalog.js.map +1 -1
  195. package/dist/research/workspace/setup-release.d.ts +22 -0
  196. package/dist/research/workspace/setup-release.js +426 -0
  197. package/dist/research/workspace/setup-release.js.map +1 -0
  198. package/dist/research/workspace/setup-runtime-integrity.d.ts +2 -0
  199. package/dist/research/workspace/setup-runtime-integrity.js +31 -0
  200. package/dist/research/workspace/setup-runtime-integrity.js.map +1 -0
  201. package/dist/research/workspace/setup-upgrade.d.ts +17 -0
  202. package/dist/research/workspace/setup-upgrade.js +784 -0
  203. package/dist/research/workspace/setup-upgrade.js.map +1 -0
  204. package/dist/research/workspace/setup.d.ts +31 -39
  205. package/dist/research/workspace/setup.js +228 -19
  206. package/dist/research/workspace/setup.js.map +1 -1
  207. package/dist/research/workspace/task-acceptance.d.ts +8 -1
  208. package/dist/research/workspace/task-acceptance.js +37 -5
  209. package/dist/research/workspace/task-acceptance.js.map +1 -1
  210. package/dist/research/workspace/task-audit.d.ts +5 -1
  211. package/dist/research/workspace/task-audit.js +33 -18
  212. package/dist/research/workspace/task-audit.js.map +1 -1
  213. package/dist/research/workspace/types.d.ts +44 -0
  214. package/dist/research/workspace/workspace.js +34 -4
  215. package/dist/research/workspace/workspace.js.map +1 -1
  216. package/package.json +5 -4
@@ -1,5 +1,12 @@
1
+ import { investigationObserverRoute, inspectInvestigationObserver, } from "./investigation-observer.js";
2
+ import { investigationWallReservations } from "./investigation-resources.js";
3
+ import { createCalculationSandboxInvocation } from "./executor.js";
4
+ import { assertInvestigationCertificationRequired, prepareInvestigationCertification, frozenPromotionView, certificationAssessment, } from "./investigation-certification.js";
5
+ import { beginProjectMutation, prepareProjectMutation, projectMutationBinding, settleProjectMutation, } from "./project-mutations.js";
6
+ import { settleProjectCost } from "./project-budget.js";
1
7
  import { Ajv2020 } from "ajv/dist/2020.js";
2
- import { spawn } from "node:child_process";
8
+ import { fork, spawn } from "node:child_process";
9
+ import { fileURLToPath } from "node:url";
3
10
  import { createHash } from "node:crypto";
4
11
  import { constants } from "node:fs";
5
12
  import { chmod, copyFile, lstat, mkdir, mkdtemp, readFile, realpath } from "node:fs/promises";
@@ -21,6 +28,7 @@ const statuses = [
21
28
  "succeeded",
22
29
  "failed",
23
30
  "timed-out",
31
+ "output-limit-exceeded",
24
32
  "cancelled",
25
33
  "invalid-output",
26
34
  "inputs-changed",
@@ -91,6 +99,7 @@ const inputSchema = {
91
99
  },
92
100
  arguments: { type: "array", maxItems: 256, items: { type: "string", maxLength: 4000 } },
93
101
  timeoutSeconds: { type: "integer", minimum: 1, maximum: 172800 },
102
+ investigationPromotionSha256: { type: "string", pattern: HASH.source },
94
103
  },
95
104
  };
96
105
  const validateInput = new Ajv2020({ strict: false, allErrors: true }).compile(inputSchema);
@@ -155,9 +164,11 @@ export async function observeNativeRun(root, projectId, value, confirm) {
155
164
  stagingDirectoryName: String(started.payload.stagingDirectoryName ?? ""),
156
165
  };
157
166
  }
167
+ await assertInvestigationCertificationRequired(root, projectId, input, events);
158
168
  await assertRunWindow(root, project, input);
159
169
  const config = await loadWorkspaceConfig(root);
160
- const timeoutSeconds = Math.min(input.timeoutSeconds, config.budget.packageMaxWallSeconds.analyze, config.budget.maxWallSeconds - project.usage.wallSeconds);
170
+ const { wallSeconds: reservedWall } = await investigationWallReservations(root, projectId, events);
171
+ const timeoutSeconds = Math.min(input.timeoutSeconds, config.budget.packageMaxWallSeconds.analyze, config.budget.maxWallSeconds - project.usage.wallSeconds - reservedWall);
161
172
  if (timeoutSeconds < input.timeoutSeconds)
162
173
  throw error("The declared calculation timeout exceeds the remaining finite workspace time budget.");
163
174
  const working = await exactDirectory(input.workingDirectory);
@@ -169,11 +180,15 @@ export async function observeNativeRun(root, projectId, value, confirm) {
169
180
  if (!(await lstat(runtimePath)).isFile())
170
181
  throw error("The selected interpreter is not a regular executable file.");
171
182
  const binarySha256 = await sha256File(runtimePath);
172
- const version = await captureProcess(input.runtime.path, ["--version"], working, { PATH: dirname(input.runtime.path) }, 5);
173
- const reportedVersion = version.stdout.trim() || version.stderr.trim();
174
- if (version.exitCode !== 0 ||
175
- !(input.runtime.kind === "node" ? /^v\d+\.\d+\.\d+$/u : /^Python \d+\.\d+\.\d+$/u).test(reportedVersion))
176
- throw error("The selected executable is not the declared ordinary Node/Python runtime. Agent CLI launchers are not supported.");
183
+ const certification = await prepareInvestigationCertification(root, project, input, binarySha256, events);
184
+ let reportedVersion = "";
185
+ if (!certification) {
186
+ const version = await captureProcess(input.runtime.path, ["--version"], working, { PATH: dirname(input.runtime.path) }, 5);
187
+ reportedVersion = version.stdout.trim() || version.stderr.trim();
188
+ if (version.exitCode !== 0 ||
189
+ !(input.runtime.kind === "node" ? /^v\d+\.\d+\.\d+$/u : /^Python \d+\.\d+\.\d+$/u).test(reportedVersion))
190
+ throw error("The selected executable is not the declared ordinary Node/Python runtime. Agent CLI launchers are not supported.");
191
+ }
177
192
  const acquisition = await loadCurrentEvidenceSnapshot(root, projectId);
178
193
  const selected = input.inputs.map((item) => {
179
194
  const artifact = acquisition.artifacts.find((artifact) => artifact.artifactId === item.artifactId && artifact.sha256 === item.sha256);
@@ -196,7 +211,11 @@ export async function observeNativeRun(root, projectId, value, confirm) {
196
211
  const staging = await mkdtemp(join(working, `.tiangong-run-${input.runId}-`));
197
212
  const privateHome = join(staging, "home");
198
213
  await mkdir(privateHome, { mode: 0o700 });
199
- const scriptFile = join(staging, basename(input.scriptPath));
214
+ const scriptFile = join(staging, certification
215
+ ? input.runtime.kind === "node"
216
+ ? "program.mjs"
217
+ : "program.py"
218
+ : basename(input.scriptPath));
200
219
  await copyExact(resolveContained(workspacePaths(root).control, script.objectLocator), scriptFile, script.sha256);
201
220
  const runInputs = [];
202
221
  for (const item of selected) {
@@ -225,6 +244,26 @@ export async function observeNativeRun(root, projectId, value, confirm) {
225
244
  const nativePacketSha256 = await nativePacketBinding(root, project, input.nativeSessionId);
226
245
  const sourceScript = await storeRunObject(root, projectId, scriptFile);
227
246
  const environmentLock = await storeRunObject(root, projectId, resolveContained(workspacePaths(root).control, environment.objectLocator));
247
+ if (certification &&
248
+ (sourceScript.sha256 !== certification.promotion.plan.recipe.script.sha256 ||
249
+ environmentLock.sha256 !== certification.promotion.plan.recipe.environmentLock.sha256))
250
+ throw error("The staged certification bytes changed after promotion scope validation.", "RESEARCH_INVESTIGATION_CERTIFICATION_INVALID");
251
+ const invocation = certification
252
+ ? await createCalculationSandboxInvocation({
253
+ binary: runtimePath,
254
+ args: [scriptFile, ...args],
255
+ capsuleRoot: staging,
256
+ workspaceRoot: root,
257
+ })
258
+ : null;
259
+ const versionInvocation = certification
260
+ ? await createCalculationSandboxInvocation({
261
+ binary: runtimePath,
262
+ args: ["--version"],
263
+ capsuleRoot: staging,
264
+ workspaceRoot: root,
265
+ })
266
+ : null;
228
267
  await appendJournalEvent(workspacePaths(root).journal, "project.task.run.started", projectId, {
229
268
  runId: input.runId,
230
269
  requestSha256,
@@ -235,9 +274,18 @@ export async function observeNativeRun(root, projectId, value, confirm) {
235
274
  runtimeBinarySha256: binarySha256,
236
275
  nativePacketSha256,
237
276
  stagingDirectoryName: basename(staging),
277
+ ...(certification
278
+ ? {
279
+ investigationPromotionSha256: certification.promotion.recordSha256,
280
+ certificationReservedWallSeconds: timeoutSeconds,
281
+ }
282
+ : {}),
238
283
  });
239
284
  return {
240
285
  project,
286
+ certification,
287
+ invocation,
288
+ versionInvocation,
241
289
  runtimePath,
242
290
  binarySha256,
243
291
  reportedVersion,
@@ -259,8 +307,9 @@ export async function observeNativeRun(root, projectId, value, confirm) {
259
307
  replayed: true,
260
308
  stagingDirectoryName: prepared.stagingDirectoryName,
261
309
  };
262
- // No workspace lease is held during the native calculation.
263
- const observed = await captureProcess(input.runtime.path, [prepared.scriptFile, ...prepared.args], prepared.staging, {
310
+ // No workspace lease is held during either the certification runtime probe
311
+ // or the calculation. Its one-use start is durable before either process.
312
+ const environment = {
264
313
  PATH: dirname(input.runtime.path),
265
314
  HOME: prepared.privateHome,
266
315
  TMPDIR: prepared.staging,
@@ -268,7 +317,35 @@ export async function observeNativeRun(root, projectId, value, confirm) {
268
317
  TZ: "UTC",
269
318
  PYTHONDONTWRITEBYTECODE: "1",
270
319
  PYTHONNOUSERSITE: "1",
271
- }, prepared.timeoutSeconds);
320
+ };
321
+ const probe = prepared.versionInvocation
322
+ ? await captureProcess(prepared.versionInvocation.binary, prepared.versionInvocation.args, prepared.staging, environment, Math.min(5, prepared.timeoutSeconds), { maxBytes: prepared.certification.promotion.plan.certification.maxOutputBytes }, investigationObserverRoute(root, projectId, requestSha256, "runtime-probe", prepared.staging))
323
+ : null;
324
+ const probeVersion = probe
325
+ ? probe.stdout.trim() || probe.stderr.trim()
326
+ : prepared.reportedVersion;
327
+ const runtimeValid = !probe ||
328
+ (probe.exitCode === 0 &&
329
+ !probe.outputLimitExceeded &&
330
+ !probe.timedOut &&
331
+ !probe.cancelled &&
332
+ probeVersion === prepared.certification.promotion.plan.recipe.runtime.version);
333
+ const availableSeconds = prepared.timeoutSeconds - (probe?.wallSeconds ?? 0);
334
+ const availableOutput = prepared.certification
335
+ ? prepared.certification.promotion.plan.certification.maxOutputBytes -
336
+ (probe?.observedOutputBytes ?? 0)
337
+ : Number.POSITIVE_INFINITY;
338
+ const observed = probe && (!runtimeValid || availableSeconds <= 0 || availableOutput <= 0)
339
+ ? probe
340
+ : await captureProcess(prepared.invocation?.binary ?? input.runtime.path, prepared.invocation?.args ?? [prepared.scriptFile, ...prepared.args], prepared.staging, environment, availableSeconds, prepared.certification
341
+ ? { maxBytes: availableOutput, paths: [...prepared.outputPaths.values()] }
342
+ : undefined, prepared.certification
343
+ ? investigationObserverRoute(root, projectId, requestSha256, "calculation", prepared.staging)
344
+ : undefined);
345
+ const wallSeconds = observed.wallSeconds + (probe && observed !== probe ? probe.wallSeconds : 0);
346
+ const outputLimitExceeded = Boolean(probe?.outputLimitExceeded || observed.outputLimitExceeded || availableOutput <= 0);
347
+ const observedOutputBytes = (observed.observedOutputBytes ?? 0) +
348
+ (probe && probe !== observed ? (probe.observedOutputBytes ?? 0) : 0);
272
349
  return await withWorkspaceLock(root, "research.task.run.commit", async () => {
273
350
  const project = await loadProject(root, projectId);
274
351
  let status = observed.cancelled
@@ -278,8 +355,15 @@ export async function observeNativeRun(root, projectId, value, confirm) {
278
355
  : observed.exitCode === 0
279
356
  ? "succeeded"
280
357
  : "failed";
358
+ if (!runtimeValid)
359
+ status = "failed";
360
+ if (outputLimitExceeded)
361
+ status = "output-limit-exceeded";
281
362
  try {
282
363
  await assertRunWindow(root, project, input);
364
+ if (prepared.certification &&
365
+ (await frozenPromotionView(root, project, prepared.certification.promotion, await readVerifiedJournal(workspacePaths(root).journal))) !== prepared.certification.effectiveDesignSha256)
366
+ status = "stale";
283
367
  if ((await nativePacketBinding(root, project, input.nativeSessionId)) !==
284
368
  prepared.nativePacketSha256 ||
285
369
  project.scientificDesign?.designSha256 !==
@@ -302,12 +386,15 @@ export async function observeNativeRun(root, projectId, value, confirm) {
302
386
  if (!stable || (await realpath(input.runtime.path)) !== prepared.runtimePath)
303
387
  status = "inputs-changed";
304
388
  const outputs = [];
305
- for (const output of input.outputs) {
389
+ for (const output of outputLimitExceeded ? [] : input.outputs) {
306
390
  const path = prepared.outputPaths.get(output.id);
307
391
  try {
308
392
  const info = await lstat(path);
309
- if (!info.isFile() || info.isSymbolicLink())
310
- throw error("Calculation output is not a regular file.");
393
+ if (!info.isFile() ||
394
+ info.isSymbolicLink() ||
395
+ (prepared.certification &&
396
+ info.size > prepared.certification.promotion.plan.certification.maxOutputBytes))
397
+ throw error("Calculation output is not a regular file within its approved byte bound.");
311
398
  if (output.mediaType === "application/json")
312
399
  JSON.parse(await readFile(path, "utf8"));
313
400
  outputs.push({
@@ -321,6 +408,9 @@ export async function observeNativeRun(root, projectId, value, confirm) {
321
408
  status = "invalid-output";
322
409
  }
323
410
  }
411
+ const investigationCertification = prepared.certification
412
+ ? await certificationAssessment(root, projectId, prepared.certification, status, outputs, probe, prepared.invocation.isolation, prepared.versionInvocation.isolation, { observedOutputBytes, outputLimitExceeded })
413
+ : undefined;
324
414
  const core = {
325
415
  schemaVersion: 1,
326
416
  kind: "tiangong-native-run",
@@ -338,7 +428,7 @@ export async function observeNativeRun(root, projectId, value, confirm) {
338
428
  environmentVerification: "declared-lock-not-attested",
339
429
  runtime: {
340
430
  kind: input.runtime.kind,
341
- version: prepared.reportedVersion,
431
+ version: runtimeValid ? probeVersion : "unverified",
342
432
  binarySha256: prepared.binarySha256,
343
433
  platform: platform(),
344
434
  architecture: arch(),
@@ -352,19 +442,45 @@ export async function observeNativeRun(root, projectId, value, confirm) {
352
442
  process: {
353
443
  exitCode: observed.exitCode,
354
444
  signal: observed.signal,
355
- startedAt: observed.startedAt,
445
+ startedAt: probe?.startedAt ?? observed.startedAt,
356
446
  finishedAt: observed.finishedAt,
357
- wallSeconds: observed.wallSeconds,
447
+ wallSeconds,
358
448
  stdoutSha256: observed.stdoutSha256,
359
449
  stderrSha256: observed.stderrSha256,
360
450
  stdoutBytes: observed.stdoutBytes,
361
451
  stderrBytes: observed.stderrBytes,
362
452
  diagnostic: safeDiagnostic(observed.stderr, observed.truncated),
453
+ ...(prepared.certification ? { observedOutputBytes, outputLimitExceeded } : {}),
363
454
  },
455
+ ...(investigationCertification ? { investigationCertification } : {}),
364
456
  };
365
457
  const record = { ...core, recordSha256: sha256Text(canonicalJson(core)) };
366
458
  await writeTaskObject(root, projectId, "runs", record.recordSha256, record);
367
- await appendJournalEvent(workspacePaths(root).journal, "project.task.run.completed", projectId, { runId: input.runId, requestSha256, recordSha256: record.recordSha256, status });
459
+ if (prepared.certification) {
460
+ let mutation = await beginProjectMutation(root, "investigation-certification", project, record.recordSha256);
461
+ try {
462
+ settleProjectCost(project, `investigation-certification-${prepared.certification.promotion.recordSha256}`, prepared.certification.promotion.plan.certification.maxCostUsd, "allocated-upper-bound");
463
+ project.usage.wallSeconds += wallSeconds;
464
+ project.updatedAt = new Date().toISOString();
465
+ mutation = await prepareProjectMutation(root, mutation, project);
466
+ await appendJournalEvent(workspacePaths(root).journal, "project.task.run.completed", projectId, {
467
+ projectId,
468
+ runId: input.runId,
469
+ requestSha256,
470
+ recordSha256: record.recordSha256,
471
+ status,
472
+ mutation: projectMutationBinding(mutation),
473
+ });
474
+ await settleProjectMutation(root, mutation);
475
+ }
476
+ catch (error) {
477
+ await settleProjectMutation(root, mutation);
478
+ throw error;
479
+ }
480
+ }
481
+ else {
482
+ await appendJournalEvent(workspacePaths(root).journal, "project.task.run.completed", projectId, { runId: input.runId, requestSha256, recordSha256: record.recordSha256, status });
483
+ }
368
484
  return { record, replayed: false, stagingDirectoryName: basename(prepared.staging) };
369
485
  });
370
486
  }
@@ -387,7 +503,7 @@ async function assertRunWindow(root, project, input) {
387
503
  throw error("An observed calculation needs an active computational requirement, frozen acquisition, and an actionable project before completed review.");
388
504
  await nativePacketBinding(root, project, input.nativeSessionId);
389
505
  }
390
- async function nativePacketBinding(root, project, sessionId) {
506
+ export async function nativePacketBinding(root, project, sessionId) {
391
507
  const active = await pathExists(join(workspacePaths(root).projects, project.id, "native/active.json"));
392
508
  if (!active) {
393
509
  if (sessionId !== null)
@@ -408,7 +524,7 @@ async function exactDirectory(path) {
408
524
  // Canonicalize parent aliases such as macOS /var before containment checks.
409
525
  return realpath(path);
410
526
  }
411
- async function copyExact(source, target, sha256) {
527
+ export async function copyExact(source, target, sha256) {
412
528
  const info = await lstat(source);
413
529
  if (!info.isFile() || info.isSymbolicLink() || (await sha256File(source)) !== sha256)
414
530
  throw error("A bound calculation file is missing, linked or changed.");
@@ -417,7 +533,7 @@ async function copyExact(source, target, sha256) {
417
533
  if ((await sha256File(target)) !== sha256)
418
534
  throw error("Calculation bytes changed during staging.");
419
535
  }
420
- async function storeRunObject(root, projectId, source) {
536
+ export async function storeRunObject(root, projectId, source) {
421
537
  const info = await lstat(source);
422
538
  if (!info.isFile() || info.isSymbolicLink())
423
539
  throw error("Run objects must be exact regular files.");
@@ -452,34 +568,59 @@ export async function inspectNativeRun(root, projectId, runId) {
452
568
  const completed = completedRunEvent(events, projectId, runId);
453
569
  if (completed)
454
570
  return { record: await readNativeRun(root, projectId, String(completed.payload.recordSha256)) };
571
+ const started = events.find((event) => event.scope === projectId &&
572
+ event.type === "project.task.run.started" &&
573
+ event.payload.runId === runId);
455
574
  return {
456
575
  projectId,
457
576
  runId,
458
- status: events.some((event) => event.scope === projectId &&
459
- event.type === "project.task.run.started" &&
460
- event.payload.runId === runId)
461
- ? "incomplete"
462
- : "not-found",
577
+ status: started ? "incomplete" : "not-found",
463
578
  automaticRetry: false,
579
+ ...(started?.payload.investigationPromotionSha256
580
+ ? {
581
+ observer: await inspectInvestigationObserver(root, projectId, String(started.payload.requestSha256)),
582
+ }
583
+ : {}),
464
584
  };
465
585
  }
466
- export async function readNativeRun(root, projectId, hash, knownEvents) {
467
- const record = validateNativeRunRecord(await readTaskObject(root, projectId, "runs", hash, "recordSha256"), projectId);
468
- const events = knownEvents ?? (await readVerifiedJournal(workspacePaths(root).journal));
469
- const completed = completedRunEvent(events, projectId, record.runId);
470
- const started = events.find((event) => event.scope === projectId &&
471
- event.type === "project.task.run.started" &&
472
- event.payload.runId === record.runId);
473
- if (completed?.payload.recordSha256 !== hash ||
586
+ /** Shared by live inspection and portable audit, including ordinary histories
587
+ * that contain no investigation records to trigger a separate verifier. */
588
+ export function assertNativeRunJournalBinding(record, started, completed) {
589
+ const certification = record.investigationCertification;
590
+ if (!started ||
591
+ !completed ||
592
+ started.scope !== record.projectId ||
593
+ completed.scope !== record.projectId ||
594
+ started.type !== "project.task.run.started" ||
595
+ completed.type !== "project.task.run.completed" ||
596
+ started.payload.runId !== record.runId ||
597
+ completed.payload.runId !== record.runId ||
598
+ completed.payload.recordSha256 !== record.recordSha256 ||
474
599
  completed.payload.requestSha256 !== record.requestSha256 ||
475
- started?.payload.requestSha256 !== record.requestSha256 ||
600
+ started.payload.requestSha256 !== record.requestSha256 ||
601
+ started.payload.requirementId !== record.requirementId ||
602
+ started.payload.requirementSha256 !== record.requirementSha256 ||
476
603
  started.payload.scriptSha256 !== record.script.sha256 ||
477
604
  started.payload.runtimeBinarySha256 !== record.runtime.binarySha256 ||
478
605
  started.payload.environmentLockSha256 !== record.environmentLock.sha256 ||
479
606
  started.payload.nativePacketSha256 !== record.nativePacketSha256 ||
607
+ (certification !== undefined &&
608
+ (!isObject(certification) ||
609
+ typeof certification.promotionSha256 !== "string" ||
610
+ !HASH.test(certification.promotionSha256))) ||
611
+ started.payload.investigationPromotionSha256 !== certification?.promotionSha256 ||
480
612
  completed.payload.status !== record.status ||
481
613
  started.sequence >= completed.sequence)
482
614
  throw error("Native run does not match its committed start and completion.", "RESEARCH_NATIVE_RUN_BINDING_INVALID");
615
+ }
616
+ export async function readNativeRun(root, projectId, hash, knownEvents) {
617
+ const record = validateNativeRunRecord(await readTaskObject(root, projectId, "runs", hash, "recordSha256"), projectId);
618
+ const events = knownEvents ?? (await readVerifiedJournal(workspacePaths(root).journal));
619
+ const completed = completedRunEvent(events, projectId, record.runId);
620
+ const started = events.find((event) => event.scope === projectId &&
621
+ event.type === "project.task.run.started" &&
622
+ event.payload.runId === record.runId);
623
+ assertNativeRunJournalBinding(record, started, completed);
483
624
  await taskDirectory(root, projectId, "run-objects", false);
484
625
  for (const object of [
485
626
  record.script,
@@ -555,22 +696,85 @@ function safeDiagnostic(text, truncated) {
555
696
  .replace(/[A-Za-z]:\\[^\s"']+/gu, "[host-path]")
556
697
  .slice(0, 2000);
557
698
  }
558
- async function captureProcess(binary, args, cwd, env, timeoutSeconds) {
699
+ export async function captureProcess(binary, args, cwd, env, timeoutSeconds, outputLimit, observation) {
700
+ if (outputLimit && (!Number.isSafeInteger(outputLimit.maxBytes) || outputLimit.maxBytes < 1))
701
+ throw error("Observed output budget must be a positive finite byte count.");
559
702
  const startedAt = new Date().toISOString();
560
703
  const start = process.hrtime.bigint();
561
704
  return new Promise((resolvePromise) => {
562
- const child = spawn(binary, args, {
563
- cwd,
564
- env,
565
- shell: false,
566
- detached: platform() !== "win32",
567
- stdio: ["ignore", "pipe", "pipe"],
568
- });
705
+ const supervised = Boolean(outputLimit);
706
+ const sourceRuntime = import.meta.url.endsWith(".ts");
707
+ const supervisorPath = fileURLToPath(new URL(sourceRuntime ? "./native-process-supervisor.ts" : "./native-process-supervisor.js", import.meta.url));
708
+ const child = supervised
709
+ ? fork(supervisorPath, [], {
710
+ cwd,
711
+ env,
712
+ detached: platform() !== "win32",
713
+ execArgv: [
714
+ ...(sourceRuntime ? ["--import", import.meta.resolve("tsx")] : []),
715
+ ...(observation ? [`--title=${observation.title}`] : []),
716
+ ],
717
+ stdio: ["ignore", "pipe", "pipe", "ipc"],
718
+ })
719
+ : spawn(binary, args, {
720
+ cwd,
721
+ env,
722
+ shell: false,
723
+ detached: platform() !== "win32",
724
+ stdio: ["ignore", "pipe", "pipe"],
725
+ });
726
+ let supervisorResult = null;
727
+ if (supervised) {
728
+ child.on("message", (value) => {
729
+ if (isObject(value) &&
730
+ value.kind === "result" &&
731
+ (value.exitCode === null || Number.isInteger(value.exitCode)) &&
732
+ (value.signal === null || typeof value.signal === "string") &&
733
+ typeof value.timedOut === "boolean")
734
+ supervisorResult = {
735
+ exitCode: value.exitCode,
736
+ signal: value.signal,
737
+ timedOut: value.timedOut,
738
+ };
739
+ });
740
+ void (async () => {
741
+ if (observation) {
742
+ if (!child.pid)
743
+ throw new Error("Observer was not spawned");
744
+ await observation.onSpawn(child.pid);
745
+ }
746
+ child.send({
747
+ kind: "start",
748
+ binary,
749
+ args,
750
+ env,
751
+ deadlineNs: (start + BigInt(Math.ceil(timeoutSeconds * 1e9))).toString(),
752
+ }, (error) => {
753
+ if (error)
754
+ child.kill("SIGTERM");
755
+ });
756
+ })().catch(() => {
757
+ spawnFailed = true;
758
+ child.kill("SIGTERM");
759
+ });
760
+ }
569
761
  const out = createHash("sha256"), err = createHash("sha256");
570
762
  const stdout = [], stderr = [];
571
763
  let stdoutBytes = 0, stderrBytes = 0, truncated = false, timedOut = false, cancelled = false, spawnFailed = false;
572
764
  const terminate = (signal) => {
573
- if (!child.pid || child.exitCode !== null || child.signalCode !== null)
765
+ if (!child.pid)
766
+ return;
767
+ if (supervised) {
768
+ if (child.connected)
769
+ child.send({ kind: "stop", reason: timedOut ? "timeout" : cancelled ? "cancel" : "output-limit" }, (error) => {
770
+ if (error && child.exitCode === null)
771
+ child.kill("SIGTERM");
772
+ });
773
+ else if (child.exitCode === null)
774
+ child.kill("SIGTERM");
775
+ return;
776
+ }
777
+ if (!outputLimit && (child.exitCode !== null || child.signalCode !== null))
574
778
  return;
575
779
  try {
576
780
  if (platform() === "win32")
@@ -579,9 +783,39 @@ async function captureProcess(binary, args, cwd, env, timeoutSeconds) {
579
783
  process.kill(-child.pid, signal);
580
784
  }
581
785
  catch {
582
- child.kill(signal);
786
+ if (child.exitCode === null && child.signalCode === null)
787
+ child.kill(signal);
583
788
  }
584
789
  };
790
+ let outputLimitExceeded = false;
791
+ const filePeaks = new Map();
792
+ let pendingScan = null;
793
+ const outputBytes = () => stdoutBytes + stderrBytes + [...filePeaks.values()].reduce((sum, n) => sum + n, 0);
794
+ const checkOutput = () => {
795
+ if (outputLimit && outputBytes() > outputLimit.maxBytes) {
796
+ outputLimitExceeded = true;
797
+ terminate("SIGKILL");
798
+ }
799
+ };
800
+ const scanOutputFiles = async () => {
801
+ if (!outputLimit)
802
+ return;
803
+ await Promise.all((outputLimit.paths ?? []).map(async (path) => {
804
+ const info = await lstat(path).catch(() => null);
805
+ if (info?.isFile() && !info.isSymbolicLink())
806
+ filePeaks.set(path, Math.max(filePeaks.get(path) ?? 0, info.size));
807
+ }));
808
+ checkOutput();
809
+ };
810
+ const outputTimer = outputLimit?.paths?.length
811
+ ? setInterval(() => {
812
+ if (pendingScan)
813
+ return;
814
+ pendingScan = scanOutputFiles().finally(() => {
815
+ pendingScan = null;
816
+ });
817
+ }, 250)
818
+ : null;
585
819
  const cancel = () => {
586
820
  cancelled = true;
587
821
  terminate("SIGKILL");
@@ -595,29 +829,36 @@ async function captureProcess(binary, args, cwd, env, timeoutSeconds) {
595
829
  child.stdout.on("data", (chunk) => {
596
830
  out.update(chunk);
597
831
  stdoutBytes += chunk.length;
598
- if (stdoutBytes <= 1024 * 1024)
832
+ if (outputLimit ? stdoutBytes + stderrBytes <= outputLimit.maxBytes : stdoutBytes <= 1024 * 1024)
599
833
  stdout.push(chunk);
600
834
  else
601
835
  truncated = true;
836
+ checkOutput();
602
837
  });
603
838
  child.stderr.on("data", (chunk) => {
604
839
  err.update(chunk);
605
840
  stderrBytes += chunk.length;
606
- if (stderrBytes <= 1024 * 1024)
841
+ if (outputLimit ? stdoutBytes + stderrBytes <= outputLimit.maxBytes : stderrBytes <= 1024 * 1024)
607
842
  stderr.push(chunk);
608
843
  else
609
844
  truncated = true;
845
+ checkOutput();
610
846
  });
611
847
  child.on("error", () => {
612
848
  spawnFailed = true;
613
849
  });
614
- child.on("close", (exitCode, signal) => {
850
+ child.on("close", async (exitCode, signal) => {
615
851
  clearTimeout(timer);
852
+ if (outputTimer)
853
+ clearInterval(outputTimer);
854
+ if (pendingScan)
855
+ await pendingScan;
856
+ await scanOutputFiles();
616
857
  process.off("SIGINT", cancel);
617
858
  process.off("SIGTERM", cancel);
618
859
  resolvePromise({
619
- exitCode: spawnFailed ? null : exitCode,
620
- signal,
860
+ exitCode: supervised ? (supervisorResult?.exitCode ?? null) : spawnFailed ? null : exitCode,
861
+ signal: supervised ? (supervisorResult?.signal ?? signal) : signal,
621
862
  startedAt,
622
863
  finishedAt: new Date().toISOString(),
623
864
  wallSeconds: Number(process.hrtime.bigint() - start) / 1e9,
@@ -628,8 +869,9 @@ async function captureProcess(binary, args, cwd, env, timeoutSeconds) {
628
869
  stdoutBytes,
629
870
  stderrBytes,
630
871
  truncated,
631
- timedOut,
872
+ timedOut: timedOut || Boolean(supervisorResult?.timedOut),
632
873
  cancelled,
874
+ ...(outputLimit ? { outputLimitExceeded, observedOutputBytes: outputBytes() } : {}),
633
875
  });
634
876
  });
635
877
  });