@tiangong-ai/cli 0.0.60 → 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 (239) hide show
  1. package/AGENTS.md +17 -6
  2. package/README.md +577 -55
  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 +341 -28
  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/acquisition-routes.d.ts +1 -1
  72. package/dist/research/workspace/acquisition-routes.js +2 -11
  73. package/dist/research/workspace/acquisition-routes.js.map +1 -1
  74. package/dist/research/workspace/artifact-read-audit.d.ts +16 -0
  75. package/dist/research/workspace/artifact-read-audit.js +176 -0
  76. package/dist/research/workspace/artifact-read-audit.js.map +1 -0
  77. package/dist/research/workspace/artifact-trace.d.ts +12 -0
  78. package/dist/research/workspace/artifact-trace.js +109 -0
  79. package/dist/research/workspace/artifact-trace.js.map +1 -0
  80. package/dist/research/workspace/artifact-view-mcp.d.ts +75 -0
  81. package/dist/research/workspace/artifact-view-mcp.js +214 -0
  82. package/dist/research/workspace/artifact-view-mcp.js.map +1 -0
  83. package/dist/research/workspace/artifact-views.d.ts +100 -0
  84. package/dist/research/workspace/artifact-views.js +395 -0
  85. package/dist/research/workspace/artifact-views.js.map +1 -0
  86. package/dist/research/workspace/audit-bundle.d.ts +4 -0
  87. package/dist/research/workspace/audit-bundle.js +130 -11
  88. package/dist/research/workspace/audit-bundle.js.map +1 -1
  89. package/dist/research/workspace/broker.js +25 -4
  90. package/dist/research/workspace/broker.js.map +1 -1
  91. package/dist/research/workspace/constants.d.ts +1 -0
  92. package/dist/research/workspace/constants.js +1 -0
  93. package/dist/research/workspace/constants.js.map +1 -1
  94. package/dist/research/workspace/content-evidence.d.ts +1 -0
  95. package/dist/research/workspace/content-evidence.js +1 -1
  96. package/dist/research/workspace/content-evidence.js.map +1 -1
  97. package/dist/research/workspace/credentials.js +2 -1
  98. package/dist/research/workspace/credentials.js.map +1 -1
  99. package/dist/research/workspace/data-evidence-adapter.d.ts +71 -0
  100. package/dist/research/workspace/data-evidence-adapter.js +269 -59
  101. package/dist/research/workspace/data-evidence-adapter.js.map +1 -1
  102. package/dist/research/workspace/data-evidence-view.d.ts +58 -0
  103. package/dist/research/workspace/data-evidence-view.js +411 -0
  104. package/dist/research/workspace/data-evidence-view.js.map +1 -0
  105. package/dist/research/workspace/evidence-ledger.js +32 -29
  106. package/dist/research/workspace/evidence-ledger.js.map +1 -1
  107. package/dist/research/workspace/evidence.d.ts +28 -0
  108. package/dist/research/workspace/evidence.js +48 -0
  109. package/dist/research/workspace/evidence.js.map +1 -1
  110. package/dist/research/workspace/executor.d.ts +32 -2
  111. package/dist/research/workspace/executor.js +286 -42
  112. package/dist/research/workspace/executor.js.map +1 -1
  113. package/dist/research/workspace/investigation-attempt.d.ts +159 -0
  114. package/dist/research/workspace/investigation-attempt.js +619 -0
  115. package/dist/research/workspace/investigation-attempt.js.map +1 -0
  116. package/dist/research/workspace/investigation-audit.d.ts +20 -0
  117. package/dist/research/workspace/investigation-audit.js +306 -0
  118. package/dist/research/workspace/investigation-audit.js.map +1 -0
  119. package/dist/research/workspace/investigation-candidate.d.ts +84 -0
  120. package/dist/research/workspace/investigation-candidate.js +156 -0
  121. package/dist/research/workspace/investigation-candidate.js.map +1 -0
  122. package/dist/research/workspace/investigation-certification.d.ts +39 -0
  123. package/dist/research/workspace/investigation-certification.js +152 -0
  124. package/dist/research/workspace/investigation-certification.js.map +1 -0
  125. package/dist/research/workspace/investigation-close.d.ts +47 -0
  126. package/dist/research/workspace/investigation-close.js +153 -0
  127. package/dist/research/workspace/investigation-close.js.map +1 -0
  128. package/dist/research/workspace/investigation-lineage.d.ts +8 -0
  129. package/dist/research/workspace/investigation-lineage.js +10 -0
  130. package/dist/research/workspace/investigation-lineage.js.map +1 -0
  131. package/dist/research/workspace/investigation-observer.d.ts +22 -0
  132. package/dist/research/workspace/investigation-observer.js +122 -0
  133. package/dist/research/workspace/investigation-observer.js.map +1 -0
  134. package/dist/research/workspace/investigation-promotion.d.ts +129 -0
  135. package/dist/research/workspace/investigation-promotion.js +316 -0
  136. package/dist/research/workspace/investigation-promotion.js.map +1 -0
  137. package/dist/research/workspace/investigation-requirements.d.ts +5 -0
  138. package/dist/research/workspace/investigation-requirements.js +32 -0
  139. package/dist/research/workspace/investigation-requirements.js.map +1 -0
  140. package/dist/research/workspace/investigation-resources.d.ts +12 -0
  141. package/dist/research/workspace/investigation-resources.js +78 -0
  142. package/dist/research/workspace/investigation-resources.js.map +1 -0
  143. package/dist/research/workspace/investigation-source-export.d.ts +6 -0
  144. package/dist/research/workspace/investigation-source-export.js +68 -0
  145. package/dist/research/workspace/investigation-source-export.js.map +1 -0
  146. package/dist/research/workspace/investigation-store.d.ts +9 -0
  147. package/dist/research/workspace/investigation-store.js +55 -0
  148. package/dist/research/workspace/investigation-store.js.map +1 -0
  149. package/dist/research/workspace/investigation.d.ts +424 -0
  150. package/dist/research/workspace/investigation.js +716 -0
  151. package/dist/research/workspace/investigation.js.map +1 -0
  152. package/dist/research/workspace/native-process-supervisor.d.ts +1 -0
  153. package/dist/research/workspace/native-process-supervisor.js +97 -0
  154. package/dist/research/workspace/native-process-supervisor.js.map +1 -0
  155. package/dist/research/workspace/native-run.d.ts +150 -0
  156. package/dist/research/workspace/native-run.js +879 -0
  157. package/dist/research/workspace/native-run.js.map +1 -0
  158. package/dist/research/workspace/preflight.d.ts +11 -2
  159. package/dist/research/workspace/preflight.js +28 -20
  160. package/dist/research/workspace/preflight.js.map +1 -1
  161. package/dist/research/workspace/project-authority.js +2 -0
  162. package/dist/research/workspace/project-authority.js.map +1 -1
  163. package/dist/research/workspace/project-budget.d.ts +41 -0
  164. package/dist/research/workspace/project-budget.js +265 -0
  165. package/dist/research/workspace/project-budget.js.map +1 -0
  166. package/dist/research/workspace/project-mutations.d.ts +1 -1
  167. package/dist/research/workspace/project-mutations.js +29 -3
  168. package/dist/research/workspace/project-mutations.js.map +1 -1
  169. package/dist/research/workspace/projects.d.ts +66 -0
  170. package/dist/research/workspace/projects.js +182 -3
  171. package/dist/research/workspace/projects.js.map +1 -1
  172. package/dist/research/workspace/provider-budget.d.ts +3 -0
  173. package/dist/research/workspace/provider-budget.js +45 -0
  174. package/dist/research/workspace/provider-budget.js.map +1 -0
  175. package/dist/research/workspace/provider-state.d.ts +1 -0
  176. package/dist/research/workspace/provider-state.js +20 -0
  177. package/dist/research/workspace/provider-state.js.map +1 -0
  178. package/dist/research/workspace/publication-lineage.d.ts +34 -0
  179. package/dist/research/workspace/publication-lineage.js +165 -0
  180. package/dist/research/workspace/publication-lineage.js.map +1 -0
  181. package/dist/research/workspace/publication-workflow.d.ts +24 -0
  182. package/dist/research/workspace/publication-workflow.js +118 -14
  183. package/dist/research/workspace/publication-workflow.js.map +1 -1
  184. package/dist/research/workspace/request-provenance.d.ts +67 -0
  185. package/dist/research/workspace/request-provenance.js +115 -0
  186. package/dist/research/workspace/request-provenance.js.map +1 -0
  187. package/dist/research/workspace/review-executor.d.ts +6 -1
  188. package/dist/research/workspace/review-executor.js +39 -14
  189. package/dist/research/workspace/review-executor.js.map +1 -1
  190. package/dist/research/workspace/runtime.d.ts +64 -1
  191. package/dist/research/workspace/runtime.js +359 -112
  192. package/dist/research/workspace/runtime.js.map +1 -1
  193. package/dist/research/workspace/sanitization.js +1 -1
  194. package/dist/research/workspace/sanitization.js.map +1 -1
  195. package/dist/research/workspace/schema-compatibility.d.ts +5 -0
  196. package/dist/research/workspace/schema-compatibility.js +55 -0
  197. package/dist/research/workspace/schema-compatibility.js.map +1 -0
  198. package/dist/research/workspace/scientific-amendment.d.ts +103 -0
  199. package/dist/research/workspace/scientific-amendment.js +537 -0
  200. package/dist/research/workspace/scientific-amendment.js.map +1 -0
  201. package/dist/research/workspace/scientific-design.js +8 -17
  202. package/dist/research/workspace/scientific-design.js.map +1 -1
  203. package/dist/research/workspace/scientific-fulfillment-audit.d.ts +21 -0
  204. package/dist/research/workspace/scientific-fulfillment-audit.js +217 -0
  205. package/dist/research/workspace/scientific-fulfillment-audit.js.map +1 -0
  206. package/dist/research/workspace/scientific-fulfillment.d.ts +130 -0
  207. package/dist/research/workspace/scientific-fulfillment.js +553 -0
  208. package/dist/research/workspace/scientific-fulfillment.js.map +1 -0
  209. package/dist/research/workspace/scientific-review-execution.js +236 -75
  210. package/dist/research/workspace/scientific-review-execution.js.map +1 -1
  211. package/dist/research/workspace/scientific-review.d.ts +6 -1
  212. package/dist/research/workspace/scientific-review.js +228 -52
  213. package/dist/research/workspace/scientific-review.js.map +1 -1
  214. package/dist/research/workspace/setup-catalog.js +3 -3
  215. package/dist/research/workspace/setup-release.d.ts +22 -0
  216. package/dist/research/workspace/setup-release.js +426 -0
  217. package/dist/research/workspace/setup-release.js.map +1 -0
  218. package/dist/research/workspace/setup-runtime-integrity.d.ts +2 -0
  219. package/dist/research/workspace/setup-runtime-integrity.js +31 -0
  220. package/dist/research/workspace/setup-runtime-integrity.js.map +1 -0
  221. package/dist/research/workspace/setup-upgrade.d.ts +17 -0
  222. package/dist/research/workspace/setup-upgrade.js +784 -0
  223. package/dist/research/workspace/setup-upgrade.js.map +1 -0
  224. package/dist/research/workspace/setup.d.ts +31 -39
  225. package/dist/research/workspace/setup.js +220 -17
  226. package/dist/research/workspace/setup.js.map +1 -1
  227. package/dist/research/workspace/task-acceptance.d.ts +17 -2
  228. package/dist/research/workspace/task-acceptance.js +106 -10
  229. package/dist/research/workspace/task-acceptance.js.map +1 -1
  230. package/dist/research/workspace/task-audit.d.ts +5 -1
  231. package/dist/research/workspace/task-audit.js +70 -5
  232. package/dist/research/workspace/task-audit.js.map +1 -1
  233. package/dist/research/workspace/task-contract.d.ts +4 -1
  234. package/dist/research/workspace/task-contract.js +26 -5
  235. package/dist/research/workspace/task-contract.js.map +1 -1
  236. package/dist/research/workspace/types.d.ts +50 -3
  237. package/dist/research/workspace/workspace.js +34 -4
  238. package/dist/research/workspace/workspace.js.map +1 -1
  239. package/package.json +2 -1
@@ -1,15 +1,20 @@
1
1
  import { Ajv2020 } from "ajv/dist/2020.js";
2
- import { lstat, readFile } from "node:fs/promises";
2
+ import { constants } from "node:fs";
3
+ import { chmod, copyFile, lstat, mkdir, readFile } from "node:fs/promises";
3
4
  import { isAbsolute, join, relative, resolve, sep } from "node:path";
5
+ import { scientificAmendmentLocator } from "./scientific-amendment.js";
4
6
  import { CliError } from "../../errors.js";
5
7
  import { taskContext } from "./task-contract.js";
8
+ import { nativeRunArtifactRecords } from "./native-run.js";
9
+ import { loadEvidenceArtifactRecords } from "./artifacts.js";
6
10
  import { sourceTypeRequirementGaps } from "./evidence-role-coverage.js";
7
11
  import { appendJournalEvent, verifyJournal } from "./journal.js";
8
12
  import { loadProject, nextScientificGate, saveProject } from "./projects.js";
9
13
  import { projectWithEffectiveAuthority, readProjectAuthorityIndex } from "./project-authority.js";
10
14
  import { assertResearchPolicyBinding } from "./research-policy.js";
11
- import { evaluateScientificDesign, parseScientificDesign, scientificDesignPolicyGaps, } from "./scientific-design.js";
15
+ import { evaluateScientificDesign, scientificDesignPolicyGaps, } from "./scientific-design.js";
12
16
  import { resolveScientificObjectBinding } from "./scientific-objects.js";
17
+ import { loadScientificFulfillmentView, scientificFulfillmentLocator, } from "./scientific-fulfillment.js";
13
18
  import { sanitizeResearchValue } from "./sanitization.js";
14
19
  import { canonicalJson, fileRecord, isObject, pathExists, resolveContained, sha256File, sha256Text, workspacePaths, writeBytesAtomic, writeJsonAtomic, writeTextAtomic, } from "./storage.js";
15
20
  import { loadWorkspaceConfig, verifyDoctorAttestation, withWorkspaceLock } from "./workspace.js";
@@ -265,7 +270,8 @@ export async function prepareScientificReview(input) {
265
270
  exitCode: 3,
266
271
  });
267
272
  }
268
- const design = await loadBoundScientificDesign(input.root, project);
273
+ const fulfillment = await loadScientificFulfillmentView(input.root, project, input.role);
274
+ const design = fulfillment.contract;
269
275
  const assessment = await readAssessment(input.assessmentPath, input.role);
270
276
  if (assessment.designSha256 !== project.scientificDesign.designSha256) {
271
277
  throw new CliError("Scientific assessment does not match the frozen design.", {
@@ -274,6 +280,90 @@ export async function prepareScientificReview(input) {
274
280
  });
275
281
  }
276
282
  const stageInputs = await stageInputRecords(input.root, project, input.role, design, assessment, input.canaryArtifactPaths ?? []);
283
+ const currentTask = await taskContext(input.root, project.id);
284
+ if (currentTask) {
285
+ for (const record of await nativeRunArtifactRecords(input.root, project.id)) {
286
+ const locator = `projects/${project.id}/${record.path}`;
287
+ stageInputs.push({
288
+ ...record,
289
+ path: locator,
290
+ sourceLocator: locator,
291
+ purpose: "native-calculation",
292
+ ownerId: project.id,
293
+ hashBasis: "raw-file-bytes",
294
+ mediaType: record.path.endsWith(".json")
295
+ ? "application/json"
296
+ : "application/octet-stream",
297
+ objectKind: null,
298
+ registrationRecordSha256: null,
299
+ });
300
+ }
301
+ }
302
+ const requestSource = currentTask?.requestProvenance.source;
303
+ if (requestSource) {
304
+ const locator = `projects/${project.id}/task/request-sources/${requestSource.objectSha256}.json`;
305
+ stageInputs.push({
306
+ ...(await fileRecord(resolveContained(paths.control, locator), locator)),
307
+ purpose: "original-request-source",
308
+ ownerId: project.id,
309
+ sourceLocator: locator,
310
+ hashBasis: "raw-file-bytes",
311
+ mediaType: "application/json",
312
+ objectKind: null,
313
+ registrationRecordSha256: null,
314
+ });
315
+ }
316
+ if (input.role !== "research-design")
317
+ stageInputs.push(...(await modelObjectInputs(input.root, project.id, design)));
318
+ const amendmentInputs = new Set();
319
+ for (const record of fulfillment.amendments) {
320
+ for (const locator of [
321
+ scientificAmendmentLocator(project.id, record.recordSha256),
322
+ record.amendmentAuthorization.sourceLocator,
323
+ record.design.objectLocator,
324
+ ]) {
325
+ if (amendmentInputs.has(locator))
326
+ continue;
327
+ amendmentInputs.add(locator);
328
+ stageInputs.push({
329
+ ...(await fileRecord(resolveContained(paths.control, locator), locator)),
330
+ purpose: "scientific-amendment",
331
+ ownerId: project.id,
332
+ sourceLocator: locator,
333
+ hashBasis: "raw-file-bytes",
334
+ mediaType: locator.endsWith(".txt") ? "text/plain" : "application/json",
335
+ objectKind: null,
336
+ registrationRecordSha256: null,
337
+ });
338
+ }
339
+ }
340
+ if (fulfillment.records.length || fulfillment.amendments.length) {
341
+ for (const record of fulfillment.records) {
342
+ const locator = scientificFulfillmentLocator(project.id, record.recordSha256);
343
+ stageInputs.push({
344
+ ...(await fileRecord(resolveContained(paths.control, locator), locator)),
345
+ purpose: "scientific-fulfillment",
346
+ ownerId: project.id,
347
+ sourceLocator: locator,
348
+ hashBasis: "raw-file-bytes",
349
+ mediaType: "application/json",
350
+ objectKind: null,
351
+ registrationRecordSha256: null,
352
+ });
353
+ }
354
+ const locator = `projects/${project.id}/scientific/design/views/${fulfillment.effectiveSha256}.json`;
355
+ await writeImmutableJson(resolveContained(paths.control, locator), design);
356
+ stageInputs.push({
357
+ ...(await fileRecord(resolveContained(paths.control, locator), locator)),
358
+ purpose: "effective-scientific-design",
359
+ ownerId: project.id,
360
+ sourceLocator: locator,
361
+ hashBasis: "raw-file-bytes",
362
+ mediaType: "application/json",
363
+ objectKind: null,
364
+ registrationRecordSha256: null,
365
+ });
366
+ }
277
367
  const assessmentSha256 = exactJsonSha256(assessment);
278
368
  const assessmentLocator = `projects/${project.id}/scientific/assessments/${input.role}/${assessmentSha256}.json`;
279
369
  await writeImmutableJson(join(paths.control, assessmentLocator), assessment);
@@ -289,7 +379,7 @@ export async function prepareScientificReview(input) {
289
379
  };
290
380
  await assertBoundPolicyObject(input.root, project, packetPolicy);
291
381
  const designEvaluation = evaluateScientificDesign(design);
292
- const issues = evaluateAssessment(input.role, assessment, design, project.evidenceRequirements, project.publicationPolicy, await assessmentEvidenceContext(input.root, project, input.role, stageInputs));
382
+ const issues = evaluateAssessment(input.role, assessment, design, project.evidenceRequirements, project.publicationPolicy, await assessmentEvidenceContext(input.root, project, input.role, stageInputs), fulfillment.deferredObjectRuleIds);
293
383
  const futureGateObligations = scientificFutureGateObligations(design, input.role);
294
384
  const packetCore = {
295
385
  schemaVersion: 1,
@@ -299,12 +389,17 @@ export async function prepareScientificReview(input) {
299
389
  design: {
300
390
  sha256: project.scientificDesign.designSha256,
301
391
  objectLocator: project.scientificDesign.objectLocator,
392
+ amendmentSha256: project.scientificDesign.amendmentSha256 ?? null,
393
+ fulfillment: {
394
+ headSha256: fulfillment.headSha256,
395
+ effectiveSha256: fulfillment.effectiveSha256,
396
+ },
302
397
  },
303
398
  policy: packetPolicy,
304
399
  reviewer: { agent: input.reviewerAgent, sessionSha256: reviewerSessionSha256 },
305
400
  preparedAt: new Date().toISOString(),
306
401
  stageInputs,
307
- taskContract: await taskContext(input.root, project.id),
402
+ taskContract: currentTask,
308
403
  assessment: { sha256: assessmentSha256, objectLocator: assessmentLocator },
309
404
  mechanicalAssessment: {
310
405
  canPass: issues.length === 0 && assessment.recommendation === "pass",
@@ -331,7 +426,10 @@ export async function prepareScientificReview(input) {
331
426
  newGenerationOnMaterialChange: true,
332
427
  revisionReserveIncluded: true,
333
428
  },
334
- instructions: reviewInstructions(input.role),
429
+ instructions: [
430
+ ...reviewInstructions(input.role),
431
+ "Scientific fulfillment files supply only previously declared pending object slots. Original design bytes remain immutable. Owner-authorized lifecycle/binding amendments, when present, are staged with their exact changes, new design version and supplied authorization source. Review the current effective design; supplied confirmation does not authenticate human identity or establish scientific satisfaction. Discharging an object-filing blocker does not establish calibration, justification or scientific correctness: independently assess the actual code, locks and source-bound values under every original policy rule.",
432
+ ],
335
433
  };
336
434
  const packetSha256 = sha256Text(canonicalJson(packetCore));
337
435
  const packet = { ...packetCore, packetSha256 };
@@ -503,8 +601,9 @@ export { readReview as readScientificReviewOutput };
503
601
  export async function assertScientificGateForStage(root, project, stage) {
504
602
  if (!project.scientificDesign)
505
603
  return;
506
- const design = await loadBoundScientificDesign(root, project);
507
604
  for (const role of requiredGateRoles(stage)) {
605
+ const fulfillment = await loadScientificFulfillmentView(root, project, role);
606
+ const design = fulfillment.contract;
508
607
  const gate = project.scientificDesign.gates[role];
509
608
  if (gate.status !== "passed" || !gate.packetSha256 || !gate.reviewSha256) {
510
609
  throw new CliError(`Scientific ${role} review must pass before ${stage}.`, {
@@ -516,7 +615,7 @@ export async function assertScientificGateForStage(root, project, stage) {
516
615
  const packet = await loadBoundPacket(root, project, role, gate.packetSha256);
517
616
  const review = await loadBoundReview(root, project, role, gate.reviewSha256);
518
617
  const assessment = await loadBoundAssessment(root, project, role, packet);
519
- const currentIssues = evaluateAssessment(role, assessment, design, project.evidenceRequirements, project.publicationPolicy, await assessmentEvidenceContext(root, project, role, packet.stageInputs)).map((issue) => issue.code);
618
+ const currentIssues = evaluateAssessment(role, assessment, design, project.evidenceRequirements, project.publicationPolicy, await assessmentEvidenceContext(root, project, role, packet.stageInputs), fulfillment.deferredObjectRuleIds).map((issue) => issue.code);
520
619
  if (packet.assessment.sha256 !== gate.assessmentSha256 ||
521
620
  packet.reviewer.sessionSha256 !== gate.reviewerSessionSha256 ||
522
621
  review.packetSha256 !== packet.packetSha256 ||
@@ -550,7 +649,7 @@ export async function inspectScientificReviewStatus(root, projectId, knownProjec
550
649
  gates: structuredClone(project.scientificDesign.gates),
551
650
  };
552
651
  }
553
- function evaluateAssessment(role, assessment, design, requirements, policy, evidenceContext = null) {
652
+ function evaluateAssessment(role, assessment, design, requirements, policy, evidenceContext = null, deferredObjectRuleIds = []) {
554
653
  const issues = new Map();
555
654
  const add = (code, message, objectIds = []) => {
556
655
  if (!issues.has(code))
@@ -566,25 +665,26 @@ function evaluateAssessment(role, assessment, design, requirements, policy, evid
566
665
  add(`POLICY_${gap.replaceAll(/[^A-Za-z0-9]+/gu, "_").toUpperCase()}`, "The frozen scientific design no longer matches its approved Research Policy disposition contract.", [gap]);
567
666
  }
568
667
  const duePolicyRules = design.policyRuleDispositions.filter((disposition) => disposition.status === "planned" &&
668
+ !deferredObjectRuleIds.includes(disposition.ruleId) &&
569
669
  disposition.dueGate !== "publication-freeze" &&
570
670
  gateRank[disposition.dueGate] <= gateRank[role]);
571
671
  if (duePolicyRules.length) {
572
- add("POLICY_RULE_DUE_UNRESOLVED", "A planned Research Policy rule reached its declared early-review gate without a new design generation that records how it was satisfied.", duePolicyRules.map((disposition) => disposition.ruleId));
672
+ add("POLICY_RULE_DUE_UNRESOLVED", "A planned Research Policy rule reached its early-review gate without its declared object fulfillment or reviewed design resolution.", duePolicyRules.map((disposition) => disposition.ruleId));
573
673
  }
574
674
  const dueUncertaintyFreezes = design.uncertaintyParameters.filter((parameter) => parameter.stateValueStatus === "pending-source-acquisition" &&
575
675
  gateRank[parameter.freezeBeforeGate] <= gateRank[role]);
576
676
  if (dueUncertaintyFreezes.length) {
577
- add("UNCERTAINTY_STATE_VALUES_NOT_FROZEN", "Source-derived uncertainty values reached their declared freeze gate without a new authoritative design generation containing exact states and source bindings.", dueUncertaintyFreezes.map((parameter) => parameter.id));
677
+ add("UNCERTAINTY_STATE_VALUES_NOT_FROZEN", "Source-derived uncertainty values reached their freeze gate without exact states and admitted source bindings.", dueUncertaintyFreezes.map((parameter) => parameter.id));
578
678
  }
579
679
  const dueModelImplementations = design.identity.modelStructures.filter((model) => model.implementationStatus === "pending-source-acquisition" &&
580
680
  gateRank[model.implementationFreezeBeforeGate] <= gateRank[role]);
581
681
  if (dueModelImplementations.length) {
582
- add("MODEL_IMPLEMENTATION_NOT_FROZEN", "A model implementation reached its declared freeze gate without a new authoritative design generation binding executable model bytes.", dueModelImplementations.map((model) => model.id));
682
+ add("MODEL_IMPLEMENTATION_NOT_FROZEN", "A model implementation reached its freeze gate without binding the declared executable bytes.", dueModelImplementations.map((model) => model.id));
583
683
  }
584
684
  const dueModelEnvironmentLocks = design.identity.modelStructures.filter((model) => model.environmentLockStatus === "pending-runtime-lock" &&
585
685
  gateRank[model.environmentLockFreezeBeforeGate] <= gateRank[role]);
586
686
  if (dueModelEnvironmentLocks.length) {
587
- add("MODEL_ENVIRONMENT_LOCK_NOT_FROZEN", "A model environment reached its declared freeze gate without a new authoritative design generation binding an exact runtime and dependency lock.", dueModelEnvironmentLocks.map((model) => model.id));
687
+ add("MODEL_ENVIRONMENT_LOCK_NOT_FROZEN", "A model environment reached its freeze gate without an exact runtime and dependency lock.", dueModelEnvironmentLocks.map((model) => model.id));
588
688
  }
589
689
  if (role === "research-design") {
590
690
  const value = assessment;
@@ -848,30 +948,7 @@ async function stageInputRecords(root, project, role, design, assessment, canary
848
948
  registrationRecordSha256: null,
849
949
  });
850
950
  };
851
- for (const model of design.identity.modelStructures) {
852
- if (model.implementationStatus === "executable-frozen") {
853
- records.push(await promoteRegisteredModelObject({
854
- root,
855
- projectId: project.id,
856
- objectKind: "model-implementation",
857
- sourceLocator: model.implementationArtifactLocator,
858
- sha256: model.implementationArtifactSha256,
859
- purpose: "model-implementation",
860
- ownerId: model.id,
861
- }));
862
- }
863
- if (model.environmentLockStatus === "exact-frozen") {
864
- records.push(await promoteRegisteredModelObject({
865
- root,
866
- projectId: project.id,
867
- objectKind: "environment-lock",
868
- sourceLocator: model.environmentLockLocator,
869
- sha256: model.environmentLockSha256,
870
- purpose: "model-environment-lock",
871
- ownerId: model.id,
872
- }));
873
- }
874
- }
951
+ records.push(...(await modelObjectInputs(root, project.id, design)));
875
952
  for (const gap of design.knownGaps) {
876
953
  for (const artifact of gap.sourceArtifacts) {
877
954
  await promoteLegacyJson({
@@ -895,8 +972,7 @@ async function stageInputRecords(root, project, role, design, assessment, canary
895
972
  });
896
973
  }
897
974
  const relativeOutputs = ["outputs/acquisition.json", "outputs/evidence-snapshot.json"];
898
- if (role === "evidence-construct" &&
899
- (await pathExists(join(projectRoot, "outputs", "content-snapshot.json")))) {
975
+ if (await pathExists(join(projectRoot, "outputs", "content-snapshot.json"))) {
900
976
  relativeOutputs.push("outputs/content-snapshot.json");
901
977
  }
902
978
  const stageOutputs = await Promise.all(relativeOutputs.map(async (relativePath) => {
@@ -912,6 +988,62 @@ async function stageInputRecords(root, project, role, design, assessment, canary
912
988
  registrationRecordSha256: null,
913
989
  };
914
990
  }));
991
+ const snapshot = await readExactJson(join(projectRoot, "outputs/evidence-snapshot.json"), "Scientific evidence snapshot", "RESEARCH_SCIENTIFIC_GATE_INVALID");
992
+ if (!isObject(snapshot) || !Array.isArray(snapshot.artifacts))
993
+ throw scientificGateError("Scientific evidence snapshot has no exact artifact set.", role);
994
+ if (snapshot.artifacts.length) {
995
+ const registered = new Map((await loadEvidenceArtifactRecords(root, project.id)).map((artifact) => [
996
+ artifact.artifactId,
997
+ artifact,
998
+ ]));
999
+ for (const selected of snapshot.artifacts) {
1000
+ const artifact = isObject(selected) && typeof selected.artifactId === "string"
1001
+ ? registered.get(selected.artifactId)
1002
+ : undefined;
1003
+ if (!artifact ||
1004
+ !isObject(selected) ||
1005
+ artifact.sha256 !== selected.sha256 ||
1006
+ artifact.bytes !== selected.bytes ||
1007
+ artifact.locator !== selected.locator ||
1008
+ artifact.mediaType !== selected.mediaType)
1009
+ throw scientificGateError("Scientific review source is not the exact frozen registered artifact.", role);
1010
+ let directory = projectRoot;
1011
+ for (const part of ["scientific", "evidence-objects", artifact.sha256]) {
1012
+ directory = join(directory, part);
1013
+ await mkdir(directory, { mode: 0o700 }).catch((error) => {
1014
+ if (error.code !== "EEXIST")
1015
+ throw error;
1016
+ });
1017
+ const info = await lstat(directory);
1018
+ if (!info.isDirectory() || info.isSymbolicLink())
1019
+ throw scientificGateError("Scientific evidence directories cannot redirect through links.", role);
1020
+ }
1021
+ const path = `projects/${project.id}/scientific/evidence-objects/${artifact.sha256}/blob`;
1022
+ const target = resolveContained(paths.control, path);
1023
+ if (!(await pathExists(target))) {
1024
+ await copyFile(resolveContained(paths.control, artifact.locator), target, constants.COPYFILE_EXCL | constants.COPYFILE_FICLONE);
1025
+ await chmod(target, 0o444);
1026
+ }
1027
+ const info = await lstat(target);
1028
+ if (!info.isFile() ||
1029
+ info.isSymbolicLink() ||
1030
+ info.size !== artifact.bytes ||
1031
+ (await sha256File(target)) !== artifact.sha256)
1032
+ throw scientificGateError("Scientific review source bytes changed while staging.", role);
1033
+ stageOutputs.push({
1034
+ path,
1035
+ sha256: artifact.sha256,
1036
+ bytes: artifact.bytes,
1037
+ purpose: "acquired-evidence",
1038
+ ownerId: artifact.artifactId,
1039
+ sourceLocator: artifact.locator,
1040
+ hashBasis: "raw-file-bytes",
1041
+ mediaType: artifact.mediaType,
1042
+ objectKind: null,
1043
+ registrationRecordSha256: null,
1044
+ });
1045
+ }
1046
+ }
915
1047
  if (role !== "evidence-construct") {
916
1048
  if (canaryArtifactPaths.length)
917
1049
  throw canaryArtifactError("Only evidence-construct accepts canary artifacts.");
@@ -985,6 +1117,32 @@ async function stageInputRecords(root, project, role, design, assessment, canary
985
1117
  }
986
1118
  return [...stageOutputs, ...canaryRecords];
987
1119
  }
1120
+ async function modelObjectInputs(root, projectId, design) {
1121
+ const records = [];
1122
+ for (const model of design.identity.modelStructures) {
1123
+ if (model.implementationStatus === "executable-frozen")
1124
+ records.push(await promoteRegisteredModelObject({
1125
+ root,
1126
+ projectId,
1127
+ objectKind: "model-implementation",
1128
+ sourceLocator: model.implementationArtifactLocator,
1129
+ sha256: model.implementationArtifactSha256,
1130
+ purpose: "model-implementation",
1131
+ ownerId: model.id,
1132
+ }));
1133
+ if (model.environmentLockStatus === "exact-frozen")
1134
+ records.push(await promoteRegisteredModelObject({
1135
+ root,
1136
+ projectId,
1137
+ objectKind: "environment-lock",
1138
+ sourceLocator: model.environmentLockLocator,
1139
+ sha256: model.environmentLockSha256,
1140
+ purpose: "model-environment-lock",
1141
+ ownerId: model.id,
1142
+ }));
1143
+ }
1144
+ return records;
1145
+ }
988
1146
  async function promoteRegisteredModelObject(input) {
989
1147
  const resolvedObject = await resolveScientificObjectBinding({
990
1148
  root: input.root,
@@ -1120,19 +1278,7 @@ async function assessmentEvidenceContext(root, project, role, stageInputs) {
1120
1278
  };
1121
1279
  }
1122
1280
  async function loadBoundScientificDesign(root, project) {
1123
- const binding = project.scientificDesign;
1124
- if (!binding)
1125
- throw scientificGateError("Project has no scientific design binding.");
1126
- const path = join(workspacePaths(root).control, binding.objectLocator);
1127
- const value = await readExactJson(path, "Scientific design", "RESEARCH_SCIENTIFIC_GATE_INVALID");
1128
- const design = parseScientificDesign(value);
1129
- const normalized = normalizedJson(design);
1130
- if (design.projectId !== project.id ||
1131
- sha256Text(normalized) !== binding.designSha256 ||
1132
- (await readFile(path, "utf8")) !== normalized) {
1133
- throw scientificGateError("Frozen scientific design failed its exact binding.");
1134
- }
1135
- return design;
1281
+ return (await loadScientificFulfillmentView(root, project)).contract;
1136
1282
  }
1137
1283
  async function loadBoundPacket(root, project, role, packetSha256) {
1138
1284
  const path = join(workspacePaths(root).projects, project.id, "scientific", "review-packets", role, `${packetSha256}.json`);
@@ -1147,6 +1293,19 @@ async function loadBoundPacket(root, project, role, packetSha256) {
1147
1293
  throw scientificGateError("Scientific review packet failed its immutable hash binding.", role);
1148
1294
  }
1149
1295
  await assertBoundPolicyObject(root, project, value.policy);
1296
+ const fulfillment = await loadScientificFulfillmentView(root, project, role);
1297
+ if ((value.design.amendmentSha256 ?? null) !== (project.scientificDesign?.amendmentSha256 ?? null))
1298
+ throw scientificGateError("Scientific review has a stale amendment generation.", role);
1299
+ if (value.design.fulfillment) {
1300
+ if (value.design.fulfillment.effectiveSha256 !== fulfillment.effectiveSha256 ||
1301
+ (value.design.fulfillment.headSha256 !== null &&
1302
+ !fulfillment.records.some((record) => record.recordSha256 === value.design.fulfillment.headSha256))) {
1303
+ throw scientificGateError("Scientific review has a stale or uncommitted fulfillment view.", role);
1304
+ }
1305
+ }
1306
+ else if (canonicalJson(fulfillment.contract) !== canonicalJson(fulfillment.base)) {
1307
+ throw scientificGateError("Scientific review does not bind the fulfilled scientific objects.", role);
1308
+ }
1150
1309
  if (canonicalJson(value.taskContract ?? null) !== canonicalJson(await taskContext(root, project.id))) {
1151
1310
  throw scientificGateError("Scientific review no longer binds the current authorized task scope.", role);
1152
1311
  }
@@ -1312,6 +1471,17 @@ function isScientificReviewPacket(value, projectId, role, finalPublicationReview
1312
1471
  isObject(value.design) &&
1313
1472
  typeof value.design.sha256 === "string" &&
1314
1473
  typeof value.design.objectLocator === "string" &&
1474
+ (value.design.amendmentSha256 === undefined ||
1475
+ value.design.amendmentSha256 === null ||
1476
+ (typeof value.design.amendmentSha256 === "string" &&
1477
+ new RegExp(SHA256_PATTERN).test(value.design.amendmentSha256))) &&
1478
+ (value.design.fulfillment === undefined ||
1479
+ (isObject(value.design.fulfillment) &&
1480
+ (value.design.fulfillment.headSha256 === null ||
1481
+ (typeof value.design.fulfillment.headSha256 === "string" &&
1482
+ new RegExp(SHA256_PATTERN).test(value.design.fulfillment.headSha256))) &&
1483
+ typeof value.design.fulfillment.effectiveSha256 === "string" &&
1484
+ new RegExp(SHA256_PATTERN).test(value.design.fulfillment.effectiveSha256))) &&
1315
1485
  isObject(value.policy) &&
1316
1486
  typeof value.policy.resolvedPolicySha256 === "string" &&
1317
1487
  typeof value.policy.approvalSha256 === "string" &&
@@ -1337,6 +1507,12 @@ function isScientificReviewPacket(value, projectId, role, finalPublicationReview
1337
1507
  "inherited-gap",
1338
1508
  "model-implementation",
1339
1509
  "model-environment-lock",
1510
+ "scientific-fulfillment",
1511
+ "scientific-amendment",
1512
+ "effective-scientific-design",
1513
+ "original-request-source",
1514
+ "native-calculation",
1515
+ "acquired-evidence",
1340
1516
  "stage-output",
1341
1517
  ].includes(String(record.purpose)) &&
1342
1518
  typeof record.ownerId === "string" &&