@tiangong-ai/cli 0.0.59 → 0.0.61
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -2
- package/README.md +287 -41
- package/dist/research/orchestration.js +209 -62
- package/dist/research/orchestration.js.map +1 -1
- package/dist/research/workspace/acquisition-forecast.d.ts +3 -0
- package/dist/research/workspace/acquisition-forecast.js +28 -3
- package/dist/research/workspace/acquisition-forecast.js.map +1 -1
- package/dist/research/workspace/acquisition-revision.d.ts +13 -0
- package/dist/research/workspace/acquisition-revision.js +142 -0
- package/dist/research/workspace/acquisition-revision.js.map +1 -0
- package/dist/research/workspace/acquisition-routes.d.ts +1 -1
- package/dist/research/workspace/acquisition-routes.js +2 -11
- package/dist/research/workspace/acquisition-routes.js.map +1 -1
- package/dist/research/workspace/acquisition.d.ts +3 -1
- package/dist/research/workspace/acquisition.js +67 -35
- package/dist/research/workspace/acquisition.js.map +1 -1
- package/dist/research/workspace/analysis-run.d.ts +2 -0
- package/dist/research/workspace/analysis-run.js +28 -0
- package/dist/research/workspace/analysis-run.js.map +1 -0
- package/dist/research/workspace/artifact-read-audit.d.ts +16 -0
- package/dist/research/workspace/artifact-read-audit.js +176 -0
- package/dist/research/workspace/artifact-read-audit.js.map +1 -0
- package/dist/research/workspace/artifact-trace.d.ts +12 -0
- package/dist/research/workspace/artifact-trace.js +109 -0
- package/dist/research/workspace/artifact-trace.js.map +1 -0
- package/dist/research/workspace/artifact-view-mcp.d.ts +75 -0
- package/dist/research/workspace/artifact-view-mcp.js +214 -0
- package/dist/research/workspace/artifact-view-mcp.js.map +1 -0
- package/dist/research/workspace/artifact-views.d.ts +100 -0
- package/dist/research/workspace/artifact-views.js +395 -0
- package/dist/research/workspace/artifact-views.js.map +1 -0
- package/dist/research/workspace/artifacts.d.ts +3 -0
- package/dist/research/workspace/artifacts.js +13 -0
- package/dist/research/workspace/artifacts.js.map +1 -1
- package/dist/research/workspace/audit-bundle.d.ts +9 -0
- package/dist/research/workspace/audit-bundle.js +124 -8
- package/dist/research/workspace/audit-bundle.js.map +1 -1
- package/dist/research/workspace/content-evidence.d.ts +3 -0
- package/dist/research/workspace/content-evidence.js +149 -22
- package/dist/research/workspace/content-evidence.js.map +1 -1
- package/dist/research/workspace/executor.d.ts +5 -1
- package/dist/research/workspace/executor.js +106 -22
- package/dist/research/workspace/executor.js.map +1 -1
- package/dist/research/workspace/native-run.d.ts +77 -0
- package/dist/research/workspace/native-run.js +637 -0
- package/dist/research/workspace/native-run.js.map +1 -0
- package/dist/research/workspace/preflight.d.ts +8 -2
- package/dist/research/workspace/preflight.js +17 -14
- package/dist/research/workspace/preflight.js.map +1 -1
- package/dist/research/workspace/project-authority.d.ts +21 -0
- package/dist/research/workspace/project-authority.js +186 -0
- package/dist/research/workspace/project-authority.js.map +1 -0
- package/dist/research/workspace/project-mutations.d.ts +26 -0
- package/dist/research/workspace/project-mutations.js +332 -0
- package/dist/research/workspace/project-mutations.js.map +1 -0
- package/dist/research/workspace/projects.d.ts +2 -1
- package/dist/research/workspace/projects.js +133 -91
- package/dist/research/workspace/projects.js.map +1 -1
- package/dist/research/workspace/publication-workflow.d.ts +3 -0
- package/dist/research/workspace/publication-workflow.js +15 -3
- package/dist/research/workspace/publication-workflow.js.map +1 -1
- package/dist/research/workspace/request-provenance.d.ts +67 -0
- package/dist/research/workspace/request-provenance.js +115 -0
- package/dist/research/workspace/request-provenance.js.map +1 -0
- package/dist/research/workspace/review-executor.js +22 -11
- package/dist/research/workspace/review-executor.js.map +1 -1
- package/dist/research/workspace/runtime.d.ts +57 -0
- package/dist/research/workspace/runtime.js +292 -140
- package/dist/research/workspace/runtime.js.map +1 -1
- package/dist/research/workspace/sanitization.js +1 -1
- package/dist/research/workspace/sanitization.js.map +1 -1
- package/dist/research/workspace/schema-compatibility.d.ts +5 -0
- package/dist/research/workspace/schema-compatibility.js +55 -0
- package/dist/research/workspace/schema-compatibility.js.map +1 -0
- package/dist/research/workspace/schemas.d.ts +6 -0
- package/dist/research/workspace/schemas.js +13 -2
- package/dist/research/workspace/schemas.js.map +1 -1
- package/dist/research/workspace/scientific-design.js +8 -17
- package/dist/research/workspace/scientific-design.js.map +1 -1
- package/dist/research/workspace/scientific-fulfillment-audit.d.ts +3 -0
- package/dist/research/workspace/scientific-fulfillment-audit.js +137 -0
- package/dist/research/workspace/scientific-fulfillment-audit.js.map +1 -0
- package/dist/research/workspace/scientific-fulfillment.d.ts +121 -0
- package/dist/research/workspace/scientific-fulfillment.js +539 -0
- package/dist/research/workspace/scientific-fulfillment.js.map +1 -0
- package/dist/research/workspace/scientific-review-execution.js +84 -61
- package/dist/research/workspace/scientific-review-execution.js.map +1 -1
- package/dist/research/workspace/scientific-review.d.ts +8 -2
- package/dist/research/workspace/scientific-review.js +205 -53
- package/dist/research/workspace/scientific-review.js.map +1 -1
- package/dist/research/workspace/setup-catalog.js +2 -2
- package/dist/research/workspace/task-acceptance.d.ts +134 -0
- package/dist/research/workspace/task-acceptance.js +544 -0
- package/dist/research/workspace/task-acceptance.js.map +1 -0
- package/dist/research/workspace/task-audit.d.ts +12 -0
- package/dist/research/workspace/task-audit.js +231 -0
- package/dist/research/workspace/task-audit.js.map +1 -0
- package/dist/research/workspace/task-contract.d.ts +142 -0
- package/dist/research/workspace/task-contract.js +506 -0
- package/dist/research/workspace/task-contract.js.map +1 -0
- package/dist/research/workspace/types.d.ts +6 -3
- package/dist/research/workspace/workspace.js +11 -8
- package/dist/research/workspace/workspace.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
2
|
-
import {
|
|
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";
|
|
4
5
|
import { CliError } from "../../errors.js";
|
|
6
|
+
import { taskContext } from "./task-contract.js";
|
|
7
|
+
import { nativeRunArtifactRecords } from "./native-run.js";
|
|
8
|
+
import { loadEvidenceArtifactRecords } from "./artifacts.js";
|
|
5
9
|
import { sourceTypeRequirementGaps } from "./evidence-role-coverage.js";
|
|
6
10
|
import { appendJournalEvent, verifyJournal } from "./journal.js";
|
|
7
11
|
import { loadProject, nextScientificGate, saveProject } from "./projects.js";
|
|
12
|
+
import { projectWithEffectiveAuthority, readProjectAuthorityIndex } from "./project-authority.js";
|
|
8
13
|
import { assertResearchPolicyBinding } from "./research-policy.js";
|
|
9
|
-
import { evaluateScientificDesign,
|
|
14
|
+
import { evaluateScientificDesign, scientificDesignPolicyGaps, } from "./scientific-design.js";
|
|
10
15
|
import { resolveScientificObjectBinding } from "./scientific-objects.js";
|
|
16
|
+
import { loadScientificFulfillmentView, scientificFulfillmentLocator, } from "./scientific-fulfillment.js";
|
|
11
17
|
import { sanitizeResearchValue } from "./sanitization.js";
|
|
12
18
|
import { canonicalJson, fileRecord, isObject, pathExists, resolveContained, sha256File, sha256Text, workspacePaths, writeBytesAtomic, writeJsonAtomic, writeTextAtomic, } from "./storage.js";
|
|
13
19
|
import { loadWorkspaceConfig, verifyDoctorAttestation, withWorkspaceLock } from "./workspace.js";
|
|
@@ -263,7 +269,8 @@ export async function prepareScientificReview(input) {
|
|
|
263
269
|
exitCode: 3,
|
|
264
270
|
});
|
|
265
271
|
}
|
|
266
|
-
const
|
|
272
|
+
const fulfillment = await loadScientificFulfillmentView(input.root, project, input.role);
|
|
273
|
+
const design = fulfillment.contract;
|
|
267
274
|
const assessment = await readAssessment(input.assessmentPath, input.role);
|
|
268
275
|
if (assessment.designSha256 !== project.scientificDesign.designSha256) {
|
|
269
276
|
throw new CliError("Scientific assessment does not match the frozen design.", {
|
|
@@ -272,6 +279,68 @@ export async function prepareScientificReview(input) {
|
|
|
272
279
|
});
|
|
273
280
|
}
|
|
274
281
|
const stageInputs = await stageInputRecords(input.root, project, input.role, design, assessment, input.canaryArtifactPaths ?? []);
|
|
282
|
+
const currentTask = await taskContext(input.root, project.id);
|
|
283
|
+
if (currentTask) {
|
|
284
|
+
for (const record of await nativeRunArtifactRecords(input.root, project.id)) {
|
|
285
|
+
const locator = `projects/${project.id}/${record.path}`;
|
|
286
|
+
stageInputs.push({
|
|
287
|
+
...record,
|
|
288
|
+
path: locator,
|
|
289
|
+
sourceLocator: locator,
|
|
290
|
+
purpose: "native-calculation",
|
|
291
|
+
ownerId: project.id,
|
|
292
|
+
hashBasis: "raw-file-bytes",
|
|
293
|
+
mediaType: record.path.endsWith(".json")
|
|
294
|
+
? "application/json"
|
|
295
|
+
: "application/octet-stream",
|
|
296
|
+
objectKind: null,
|
|
297
|
+
registrationRecordSha256: null,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
const requestSource = currentTask?.requestProvenance.source;
|
|
302
|
+
if (requestSource) {
|
|
303
|
+
const locator = `projects/${project.id}/task/request-sources/${requestSource.objectSha256}.json`;
|
|
304
|
+
stageInputs.push({
|
|
305
|
+
...(await fileRecord(resolveContained(paths.control, locator), locator)),
|
|
306
|
+
purpose: "original-request-source",
|
|
307
|
+
ownerId: project.id,
|
|
308
|
+
sourceLocator: locator,
|
|
309
|
+
hashBasis: "raw-file-bytes",
|
|
310
|
+
mediaType: "application/json",
|
|
311
|
+
objectKind: null,
|
|
312
|
+
registrationRecordSha256: null,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
if (input.role !== "research-design")
|
|
316
|
+
stageInputs.push(...(await modelObjectInputs(input.root, project.id, design)));
|
|
317
|
+
if (fulfillment.records.length) {
|
|
318
|
+
for (const record of fulfillment.records) {
|
|
319
|
+
const locator = scientificFulfillmentLocator(project.id, record.recordSha256);
|
|
320
|
+
stageInputs.push({
|
|
321
|
+
...(await fileRecord(resolveContained(paths.control, locator), locator)),
|
|
322
|
+
purpose: "scientific-fulfillment",
|
|
323
|
+
ownerId: project.id,
|
|
324
|
+
sourceLocator: locator,
|
|
325
|
+
hashBasis: "raw-file-bytes",
|
|
326
|
+
mediaType: "application/json",
|
|
327
|
+
objectKind: null,
|
|
328
|
+
registrationRecordSha256: null,
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
const locator = `projects/${project.id}/scientific/design/views/${fulfillment.effectiveSha256}.json`;
|
|
332
|
+
await writeImmutableJson(resolveContained(paths.control, locator), design);
|
|
333
|
+
stageInputs.push({
|
|
334
|
+
...(await fileRecord(resolveContained(paths.control, locator), locator)),
|
|
335
|
+
purpose: "effective-scientific-design",
|
|
336
|
+
ownerId: project.id,
|
|
337
|
+
sourceLocator: locator,
|
|
338
|
+
hashBasis: "raw-file-bytes",
|
|
339
|
+
mediaType: "application/json",
|
|
340
|
+
objectKind: null,
|
|
341
|
+
registrationRecordSha256: null,
|
|
342
|
+
});
|
|
343
|
+
}
|
|
275
344
|
const assessmentSha256 = exactJsonSha256(assessment);
|
|
276
345
|
const assessmentLocator = `projects/${project.id}/scientific/assessments/${input.role}/${assessmentSha256}.json`;
|
|
277
346
|
await writeImmutableJson(join(paths.control, assessmentLocator), assessment);
|
|
@@ -287,7 +356,7 @@ export async function prepareScientificReview(input) {
|
|
|
287
356
|
};
|
|
288
357
|
await assertBoundPolicyObject(input.root, project, packetPolicy);
|
|
289
358
|
const designEvaluation = evaluateScientificDesign(design);
|
|
290
|
-
const issues = evaluateAssessment(input.role, assessment, design, project.evidenceRequirements, project.publicationPolicy, await assessmentEvidenceContext(input.root, project, input.role, stageInputs));
|
|
359
|
+
const issues = evaluateAssessment(input.role, assessment, design, project.evidenceRequirements, project.publicationPolicy, await assessmentEvidenceContext(input.root, project, input.role, stageInputs), fulfillment.deferredObjectRuleIds);
|
|
291
360
|
const futureGateObligations = scientificFutureGateObligations(design, input.role);
|
|
292
361
|
const packetCore = {
|
|
293
362
|
schemaVersion: 1,
|
|
@@ -297,11 +366,16 @@ export async function prepareScientificReview(input) {
|
|
|
297
366
|
design: {
|
|
298
367
|
sha256: project.scientificDesign.designSha256,
|
|
299
368
|
objectLocator: project.scientificDesign.objectLocator,
|
|
369
|
+
fulfillment: {
|
|
370
|
+
headSha256: fulfillment.headSha256,
|
|
371
|
+
effectiveSha256: fulfillment.effectiveSha256,
|
|
372
|
+
},
|
|
300
373
|
},
|
|
301
374
|
policy: packetPolicy,
|
|
302
375
|
reviewer: { agent: input.reviewerAgent, sessionSha256: reviewerSessionSha256 },
|
|
303
376
|
preparedAt: new Date().toISOString(),
|
|
304
377
|
stageInputs,
|
|
378
|
+
taskContract: currentTask,
|
|
305
379
|
assessment: { sha256: assessmentSha256, objectLocator: assessmentLocator },
|
|
306
380
|
mechanicalAssessment: {
|
|
307
381
|
canPass: issues.length === 0 && assessment.recommendation === "pass",
|
|
@@ -328,7 +402,10 @@ export async function prepareScientificReview(input) {
|
|
|
328
402
|
newGenerationOnMaterialChange: true,
|
|
329
403
|
revisionReserveIncluded: true,
|
|
330
404
|
},
|
|
331
|
-
instructions:
|
|
405
|
+
instructions: [
|
|
406
|
+
...reviewInstructions(input.role),
|
|
407
|
+
"Scientific fulfillment files supply only previously declared pending object slots. The base design and policy dispositions remain immutable. 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.",
|
|
408
|
+
],
|
|
332
409
|
};
|
|
333
410
|
const packetSha256 = sha256Text(canonicalJson(packetCore));
|
|
334
411
|
const packet = { ...packetCore, packetSha256 };
|
|
@@ -500,8 +577,9 @@ export { readReview as readScientificReviewOutput };
|
|
|
500
577
|
export async function assertScientificGateForStage(root, project, stage) {
|
|
501
578
|
if (!project.scientificDesign)
|
|
502
579
|
return;
|
|
503
|
-
const design = await loadBoundScientificDesign(root, project);
|
|
504
580
|
for (const role of requiredGateRoles(stage)) {
|
|
581
|
+
const fulfillment = await loadScientificFulfillmentView(root, project, role);
|
|
582
|
+
const design = fulfillment.contract;
|
|
505
583
|
const gate = project.scientificDesign.gates[role];
|
|
506
584
|
if (gate.status !== "passed" || !gate.packetSha256 || !gate.reviewSha256) {
|
|
507
585
|
throw new CliError(`Scientific ${role} review must pass before ${stage}.`, {
|
|
@@ -513,7 +591,7 @@ export async function assertScientificGateForStage(root, project, stage) {
|
|
|
513
591
|
const packet = await loadBoundPacket(root, project, role, gate.packetSha256);
|
|
514
592
|
const review = await loadBoundReview(root, project, role, gate.reviewSha256);
|
|
515
593
|
const assessment = await loadBoundAssessment(root, project, role, packet);
|
|
516
|
-
const currentIssues = evaluateAssessment(role, assessment, design, project.evidenceRequirements, project.publicationPolicy, await assessmentEvidenceContext(root, project, role, packet.stageInputs)).map((issue) => issue.code);
|
|
594
|
+
const currentIssues = evaluateAssessment(role, assessment, design, project.evidenceRequirements, project.publicationPolicy, await assessmentEvidenceContext(root, project, role, packet.stageInputs), fulfillment.deferredObjectRuleIds).map((issue) => issue.code);
|
|
517
595
|
if (packet.assessment.sha256 !== gate.assessmentSha256 ||
|
|
518
596
|
packet.reviewer.sessionSha256 !== gate.reviewerSessionSha256 ||
|
|
519
597
|
review.packetSha256 !== packet.packetSha256 ||
|
|
@@ -526,8 +604,9 @@ export async function assertScientificGateForStage(root, project, stage) {
|
|
|
526
604
|
}
|
|
527
605
|
}
|
|
528
606
|
}
|
|
529
|
-
export async function inspectScientificReviewStatus(root, projectId) {
|
|
530
|
-
const project =
|
|
607
|
+
export async function inspectScientificReviewStatus(root, projectId, knownProject) {
|
|
608
|
+
const project = knownProject ??
|
|
609
|
+
projectWithEffectiveAuthority(await loadProject(root, projectId), await readProjectAuthorityIndex(root));
|
|
531
610
|
if (!project.scientificDesign) {
|
|
532
611
|
return { projectId, reviewState: "not-required", nextGate: null, gates: null };
|
|
533
612
|
}
|
|
@@ -546,7 +625,7 @@ export async function inspectScientificReviewStatus(root, projectId) {
|
|
|
546
625
|
gates: structuredClone(project.scientificDesign.gates),
|
|
547
626
|
};
|
|
548
627
|
}
|
|
549
|
-
function evaluateAssessment(role, assessment, design, requirements, policy, evidenceContext = null) {
|
|
628
|
+
function evaluateAssessment(role, assessment, design, requirements, policy, evidenceContext = null, deferredObjectRuleIds = []) {
|
|
550
629
|
const issues = new Map();
|
|
551
630
|
const add = (code, message, objectIds = []) => {
|
|
552
631
|
if (!issues.has(code))
|
|
@@ -562,25 +641,26 @@ function evaluateAssessment(role, assessment, design, requirements, policy, evid
|
|
|
562
641
|
add(`POLICY_${gap.replaceAll(/[^A-Za-z0-9]+/gu, "_").toUpperCase()}`, "The frozen scientific design no longer matches its approved Research Policy disposition contract.", [gap]);
|
|
563
642
|
}
|
|
564
643
|
const duePolicyRules = design.policyRuleDispositions.filter((disposition) => disposition.status === "planned" &&
|
|
644
|
+
!deferredObjectRuleIds.includes(disposition.ruleId) &&
|
|
565
645
|
disposition.dueGate !== "publication-freeze" &&
|
|
566
646
|
gateRank[disposition.dueGate] <= gateRank[role]);
|
|
567
647
|
if (duePolicyRules.length) {
|
|
568
|
-
add("POLICY_RULE_DUE_UNRESOLVED", "A planned Research Policy rule reached its
|
|
648
|
+
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));
|
|
569
649
|
}
|
|
570
650
|
const dueUncertaintyFreezes = design.uncertaintyParameters.filter((parameter) => parameter.stateValueStatus === "pending-source-acquisition" &&
|
|
571
651
|
gateRank[parameter.freezeBeforeGate] <= gateRank[role]);
|
|
572
652
|
if (dueUncertaintyFreezes.length) {
|
|
573
|
-
add("UNCERTAINTY_STATE_VALUES_NOT_FROZEN", "Source-derived uncertainty values reached their
|
|
653
|
+
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));
|
|
574
654
|
}
|
|
575
655
|
const dueModelImplementations = design.identity.modelStructures.filter((model) => model.implementationStatus === "pending-source-acquisition" &&
|
|
576
656
|
gateRank[model.implementationFreezeBeforeGate] <= gateRank[role]);
|
|
577
657
|
if (dueModelImplementations.length) {
|
|
578
|
-
add("MODEL_IMPLEMENTATION_NOT_FROZEN", "A model implementation reached its
|
|
658
|
+
add("MODEL_IMPLEMENTATION_NOT_FROZEN", "A model implementation reached its freeze gate without binding the declared executable bytes.", dueModelImplementations.map((model) => model.id));
|
|
579
659
|
}
|
|
580
660
|
const dueModelEnvironmentLocks = design.identity.modelStructures.filter((model) => model.environmentLockStatus === "pending-runtime-lock" &&
|
|
581
661
|
gateRank[model.environmentLockFreezeBeforeGate] <= gateRank[role]);
|
|
582
662
|
if (dueModelEnvironmentLocks.length) {
|
|
583
|
-
add("MODEL_ENVIRONMENT_LOCK_NOT_FROZEN", "A model environment reached its
|
|
663
|
+
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));
|
|
584
664
|
}
|
|
585
665
|
if (role === "research-design") {
|
|
586
666
|
const value = assessment;
|
|
@@ -844,30 +924,7 @@ async function stageInputRecords(root, project, role, design, assessment, canary
|
|
|
844
924
|
registrationRecordSha256: null,
|
|
845
925
|
});
|
|
846
926
|
};
|
|
847
|
-
|
|
848
|
-
if (model.implementationStatus === "executable-frozen") {
|
|
849
|
-
records.push(await promoteRegisteredModelObject({
|
|
850
|
-
root,
|
|
851
|
-
projectId: project.id,
|
|
852
|
-
objectKind: "model-implementation",
|
|
853
|
-
sourceLocator: model.implementationArtifactLocator,
|
|
854
|
-
sha256: model.implementationArtifactSha256,
|
|
855
|
-
purpose: "model-implementation",
|
|
856
|
-
ownerId: model.id,
|
|
857
|
-
}));
|
|
858
|
-
}
|
|
859
|
-
if (model.environmentLockStatus === "exact-frozen") {
|
|
860
|
-
records.push(await promoteRegisteredModelObject({
|
|
861
|
-
root,
|
|
862
|
-
projectId: project.id,
|
|
863
|
-
objectKind: "environment-lock",
|
|
864
|
-
sourceLocator: model.environmentLockLocator,
|
|
865
|
-
sha256: model.environmentLockSha256,
|
|
866
|
-
purpose: "model-environment-lock",
|
|
867
|
-
ownerId: model.id,
|
|
868
|
-
}));
|
|
869
|
-
}
|
|
870
|
-
}
|
|
927
|
+
records.push(...(await modelObjectInputs(root, project.id, design)));
|
|
871
928
|
for (const gap of design.knownGaps) {
|
|
872
929
|
for (const artifact of gap.sourceArtifacts) {
|
|
873
930
|
await promoteLegacyJson({
|
|
@@ -891,8 +948,7 @@ async function stageInputRecords(root, project, role, design, assessment, canary
|
|
|
891
948
|
});
|
|
892
949
|
}
|
|
893
950
|
const relativeOutputs = ["outputs/acquisition.json", "outputs/evidence-snapshot.json"];
|
|
894
|
-
if (
|
|
895
|
-
(await pathExists(join(projectRoot, "outputs", "content-snapshot.json")))) {
|
|
951
|
+
if (await pathExists(join(projectRoot, "outputs", "content-snapshot.json"))) {
|
|
896
952
|
relativeOutputs.push("outputs/content-snapshot.json");
|
|
897
953
|
}
|
|
898
954
|
const stageOutputs = await Promise.all(relativeOutputs.map(async (relativePath) => {
|
|
@@ -908,6 +964,62 @@ async function stageInputRecords(root, project, role, design, assessment, canary
|
|
|
908
964
|
registrationRecordSha256: null,
|
|
909
965
|
};
|
|
910
966
|
}));
|
|
967
|
+
const snapshot = await readExactJson(join(projectRoot, "outputs/evidence-snapshot.json"), "Scientific evidence snapshot", "RESEARCH_SCIENTIFIC_GATE_INVALID");
|
|
968
|
+
if (!isObject(snapshot) || !Array.isArray(snapshot.artifacts))
|
|
969
|
+
throw scientificGateError("Scientific evidence snapshot has no exact artifact set.", role);
|
|
970
|
+
if (snapshot.artifacts.length) {
|
|
971
|
+
const registered = new Map((await loadEvidenceArtifactRecords(root, project.id)).map((artifact) => [
|
|
972
|
+
artifact.artifactId,
|
|
973
|
+
artifact,
|
|
974
|
+
]));
|
|
975
|
+
for (const selected of snapshot.artifacts) {
|
|
976
|
+
const artifact = isObject(selected) && typeof selected.artifactId === "string"
|
|
977
|
+
? registered.get(selected.artifactId)
|
|
978
|
+
: undefined;
|
|
979
|
+
if (!artifact ||
|
|
980
|
+
!isObject(selected) ||
|
|
981
|
+
artifact.sha256 !== selected.sha256 ||
|
|
982
|
+
artifact.bytes !== selected.bytes ||
|
|
983
|
+
artifact.locator !== selected.locator ||
|
|
984
|
+
artifact.mediaType !== selected.mediaType)
|
|
985
|
+
throw scientificGateError("Scientific review source is not the exact frozen registered artifact.", role);
|
|
986
|
+
let directory = projectRoot;
|
|
987
|
+
for (const part of ["scientific", "evidence-objects", artifact.sha256]) {
|
|
988
|
+
directory = join(directory, part);
|
|
989
|
+
await mkdir(directory, { mode: 0o700 }).catch((error) => {
|
|
990
|
+
if (error.code !== "EEXIST")
|
|
991
|
+
throw error;
|
|
992
|
+
});
|
|
993
|
+
const info = await lstat(directory);
|
|
994
|
+
if (!info.isDirectory() || info.isSymbolicLink())
|
|
995
|
+
throw scientificGateError("Scientific evidence directories cannot redirect through links.", role);
|
|
996
|
+
}
|
|
997
|
+
const path = `projects/${project.id}/scientific/evidence-objects/${artifact.sha256}/blob`;
|
|
998
|
+
const target = resolveContained(paths.control, path);
|
|
999
|
+
if (!(await pathExists(target))) {
|
|
1000
|
+
await copyFile(resolveContained(paths.control, artifact.locator), target, constants.COPYFILE_EXCL | constants.COPYFILE_FICLONE);
|
|
1001
|
+
await chmod(target, 0o444);
|
|
1002
|
+
}
|
|
1003
|
+
const info = await lstat(target);
|
|
1004
|
+
if (!info.isFile() ||
|
|
1005
|
+
info.isSymbolicLink() ||
|
|
1006
|
+
info.size !== artifact.bytes ||
|
|
1007
|
+
(await sha256File(target)) !== artifact.sha256)
|
|
1008
|
+
throw scientificGateError("Scientific review source bytes changed while staging.", role);
|
|
1009
|
+
stageOutputs.push({
|
|
1010
|
+
path,
|
|
1011
|
+
sha256: artifact.sha256,
|
|
1012
|
+
bytes: artifact.bytes,
|
|
1013
|
+
purpose: "acquired-evidence",
|
|
1014
|
+
ownerId: artifact.artifactId,
|
|
1015
|
+
sourceLocator: artifact.locator,
|
|
1016
|
+
hashBasis: "raw-file-bytes",
|
|
1017
|
+
mediaType: artifact.mediaType,
|
|
1018
|
+
objectKind: null,
|
|
1019
|
+
registrationRecordSha256: null,
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
911
1023
|
if (role !== "evidence-construct") {
|
|
912
1024
|
if (canaryArtifactPaths.length)
|
|
913
1025
|
throw canaryArtifactError("Only evidence-construct accepts canary artifacts.");
|
|
@@ -981,6 +1093,32 @@ async function stageInputRecords(root, project, role, design, assessment, canary
|
|
|
981
1093
|
}
|
|
982
1094
|
return [...stageOutputs, ...canaryRecords];
|
|
983
1095
|
}
|
|
1096
|
+
async function modelObjectInputs(root, projectId, design) {
|
|
1097
|
+
const records = [];
|
|
1098
|
+
for (const model of design.identity.modelStructures) {
|
|
1099
|
+
if (model.implementationStatus === "executable-frozen")
|
|
1100
|
+
records.push(await promoteRegisteredModelObject({
|
|
1101
|
+
root,
|
|
1102
|
+
projectId,
|
|
1103
|
+
objectKind: "model-implementation",
|
|
1104
|
+
sourceLocator: model.implementationArtifactLocator,
|
|
1105
|
+
sha256: model.implementationArtifactSha256,
|
|
1106
|
+
purpose: "model-implementation",
|
|
1107
|
+
ownerId: model.id,
|
|
1108
|
+
}));
|
|
1109
|
+
if (model.environmentLockStatus === "exact-frozen")
|
|
1110
|
+
records.push(await promoteRegisteredModelObject({
|
|
1111
|
+
root,
|
|
1112
|
+
projectId,
|
|
1113
|
+
objectKind: "environment-lock",
|
|
1114
|
+
sourceLocator: model.environmentLockLocator,
|
|
1115
|
+
sha256: model.environmentLockSha256,
|
|
1116
|
+
purpose: "model-environment-lock",
|
|
1117
|
+
ownerId: model.id,
|
|
1118
|
+
}));
|
|
1119
|
+
}
|
|
1120
|
+
return records;
|
|
1121
|
+
}
|
|
984
1122
|
async function promoteRegisteredModelObject(input) {
|
|
985
1123
|
const resolvedObject = await resolveScientificObjectBinding({
|
|
986
1124
|
root: input.root,
|
|
@@ -1116,19 +1254,7 @@ async function assessmentEvidenceContext(root, project, role, stageInputs) {
|
|
|
1116
1254
|
};
|
|
1117
1255
|
}
|
|
1118
1256
|
async function loadBoundScientificDesign(root, project) {
|
|
1119
|
-
|
|
1120
|
-
if (!binding)
|
|
1121
|
-
throw scientificGateError("Project has no scientific design binding.");
|
|
1122
|
-
const path = join(workspacePaths(root).control, binding.objectLocator);
|
|
1123
|
-
const value = await readExactJson(path, "Scientific design", "RESEARCH_SCIENTIFIC_GATE_INVALID");
|
|
1124
|
-
const design = parseScientificDesign(value);
|
|
1125
|
-
const normalized = normalizedJson(design);
|
|
1126
|
-
if (design.projectId !== project.id ||
|
|
1127
|
-
sha256Text(normalized) !== binding.designSha256 ||
|
|
1128
|
-
(await readFile(path, "utf8")) !== normalized) {
|
|
1129
|
-
throw scientificGateError("Frozen scientific design failed its exact binding.");
|
|
1130
|
-
}
|
|
1131
|
-
return design;
|
|
1257
|
+
return (await loadScientificFulfillmentView(root, project)).contract;
|
|
1132
1258
|
}
|
|
1133
1259
|
async function loadBoundPacket(root, project, role, packetSha256) {
|
|
1134
1260
|
const path = join(workspacePaths(root).projects, project.id, "scientific", "review-packets", role, `${packetSha256}.json`);
|
|
@@ -1143,6 +1269,20 @@ async function loadBoundPacket(root, project, role, packetSha256) {
|
|
|
1143
1269
|
throw scientificGateError("Scientific review packet failed its immutable hash binding.", role);
|
|
1144
1270
|
}
|
|
1145
1271
|
await assertBoundPolicyObject(root, project, value.policy);
|
|
1272
|
+
const fulfillment = await loadScientificFulfillmentView(root, project, role);
|
|
1273
|
+
if (value.design.fulfillment) {
|
|
1274
|
+
if (value.design.fulfillment.effectiveSha256 !== fulfillment.effectiveSha256 ||
|
|
1275
|
+
(value.design.fulfillment.headSha256 !== null &&
|
|
1276
|
+
!fulfillment.records.some((record) => record.recordSha256 === value.design.fulfillment.headSha256))) {
|
|
1277
|
+
throw scientificGateError("Scientific review has a stale or uncommitted fulfillment view.", role);
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
else if (canonicalJson(fulfillment.contract) !== canonicalJson(fulfillment.base)) {
|
|
1281
|
+
throw scientificGateError("Scientific review does not bind the fulfilled scientific objects.", role);
|
|
1282
|
+
}
|
|
1283
|
+
if (canonicalJson(value.taskContract ?? null) !== canonicalJson(await taskContext(root, project.id))) {
|
|
1284
|
+
throw scientificGateError("Scientific review no longer binds the current authorized task scope.", role);
|
|
1285
|
+
}
|
|
1146
1286
|
await assertBoundStageInputs(root, value.stageInputs, role);
|
|
1147
1287
|
return value;
|
|
1148
1288
|
}
|
|
@@ -1305,6 +1445,13 @@ function isScientificReviewPacket(value, projectId, role, finalPublicationReview
|
|
|
1305
1445
|
isObject(value.design) &&
|
|
1306
1446
|
typeof value.design.sha256 === "string" &&
|
|
1307
1447
|
typeof value.design.objectLocator === "string" &&
|
|
1448
|
+
(value.design.fulfillment === undefined ||
|
|
1449
|
+
(isObject(value.design.fulfillment) &&
|
|
1450
|
+
(value.design.fulfillment.headSha256 === null ||
|
|
1451
|
+
(typeof value.design.fulfillment.headSha256 === "string" &&
|
|
1452
|
+
new RegExp(SHA256_PATTERN).test(value.design.fulfillment.headSha256))) &&
|
|
1453
|
+
typeof value.design.fulfillment.effectiveSha256 === "string" &&
|
|
1454
|
+
new RegExp(SHA256_PATTERN).test(value.design.fulfillment.effectiveSha256))) &&
|
|
1308
1455
|
isObject(value.policy) &&
|
|
1309
1456
|
typeof value.policy.resolvedPolicySha256 === "string" &&
|
|
1310
1457
|
typeof value.policy.approvalSha256 === "string" &&
|
|
@@ -1330,6 +1477,11 @@ function isScientificReviewPacket(value, projectId, role, finalPublicationReview
|
|
|
1330
1477
|
"inherited-gap",
|
|
1331
1478
|
"model-implementation",
|
|
1332
1479
|
"model-environment-lock",
|
|
1480
|
+
"scientific-fulfillment",
|
|
1481
|
+
"effective-scientific-design",
|
|
1482
|
+
"original-request-source",
|
|
1483
|
+
"native-calculation",
|
|
1484
|
+
"acquired-evidence",
|
|
1333
1485
|
"stage-output",
|
|
1334
1486
|
].includes(String(record.purpose)) &&
|
|
1335
1487
|
typeof record.ownerId === "string" &&
|