@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.
Files changed (61) hide show
  1. package/AGENTS.md +2 -2
  2. package/README.md +126 -21
  3. package/dist/research/orchestration.js +101 -44
  4. package/dist/research/orchestration.js.map +1 -1
  5. package/dist/research/workspace/acquisition-forecast.d.ts +3 -0
  6. package/dist/research/workspace/acquisition-forecast.js +28 -3
  7. package/dist/research/workspace/acquisition-forecast.js.map +1 -1
  8. package/dist/research/workspace/acquisition-revision.d.ts +13 -0
  9. package/dist/research/workspace/acquisition-revision.js +142 -0
  10. package/dist/research/workspace/acquisition-revision.js.map +1 -0
  11. package/dist/research/workspace/acquisition.d.ts +3 -1
  12. package/dist/research/workspace/acquisition.js +67 -35
  13. package/dist/research/workspace/acquisition.js.map +1 -1
  14. package/dist/research/workspace/analysis-run.d.ts +2 -0
  15. package/dist/research/workspace/analysis-run.js +28 -0
  16. package/dist/research/workspace/analysis-run.js.map +1 -0
  17. package/dist/research/workspace/artifacts.d.ts +3 -0
  18. package/dist/research/workspace/artifacts.js +13 -0
  19. package/dist/research/workspace/artifacts.js.map +1 -1
  20. package/dist/research/workspace/audit-bundle.d.ts +5 -0
  21. package/dist/research/workspace/audit-bundle.js +17 -0
  22. package/dist/research/workspace/audit-bundle.js.map +1 -1
  23. package/dist/research/workspace/content-evidence.d.ts +3 -0
  24. package/dist/research/workspace/content-evidence.js +149 -22
  25. package/dist/research/workspace/content-evidence.js.map +1 -1
  26. package/dist/research/workspace/project-authority.d.ts +21 -0
  27. package/dist/research/workspace/project-authority.js +186 -0
  28. package/dist/research/workspace/project-authority.js.map +1 -0
  29. package/dist/research/workspace/project-mutations.d.ts +26 -0
  30. package/dist/research/workspace/project-mutations.js +330 -0
  31. package/dist/research/workspace/project-mutations.js.map +1 -0
  32. package/dist/research/workspace/projects.d.ts +2 -1
  33. package/dist/research/workspace/projects.js +132 -91
  34. package/dist/research/workspace/projects.js.map +1 -1
  35. package/dist/research/workspace/publication-workflow.d.ts +3 -0
  36. package/dist/research/workspace/publication-workflow.js +15 -3
  37. package/dist/research/workspace/publication-workflow.js.map +1 -1
  38. package/dist/research/workspace/runtime.d.ts +5 -0
  39. package/dist/research/workspace/runtime.js +83 -54
  40. package/dist/research/workspace/runtime.js.map +1 -1
  41. package/dist/research/workspace/schemas.d.ts +6 -0
  42. package/dist/research/workspace/schemas.js +13 -2
  43. package/dist/research/workspace/schemas.js.map +1 -1
  44. package/dist/research/workspace/scientific-review-execution.js +2 -0
  45. package/dist/research/workspace/scientific-review-execution.js.map +1 -1
  46. package/dist/research/workspace/scientific-review.d.ts +3 -1
  47. package/dist/research/workspace/scientific-review.js +9 -2
  48. package/dist/research/workspace/scientific-review.js.map +1 -1
  49. package/dist/research/workspace/setup-catalog.js +2 -2
  50. package/dist/research/workspace/task-acceptance.d.ts +126 -0
  51. package/dist/research/workspace/task-acceptance.js +480 -0
  52. package/dist/research/workspace/task-acceptance.js.map +1 -0
  53. package/dist/research/workspace/task-audit.d.ts +12 -0
  54. package/dist/research/workspace/task-audit.js +181 -0
  55. package/dist/research/workspace/task-audit.js.map +1 -0
  56. package/dist/research/workspace/task-contract.d.ts +139 -0
  57. package/dist/research/workspace/task-contract.js +485 -0
  58. package/dist/research/workspace/task-contract.js.map +1 -0
  59. package/dist/research/workspace/workspace.js +11 -8
  60. package/dist/research/workspace/workspace.js.map +1 -1
  61. 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 verifyJournal(workspacePaths(root).journal);
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 verifyJournal(workspacePaths(input.root).journal);
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 capsule = await createCapsule(root, project, workPackage, runId, config);
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, input.inputOnlyProvenance
2299
- ? { inputOnlyProvenanceIds: input.capsule.inputManifest.map((record) => record.id) }
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.mode === "qualitative") {
2518
- if (run.status !== "not-applicable" ||
2519
- run.command !== null ||
2520
- run.randomSeed !== null ||
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
- const projects = await projectsForRun(root, projectId);
3534
- const summaries = projects.map((project) => projectRunSummary(root, project));
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
- if (project.lineage.supersededBy ||
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)).filter((project) => project.lineage.supersededBy === null &&
3605
- project.status !== "archived" &&
3606
- project.status !== "abandoned");
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,