@tiangong-ai/cli 0.0.59 → 0.0.60
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 +126 -21
- package/dist/research/orchestration.js +101 -44
- 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.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/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 +5 -0
- package/dist/research/workspace/audit-bundle.js +17 -0
- 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/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 +330 -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 +132 -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/runtime.d.ts +5 -0
- package/dist/research/workspace/runtime.js +83 -54
- package/dist/research/workspace/runtime.js.map +1 -1
- 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-review-execution.js +2 -0
- package/dist/research/workspace/scientific-review-execution.js.map +1 -1
- package/dist/research/workspace/scientific-review.d.ts +3 -1
- package/dist/research/workspace/scientific-review.js +9 -2
- 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 +126 -0
- package/dist/research/workspace/task-acceptance.js +480 -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 +181 -0
- package/dist/research/workspace/task-audit.js.map +1 -0
- package/dist/research/workspace/task-contract.d.ts +139 -0
- package/dist/research/workspace/task-contract.js +485 -0
- package/dist/research/workspace/task-contract.js.map +1 -0
- package/dist/research/workspace/workspace.js +11 -8
- package/dist/research/workspace/workspace.js.map +1 -1
- package/package.json +1 -1
|
@@ -2,6 +2,10 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { cp, lstat, readFile, rm } from "node:fs/promises";
|
|
3
3
|
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
4
4
|
import { CliError } from "../../errors.js";
|
|
5
|
+
import { isConsistentAnalysisRunMetadata } from "./analysis-run.js";
|
|
6
|
+
import { taskContext } from "./task-contract.js";
|
|
7
|
+
import { compileTaskAcceptanceContext, inspectProjectTask, taskAcceptancePrompt, validateTaskReview, } from "./task-acceptance.js";
|
|
8
|
+
import { assertProjectAuthority, projectAuthority, projectWithEffectiveAuthority, readProjectAuthorityIndex, } from "./project-authority.js";
|
|
5
9
|
import { dataPublicSchemas } from "../../data/schemas.js";
|
|
6
10
|
import { loadCapabilityDeclarations, stageLockedCapabilities, verifyCapabilities, } from "./capabilities.js";
|
|
7
11
|
import { startCapabilityBroker } from "./broker.js";
|
|
@@ -177,13 +181,13 @@ async function runResearchWorkspaceInternal(root, options, packageExecutor, allo
|
|
|
177
181
|
if (options.dryRun)
|
|
178
182
|
return dryRunResult(root, requestId, options.projectId);
|
|
179
183
|
return withWorkspaceLock(root, "research.run", async () => {
|
|
180
|
-
await
|
|
184
|
+
const authority = await readProjectAuthorityIndex(root);
|
|
181
185
|
const config = await loadWorkspaceConfig(root);
|
|
182
186
|
assertExecutionConfiguration(config);
|
|
183
187
|
const reviewerPackageExecutor = useConfiguredReviewerExecutor
|
|
184
188
|
? createReviewExecutor({ root, execution: config.reviewerExecution }).execute
|
|
185
189
|
: null;
|
|
186
|
-
for (const project of await projectsForRun(root, options.projectId)) {
|
|
190
|
+
for (const project of await projectsForRun(root, options.projectId, authority)) {
|
|
187
191
|
await assertProjectPublicationPolicy(root, project);
|
|
188
192
|
}
|
|
189
193
|
let doctorAttestation = null;
|
|
@@ -205,7 +209,7 @@ async function runResearchWorkspaceInternal(root, options, packageExecutor, allo
|
|
|
205
209
|
});
|
|
206
210
|
}
|
|
207
211
|
doctorAttestation = verification.attestation;
|
|
208
|
-
const unconfirmed = (await projectsForRun(root, options.projectId)).filter((project) => config.budget.maxCostUsd > config.budget.confirmationCostUsd &&
|
|
212
|
+
const unconfirmed = (await projectsForRun(root, options.projectId, authority)).filter((project) => config.budget.maxCostUsd > config.budget.confirmationCostUsd &&
|
|
209
213
|
!project.budgetConfirmedAt);
|
|
210
214
|
if (unconfirmed.length) {
|
|
211
215
|
throw new CliError("Production research budget has not been explicitly confirmed.", {
|
|
@@ -219,7 +223,7 @@ async function runResearchWorkspaceInternal(root, options, packageExecutor, allo
|
|
|
219
223
|
const executed = [];
|
|
220
224
|
let cycles = 0;
|
|
221
225
|
while (cycles < options.maxCycles) {
|
|
222
|
-
const projects = await projectsForRun(root, options.projectId);
|
|
226
|
+
const projects = await projectsForRun(root, options.projectId, authority);
|
|
223
227
|
const selected = projects
|
|
224
228
|
.map((project) => ({ project, workPackage: nextReadyPackage(project) }))
|
|
225
229
|
.filter((item) => Boolean(item.workPackage) &&
|
|
@@ -233,7 +237,7 @@ async function runResearchWorkspaceInternal(root, options, packageExecutor, allo
|
|
|
233
237
|
const results = await Promise.all(selected.map(({ project, workPackage }) => executeWorkPackage(root, project.id, workPackage.id, config, options, requestId, packageExecutor, reviewerPackageExecutor, doctorAttestation)));
|
|
234
238
|
executed.push(...results);
|
|
235
239
|
}
|
|
236
|
-
const result = await summarizeRun(root, requestId, cycles, executed, options.maxCycles, options.projectId);
|
|
240
|
+
const result = await summarizeRun(root, requestId, cycles, executed, options.maxCycles, options.projectId, authority);
|
|
237
241
|
emitProgress(options, progressEvent("run.completed", requestId, options.projectId ?? null, null, null, {
|
|
238
242
|
status: result.status,
|
|
239
243
|
stopReason: result.stopReason,
|
|
@@ -306,10 +310,11 @@ export async function inspectNativeResearchStage(root, project) {
|
|
|
306
310
|
}
|
|
307
311
|
export async function prepareNativeResearchStage(input) {
|
|
308
312
|
return withWorkspaceLock(input.root, "research.native-stage.prepare", async () => {
|
|
309
|
-
await
|
|
313
|
+
const authority = await readProjectAuthorityIndex(input.root);
|
|
310
314
|
const config = await loadWorkspaceConfig(input.root);
|
|
311
315
|
assertExecutionConfiguration(config);
|
|
312
|
-
const project = await loadProject(input.root, input.projectId);
|
|
316
|
+
const project = projectWithEffectiveAuthority(await loadProject(input.root, input.projectId), authority);
|
|
317
|
+
assertProjectAuthority(project, authority);
|
|
313
318
|
await assertScientificGateForStage(input.root, project, input.stage);
|
|
314
319
|
await assertProjectPublicationPolicy(input.root, project);
|
|
315
320
|
if (config.producer.agent !== input.hostAgent) {
|
|
@@ -397,9 +402,22 @@ export async function prepareNativeResearchStage(input) {
|
|
|
397
402
|
const dataCapabilities = projectResearchDataCapabilities();
|
|
398
403
|
const hasDataEvidence = dataCapabilities.capabilities.length > 0;
|
|
399
404
|
const basePrompt = packagePrompt(project, workPackage, capsule.inputManifest, capsule.stagedSkills, capsule.capabilityDocumentation, null, capsule.contextBundle, capsule.contextBundleContent, stageContextContent, discovery, await listEvidenceCandidates(input.root, project.id));
|
|
405
|
+
const taskContract = await taskContext(input.root, project.id, authority.taskEvents.get(project.id) ?? []);
|
|
406
|
+
const taskPrompt = taskContract
|
|
407
|
+
? "Original task and current authorized scope (a workflow finish is not task completion):\n" +
|
|
408
|
+
JSON.stringify(taskContract)
|
|
409
|
+
: "";
|
|
410
|
+
if (taskContract &&
|
|
411
|
+
Buffer.byteLength(taskPrompt +
|
|
412
|
+
(input.stage === "discover" ? capsule.contextBundleContent : "") +
|
|
413
|
+
stageContextContent, "utf8") >
|
|
414
|
+
config.budget.maxInputContextTokens * RESEARCH_ESTIMATED_BYTES_PER_TOKEN) {
|
|
415
|
+
throw new CliError("The complete task and evidence context exceeds the reviewed input allowance. Condense representation without dropping requirements or explicitly review a larger context budget.", { code: "RESEARCH_TASK_CONTEXT_TOO_LARGE", exitCode: 3 });
|
|
416
|
+
}
|
|
400
417
|
const prompt = [
|
|
401
418
|
"Perform this producer stage in the current interactive host session. Do not launch codex exec, claude -p, or any other nested reasoning agent.",
|
|
402
419
|
capsule.publicationPolicyDocumentation,
|
|
420
|
+
taskPrompt,
|
|
403
421
|
input.stage === "discover" && (hasBrokeredEvidence || hasDataEvidence)
|
|
404
422
|
? [
|
|
405
423
|
hasBrokeredEvidence
|
|
@@ -436,6 +454,7 @@ export async function prepareNativeResearchStage(input) {
|
|
|
436
454
|
? { inputOnlyProvenanceIds: capsule.inputManifest.map((record) => record.id) }
|
|
437
455
|
: {}),
|
|
438
456
|
publicationPolicy: capsule.publicationPolicy,
|
|
457
|
+
taskContract,
|
|
439
458
|
discovery,
|
|
440
459
|
limits: {
|
|
441
460
|
maxOutputBytes: config.budget.maxBytesPerPackage,
|
|
@@ -792,6 +811,8 @@ export function nativeEvidenceRequestSchema(requireAcquisitionRoute) {
|
|
|
792
811
|
}
|
|
793
812
|
export async function submitNativeResearchStage(input) {
|
|
794
813
|
return withWorkspaceLock(input.root, "research.native-stage.submit", async () => {
|
|
814
|
+
const authority = await readProjectAuthorityIndex(input.root);
|
|
815
|
+
assertProjectAuthority(await loadProject(input.root, input.projectId), authority);
|
|
795
816
|
const session = await readNativeStageSession(input.root, input.projectId);
|
|
796
817
|
if (session.packet.sessionId !== input.sessionId) {
|
|
797
818
|
throw new CliError("Native stage session ID does not match the active session.", {
|
|
@@ -1264,7 +1285,11 @@ async function executeWorkPackage(root, projectId, packageId, config, options, r
|
|
|
1264
1285
|
: null;
|
|
1265
1286
|
const stageOutputTokens = discovery?.plan.outputTokenLimit ?? config.budget.maxOutputTokens;
|
|
1266
1287
|
const reservation = reservePackageBudget(project, workPackage, config, discovery?.plan.reservedDiscoverTokens);
|
|
1267
|
-
const
|
|
1288
|
+
const taskAcceptance = workPackage.stage === "review" ? await compileTaskAcceptanceContext(root, project) : null;
|
|
1289
|
+
if (taskAcceptance?.requirements.some((row) => row.current && row.status === "unanswered")) {
|
|
1290
|
+
throw new CliError("Record an actual check or an honest not-run/inconclusive disposition for each current requirement before spending review budget.", { code: "RESEARCH_TASK_ACCEPTANCE_REQUIRED", exitCode: 3 });
|
|
1291
|
+
}
|
|
1292
|
+
const capsule = await createCapsule(root, project, workPackage, runId, config, taskAcceptance);
|
|
1268
1293
|
capsuleRoot = capsule.capsuleRoot;
|
|
1269
1294
|
capsuleDisposition = capsuleDispositionForHost(config.producer.agent);
|
|
1270
1295
|
retainedCapsuleId =
|
|
@@ -1303,7 +1328,8 @@ async function executeWorkPackage(root, projectId, packageId, config, options, r
|
|
|
1303
1328
|
prompt: packagePrompt(project, workPackage, capsule.inputManifest, capsule.stagedSkills, capsule.capabilityDocumentation, capsule.reviewPacketSha256, capsule.contextBundle, capsule.contextBundleContent, stageContextContent, discovery, await listEvidenceCandidates(root, project.id)) +
|
|
1304
1329
|
(capsule.publicationPolicyDocumentation
|
|
1305
1330
|
? `\n\n${capsule.publicationPolicyDocumentation}`
|
|
1306
|
-
: "")
|
|
1331
|
+
: "") +
|
|
1332
|
+
(capsule.taskAcceptancePrompt ? `\n\n${capsule.taskAcceptancePrompt}` : ""),
|
|
1307
1333
|
brokerUrl: primaryBrokerUrl,
|
|
1308
1334
|
inputOnlyProvenance,
|
|
1309
1335
|
maxOutputTokens: Math.min(stageOutputTokens, reservation.tokens),
|
|
@@ -1557,7 +1583,7 @@ async function stagePublicationPolicy(root, project, capsuleProject) {
|
|
|
1557
1583
|
await writeJsonAtomic(staged.manifestPath, staged);
|
|
1558
1584
|
return staged;
|
|
1559
1585
|
}
|
|
1560
|
-
async function createCapsule(root, project, workPackage, runId, config) {
|
|
1586
|
+
async function createCapsule(root, project, workPackage, runId, config, taskAcceptance = null) {
|
|
1561
1587
|
const paths = workspacePaths(root);
|
|
1562
1588
|
const capsuleRoot = join(paths.runtime, runId);
|
|
1563
1589
|
const capsuleProject = join(capsuleRoot, "project");
|
|
@@ -1673,7 +1699,7 @@ async function createCapsule(root, project, workPackage, runId, config) {
|
|
|
1673
1699
|
? await writeReviewEvidenceContext(root, project.id, capsuleProject, contextBundleContent, evidenceReceipts, contextualEvidenceArtifacts, config.budget.maxInputContextTokens * RESEARCH_ESTIMATED_BYTES_PER_TOKEN)
|
|
1674
1700
|
: null;
|
|
1675
1701
|
const reviewPacket = reviewEvidenceContext
|
|
1676
|
-
? await writeReviewPacket(root, capsuleProject, project, inputManifest, evidenceReceipts, evidenceArtifacts, reviewEvidenceContext.persistent)
|
|
1702
|
+
? await writeReviewPacket(root, capsuleProject, project, inputManifest, evidenceReceipts, evidenceArtifacts, reviewEvidenceContext.persistent, taskAcceptance)
|
|
1677
1703
|
: null;
|
|
1678
1704
|
return {
|
|
1679
1705
|
capsuleRoot,
|
|
@@ -1687,6 +1713,8 @@ async function createCapsule(root, project, workPackage, runId, config) {
|
|
|
1687
1713
|
publicationPolicyDocumentation,
|
|
1688
1714
|
reviewPacketSha256: reviewPacket?.sha256 ?? null,
|
|
1689
1715
|
reviewPacketRecord: reviewPacket?.record ?? null,
|
|
1716
|
+
taskAcceptance,
|
|
1717
|
+
taskAcceptancePrompt: await taskAcceptancePrompt(root, taskAcceptance),
|
|
1690
1718
|
};
|
|
1691
1719
|
}
|
|
1692
1720
|
async function buildCapabilityDocumentation(capsuleProject, stagedSkills, maxBytes) {
|
|
@@ -1733,7 +1761,7 @@ async function buildInputContextBundle(capsuleProject, inputManifest) {
|
|
|
1733
1761
|
}
|
|
1734
1762
|
return `${sections.join("\n\n")}\n`;
|
|
1735
1763
|
}
|
|
1736
|
-
async function writeReviewPacket(root, capsuleProject, project, inputManifest, evidenceReceipts, evidenceArtifacts, reviewEvidenceContext) {
|
|
1764
|
+
async function writeReviewPacket(root, capsuleProject, project, inputManifest, evidenceReceipts, evidenceArtifacts, reviewEvidenceContext, taskAcceptance) {
|
|
1737
1765
|
const snapshot = await loadCurrentEvidenceSnapshot(root, project.id);
|
|
1738
1766
|
const immutableSnapshots = await loadImmutableEvidenceSnapshotChain(root, project.id, snapshot.snapshotSha256);
|
|
1739
1767
|
const snapshotChain = await Promise.all(immutableSnapshots.map((immutableSnapshot) => {
|
|
@@ -1790,6 +1818,7 @@ async function writeReviewPacket(root, capsuleProject, project, inputManifest, e
|
|
|
1790
1818
|
parentSnapshotSha256: snapshot.parentSnapshotSha256,
|
|
1791
1819
|
},
|
|
1792
1820
|
snapshotChain,
|
|
1821
|
+
taskAcceptance,
|
|
1793
1822
|
inputs: inputManifest,
|
|
1794
1823
|
reviewEvidenceContext,
|
|
1795
1824
|
inputFiles: [...inputFiles.values()].sort((left, right) => left.path.localeCompare(right.path)),
|
|
@@ -2130,7 +2159,7 @@ async function persistReviewPacket(root, projectId, packet, packetSha256) {
|
|
|
2130
2159
|
}
|
|
2131
2160
|
return fileRecord(path, logicalPath);
|
|
2132
2161
|
}
|
|
2133
|
-
async function loadVerifiedReviewPacket(root, projectId, packetSha256) {
|
|
2162
|
+
export async function loadVerifiedReviewPacket(root, projectId, packetSha256) {
|
|
2134
2163
|
const logicalPath = `review/packets/${packetSha256}.json`;
|
|
2135
2164
|
const path = resolveContained(projectRoot(root, projectId), logicalPath);
|
|
2136
2165
|
const packet = await readJsonFile(path, "Research review packet");
|
|
@@ -2295,9 +2324,19 @@ function agentRequest(input) {
|
|
|
2295
2324
|
return {
|
|
2296
2325
|
route: input.route,
|
|
2297
2326
|
prompt: input.prompt,
|
|
2298
|
-
outputSchema: schemaForStage(input.workPackage.stage, input.capsule.reviewPacketSha256,
|
|
2299
|
-
|
|
2300
|
-
|
|
2327
|
+
outputSchema: schemaForStage(input.workPackage.stage, input.capsule.reviewPacketSha256, {
|
|
2328
|
+
...(input.inputOnlyProvenance
|
|
2329
|
+
? { inputOnlyProvenanceIds: input.capsule.inputManifest.map((record) => record.id) }
|
|
2330
|
+
: {}),
|
|
2331
|
+
...(input.capsule.taskAcceptance
|
|
2332
|
+
? {
|
|
2333
|
+
taskAcceptance: {
|
|
2334
|
+
contextSha256: input.capsule.taskAcceptance.contextSha256,
|
|
2335
|
+
requirementSha256s: input.capsule.taskAcceptance.requirements.map((row) => row.requirementSha256),
|
|
2336
|
+
},
|
|
2337
|
+
}
|
|
2338
|
+
: {}),
|
|
2339
|
+
}),
|
|
2301
2340
|
requestId: input.requestId,
|
|
2302
2341
|
purpose: input.purpose,
|
|
2303
2342
|
capsuleRoot: input.capsule.capsuleRoot,
|
|
@@ -2400,6 +2439,7 @@ async function validateOutputShape(root, project, workPackage, path, reviewPacke
|
|
|
2400
2439
|
await validateAnalysis(path, value);
|
|
2401
2440
|
}
|
|
2402
2441
|
if (workPackage.stage === "review") {
|
|
2442
|
+
validateTaskReview(value, await compileTaskAcceptanceContext(root, project));
|
|
2403
2443
|
if (value.decision !== "pass") {
|
|
2404
2444
|
throw new CliError("Independent review requested revision.", {
|
|
2405
2445
|
code: "RESEARCH_REVIEW_REVISION_REQUIRED",
|
|
@@ -2514,23 +2554,10 @@ async function validateAnalysis(path, value) {
|
|
|
2514
2554
|
inputArtifactSha256s.some((sha256) => !allowedInputSha256s.has(sha256))) {
|
|
2515
2555
|
throw new StructuredOutputError("Analysis run refers to an artifact outside the inference snapshot.");
|
|
2516
2556
|
}
|
|
2517
|
-
if (run
|
|
2518
|
-
|
|
2519
|
-
run
|
|
2520
|
-
|
|
2521
|
-
implementationSha256s.length > 0 ||
|
|
2522
|
-
environmentSha256s.length > 0) {
|
|
2523
|
-
throw new StructuredOutputError("Qualitative analysis run metadata is inconsistent.");
|
|
2524
|
-
}
|
|
2525
|
-
}
|
|
2526
|
-
else if (run.status !== "reproduced" ||
|
|
2527
|
-
typeof run.command !== "string" ||
|
|
2528
|
-
run.command.trim().length < 1 ||
|
|
2529
|
-
typeof run.randomSeed !== "string" ||
|
|
2530
|
-
run.randomSeed.trim().length < 1 ||
|
|
2531
|
-
implementationSha256s.length < 1 ||
|
|
2532
|
-
environmentSha256s.length < 1) {
|
|
2533
|
-
throw new StructuredOutputError("Computational analysis requires exact reproduced run metadata.");
|
|
2557
|
+
if (!isConsistentAnalysisRunMetadata(run)) {
|
|
2558
|
+
throw new StructuredOutputError(run.mode === "qualitative"
|
|
2559
|
+
? "Qualitative analysis run metadata is inconsistent."
|
|
2560
|
+
: "Computational analysis requires exact reproduced run metadata.");
|
|
2534
2561
|
}
|
|
2535
2562
|
const findings = value.findings;
|
|
2536
2563
|
const findingIds = new Set();
|
|
@@ -2872,6 +2899,7 @@ async function closeProjectMechanically(root, project, workPackage) {
|
|
|
2872
2899
|
throw deterministicError("Project review does not bind a valid review packet hash.");
|
|
2873
2900
|
}
|
|
2874
2901
|
const reviewPacket = await loadVerifiedReviewPacket(root, project.id, review.packetSha256);
|
|
2902
|
+
validateTaskReview(review, await compileTaskAcceptanceContext(root, project));
|
|
2875
2903
|
await verifyReviewLedgerBinding(root, project.id, snapshot.snapshotId, review.packetSha256);
|
|
2876
2904
|
const evidenceReceipts = await loadProjectEvidenceReceipts(root, project.id);
|
|
2877
2905
|
const journal = await verifyJournal(workspacePaths(root).journal);
|
|
@@ -3529,9 +3557,20 @@ function projectRunSummary(root, project) {
|
|
|
3529
3557
|
usage: project.usage,
|
|
3530
3558
|
};
|
|
3531
3559
|
}
|
|
3532
|
-
async function summarizeRun(root, requestId, cycles, executed, maxCycles, projectId) {
|
|
3533
|
-
|
|
3534
|
-
const
|
|
3560
|
+
async function summarizeRun(root, requestId, cycles, executed, maxCycles, projectId, authority) {
|
|
3561
|
+
// One fresh journal view after execution includes newly committed reviewer results.
|
|
3562
|
+
const summaryAuthority = executed.length
|
|
3563
|
+
? await readProjectAuthorityIndex(root)
|
|
3564
|
+
: (authority ?? (await readProjectAuthorityIndex(root)));
|
|
3565
|
+
const projects = await projectsForRun(root, projectId, summaryAuthority);
|
|
3566
|
+
const summaries = await Promise.all(projects.map(async (project) => {
|
|
3567
|
+
const summary = projectRunSummary(root, project);
|
|
3568
|
+
const events = summaryAuthority.taskEvents.get(project.id);
|
|
3569
|
+
if (!events)
|
|
3570
|
+
return summary;
|
|
3571
|
+
const task = await inspectProjectTask(root, project.id, events);
|
|
3572
|
+
return task.status === "configured" ? { ...summary, task } : summary;
|
|
3573
|
+
}));
|
|
3535
3574
|
const unfinished = summaries.filter((project) => project.status !== "complete");
|
|
3536
3575
|
const waiting = unfinished.filter((project) => project.status === "waiting-user" || project.status === "waiting-external");
|
|
3537
3576
|
const hasReadyPackage = summaries.some((project) => project.readyPackage !== null);
|
|
@@ -3583,27 +3622,16 @@ async function summarizeRun(root, requestId, cycles, executed, maxCycles, projec
|
|
|
3583
3622
|
projects: summaries,
|
|
3584
3623
|
};
|
|
3585
3624
|
}
|
|
3586
|
-
async function projectsForRun(root, projectId) {
|
|
3625
|
+
async function projectsForRun(root, projectId, knownAuthority) {
|
|
3626
|
+
const authority = knownAuthority ?? (await readProjectAuthorityIndex(root));
|
|
3587
3627
|
if (projectId) {
|
|
3588
|
-
const project = await loadProject(root, projectId);
|
|
3589
|
-
|
|
3590
|
-
project.status === "archived" ||
|
|
3591
|
-
project.status === "abandoned") {
|
|
3592
|
-
throw new CliError(`Research project ${projectId} is historical and cannot be executed.`, {
|
|
3593
|
-
code: "RESEARCH_PROJECT_NOT_AUTHORITATIVE",
|
|
3594
|
-
exitCode: 3,
|
|
3595
|
-
details: {
|
|
3596
|
-
projectId,
|
|
3597
|
-
status: project.status,
|
|
3598
|
-
authoritativeProjectId: project.lineage.supersededBy ?? null,
|
|
3599
|
-
},
|
|
3600
|
-
});
|
|
3601
|
-
}
|
|
3628
|
+
const project = projectWithEffectiveAuthority(await loadProject(root, projectId), authority);
|
|
3629
|
+
assertProjectAuthority(project, authority);
|
|
3602
3630
|
return [project];
|
|
3603
3631
|
}
|
|
3604
|
-
return (await listProjects(root))
|
|
3605
|
-
project.
|
|
3606
|
-
project
|
|
3632
|
+
return (await listProjects(root, authority))
|
|
3633
|
+
.filter((project) => projectAuthority(project, authority).state === "authoritative")
|
|
3634
|
+
.map((project) => projectWithEffectiveAuthority(project, authority));
|
|
3607
3635
|
}
|
|
3608
3636
|
function assertPublicEvidenceUrl(value, sourceId) {
|
|
3609
3637
|
let url;
|
|
@@ -3704,6 +3732,7 @@ async function nativeStageBinding(root, project, workPackage) {
|
|
|
3704
3732
|
return sha256Text(canonicalJson({
|
|
3705
3733
|
projectId: project.id,
|
|
3706
3734
|
questionSha256: sha256Text(project.question),
|
|
3735
|
+
taskContractSha256: (await taskContext(root, project.id))?.contractSha256 ?? null,
|
|
3707
3736
|
evidenceRequirements: project.evidenceRequirements,
|
|
3708
3737
|
inputs: project.inputs.map((record) => ({
|
|
3709
3738
|
id: record.id,
|