@fitlab-ai/agent-infra 0.11.3 → 0.11.4

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 (102) hide show
  1. package/README.md +17 -8
  2. package/README.zh-CN.md +17 -8
  3. package/dist/bin/internal-cli.js +1 -1
  4. package/dist/lib/internal/cli-route-inventory.js +1 -1
  5. package/dist/lib/internal/task-artifact.js +1 -1
  6. package/dist/lib/internal/task-finalization.js +41 -1
  7. package/dist/lib/internal/task-ledger.js +13 -5
  8. package/dist/lib/internal/task-operation-registry.js +1 -0
  9. package/dist/lib/platform/completion-backfill.js +63 -17
  10. package/dist/lib/sandbox/control/server.js +26 -143
  11. package/dist/lib/sandbox/task-cleanup.js +13 -20
  12. package/dist/lib/task/artifact-command.js +11 -4
  13. package/dist/lib/task/artifact-lifecycle.js +217 -85
  14. package/dist/lib/task/artifact-operations.js +1 -26
  15. package/dist/lib/task/artifact-receipts.js +4 -1
  16. package/dist/lib/task/artifact-schema.js +1 -0
  17. package/dist/lib/task/artifact-selection.js +25 -0
  18. package/dist/lib/task/capabilities.js +158 -39
  19. package/dist/lib/task/commands/status.js +9 -2
  20. package/dist/lib/task/control-authority.js +3 -2
  21. package/dist/lib/task/decision-intents.js +12 -0
  22. package/dist/lib/task/delivery.js +1 -43
  23. package/dist/lib/task/events.js +80 -64
  24. package/dist/lib/task/finalization.js +301 -143
  25. package/dist/lib/task/ledger-intents.js +157 -15
  26. package/dist/lib/task/lifecycle-path.js +75 -0
  27. package/dist/lib/task/lifecycle.js +10 -0
  28. package/dist/lib/task/local-artifact-finalization.js +1 -20
  29. package/dist/lib/task/orchestration.js +17 -7
  30. package/dist/lib/task/review-finalization.js +3 -16
  31. package/dist/lib/task/rework-intent.js +70 -4
  32. package/dist/lib/task/verification-engine.js +20 -30
  33. package/dist/lib/task/verification.js +1 -0
  34. package/dist/lib/task/workflow-warning-intents.js +21 -6
  35. package/dist/lib/task/workspace-snapshot.js +7 -2
  36. package/dist/lifecycle-build-manifest.json +3 -3
  37. package/lib/internal/cli-route-inventory.ts +1 -1
  38. package/lib/internal/task-artifact.ts +1 -1
  39. package/lib/internal/task-finalization.ts +40 -1
  40. package/lib/internal/task-ledger.ts +12 -5
  41. package/lib/internal/task-operation-registry.ts +1 -0
  42. package/lib/platform/completion-backfill.ts +78 -16
  43. package/lib/sandbox/control/server.ts +21 -146
  44. package/lib/sandbox/task-cleanup.ts +12 -20
  45. package/lib/task/artifact-command.ts +14 -4
  46. package/lib/task/artifact-lifecycle.ts +236 -85
  47. package/lib/task/artifact-operations.ts +1 -26
  48. package/lib/task/artifact-receipts.ts +4 -1
  49. package/lib/task/artifact-schema.ts +1 -0
  50. package/lib/task/artifact-selection.ts +53 -0
  51. package/lib/task/capabilities.ts +154 -40
  52. package/lib/task/commands/status.ts +11 -2
  53. package/lib/task/control-authority.ts +3 -1
  54. package/lib/task/decision-intents.ts +9 -0
  55. package/lib/task/delivery.ts +1 -40
  56. package/lib/task/events.ts +82 -64
  57. package/lib/task/finalization.ts +269 -111
  58. package/lib/task/ledger-intents.ts +155 -15
  59. package/lib/task/lifecycle-path.ts +98 -0
  60. package/lib/task/lifecycle.ts +13 -0
  61. package/lib/task/local-artifact-finalization.ts +2 -25
  62. package/lib/task/orchestration.ts +26 -8
  63. package/lib/task/review-finalization.ts +3 -16
  64. package/lib/task/rework-intent.ts +75 -5
  65. package/lib/task/verification-engine.ts +19 -30
  66. package/lib/task/verification.ts +2 -1
  67. package/lib/task/workflow-warning-intents.ts +18 -6
  68. package/lib/task/workspace-snapshot.ts +3 -2
  69. package/package.json +1 -1
  70. package/templates/.agents/rules/review-handshake.en.md +4 -4
  71. package/templates/.agents/rules/review-handshake.zh-CN.md +4 -4
  72. package/templates/.agents/skills/analyze-task/SKILL.en.md +11 -4
  73. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +11 -4
  74. package/templates/.agents/skills/code-task/SKILL.en.md +14 -8
  75. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +18 -13
  76. package/templates/.agents/skills/code-task/reference/code-rules.en.md +2 -2
  77. package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +2 -2
  78. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +4 -4
  79. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +4 -4
  80. package/templates/.agents/skills/code-task/reference/report-template.en.md +1 -1
  81. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +1 -1
  82. package/templates/.agents/skills/complete-task/SKILL.en.md +2 -2
  83. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -2
  84. package/templates/.agents/skills/plan-task/SKILL.en.md +13 -10
  85. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +13 -10
  86. package/templates/.agents/skills/review-analysis/SKILL.en.md +5 -1
  87. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +5 -1
  88. package/templates/.agents/skills/review-code/SKILL.en.md +5 -1
  89. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -1
  90. package/templates/.agents/skills/review-plan/SKILL.en.md +5 -1
  91. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +5 -1
  92. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +18 -2
  93. package/templates/.agents/templates/task.en.md +2 -2
  94. package/templates/.agents/templates/task.zh-CN.md +2 -2
  95. package/templates/.agents/workflows/bug-fix.en.yaml +11 -6
  96. package/templates/.agents/workflows/bug-fix.zh-CN.yaml +11 -6
  97. package/templates/.agents/workflows/feature-development.en.yaml +11 -6
  98. package/templates/.agents/workflows/feature-development.zh-CN.yaml +11 -6
  99. package/templates/.agents/workflows/refactoring.en.yaml +11 -6
  100. package/templates/.agents/workflows/refactoring.zh-CN.yaml +11 -6
  101. package/dist/lib/sandbox/control/finalization-response.js +0 -33
  102. package/lib/sandbox/control/finalization-response.ts +0 -35
@@ -1,4 +1,3 @@
1
- import { finalizationTerminalResponse } from './finalization-response.ts';
2
1
  import { completedReentryView } from './completed-reentry.ts';
3
2
  import fs from 'node:fs';
4
3
  import path from 'node:path';
@@ -77,7 +76,6 @@ import { readCodexControllerRegistration } from './controller-registration.ts';
77
76
  import { validateSandboxControlIdentity } from './identity-sentinel.ts';
78
77
  import {
79
78
  mergeSandboxTaskView,
80
- taskViewAfterFinalization,
81
79
  taskViewForManifest,
82
80
  type SandboxTaskView
83
81
  } from './task-view.ts';
@@ -207,61 +205,6 @@ export function writeSandboxControlResponse(manifest: SandboxControlManifest, re
207
205
  return true;
208
206
  }
209
207
 
210
- type FinalizationRecovery = Readonly<{
211
- status: 'matched' | 'deferred' | 'not-applicable';
212
- response?: SandboxControlResponse;
213
- }>;
214
-
215
- function finalizationRecoveryResponse(
216
- manifest: SandboxControlManifest,
217
- requestId: string,
218
- exitCode: number
219
- ): FinalizationRecovery {
220
- if (!manifest.taskId || exitCode !== 0) return { status: 'not-applicable' };
221
- let receipt;
222
- try {
223
- receipt = readTaskFinalizationReceipt(manifest.repoRoot, manifest.taskId);
224
- if (!receipt || !receipt.controlBinding
225
- || receipt.controlBinding.generation !== manifest.generation
226
- || receipt.controlBinding.requestId !== requestId) return { status: 'deferred' };
227
- const resolved = resolveTaskRef(manifest.taskId, { repoRoot: manifest.repoRoot });
228
- if (!resolved.ok || resolved.state !== 'completed' || receipt.lifecycle !== 'done') return { status: 'deferred' };
229
- } catch {
230
- return { status: 'deferred' };
231
- }
232
- return { status: 'matched', response: finalizationTerminalResponse(manifest.taskId, requestId, receipt) };
233
- }
234
-
235
- function publishFinalizationTaskView(
236
- manifest: SandboxControlManifest,
237
- broker: BrokerOwner,
238
- requestId: string,
239
- state: 'starting' | 'healthy' | 'busy' | 'parked',
240
- reasonCode: string | null,
241
- activeRequestId: string | null
242
- ): SandboxTaskView {
243
- let view: SandboxTaskView;
244
- try {
245
- const resolved = resolveTaskRef(manifest.taskId ?? '', { repoRoot: manifest.repoRoot });
246
- const receipt = readTaskFinalizationReceipt(manifest.repoRoot, manifest.taskId ?? '');
247
- view = resolved.ok && resolved.state === 'completed'
248
- ? taskViewAfterFinalization({
249
- taskId: manifest.taskId ?? '', generation: manifest.generation, requestId, receipt
250
- })
251
- : {
252
- state: 'unknown', taskId: manifest.taskId, observedSource: 'unknown', receipt: null,
253
- reasonCode: 'SANDBOX_TASK_VIEW_SOURCE_UNCONFIRMED'
254
- };
255
- } catch {
256
- view = {
257
- state: 'unknown', taskId: manifest.taskId, observedSource: 'unknown', receipt: null,
258
- reasonCode: 'SANDBOX_TASK_VIEW_RECEIPT_INVALID'
259
- };
260
- }
261
- writeSandboxControlStatus(manifest, broker, state, reasonCode, activeRequestId, Date.now(), view);
262
- return view;
263
- }
264
-
265
208
  function payloadReference(payload: ReturnType<typeof createSandboxControlPayload>) {
266
209
  return {
267
210
  version: payload.version,
@@ -458,11 +401,7 @@ export async function recoveryResponse(
458
401
  }, payload.stdout);
459
402
  if (JSON.stringify(payloadTerminal) !== JSON.stringify(terminalResult)) return unknown(request.id);
460
403
  }
461
- const finalization = request.family === 'task-finalization' ? finalizationRecoveryResponse(manifest, request.id, 0) : null;
462
- if (evidence.exitCode === 0 && finalization?.status === 'deferred') return null;
463
- const recovery: RecoveryDomainEvidence = finalization
464
- ? { domain: { consistent: finalization.status === 'matched' } }
465
- : await readRecoveryDomain(manifest, manifestPath, request, operation, terminalResult, payload?.stdout ?? null);
404
+ const recovery = await readRecoveryDomain(manifest, manifestPath, request, operation, terminalResult, payload?.stdout ?? null);
466
405
  const binding = operationRecoveryBinding(request.id, manifest.generation, manifest.taskId, request.family, operationName!);
467
406
  const decision = classifySandboxControlRecovery({
468
407
  operation,
@@ -476,16 +415,11 @@ export async function recoveryResponse(
476
415
  if (decision.outcome === 'unknown' || decision.outcome === 'in-progress') return unknown(request.id);
477
416
  if (decision.outcome === 'not-executed') return notExecuted(request.id);
478
417
  if (decision.outcome !== 'success' && decision.outcome !== 'failure') return null;
479
- if (request.family === 'task-finalization' && evidence.exitCode === 0) {
480
- if (finalization?.status !== 'matched') return null;
481
- return finalization.response ?? null;
482
- }
483
418
  return genericRecoveryResponse(request, evidence.exitCode, payload);
484
419
  }
485
420
 
486
421
  async function terminalMatchesEvidence(
487
422
  manifest: SandboxControlManifest,
488
- manifestPath: string,
489
423
  request: SandboxControlRequest,
490
424
  response: SandboxControlResponse,
491
425
  evidence: ReturnType<typeof readSandboxControlResultEvidence>,
@@ -493,14 +427,6 @@ async function terminalMatchesEvidence(
493
427
  payloadInvalid: boolean,
494
428
  terminalResult: ReturnType<typeof readSandboxControlTerminalResult> | null
495
429
  ): Promise<{ valid: boolean; payloadReferenced: boolean }> {
496
- if (request.family === 'task-finalization' && evidence.exitCode === 0) {
497
- if (!terminalResult) return { valid: false, payloadReferenced: false };
498
- const expected = await recoveryResponse(manifest, manifestPath, request, evidence, payload, terminalResult);
499
- return {
500
- valid: expected !== null && JSON.stringify(response) === JSON.stringify(expected),
501
- payloadReferenced: false
502
- };
503
- }
504
430
  if (response.version !== 2 || response.id !== request.id || response.phase !== 'completed'
505
431
  || response.exitCode !== evidence.exitCode || response.error !== null) {
506
432
  return { valid: false, payloadReferenced: false };
@@ -539,7 +465,6 @@ function publishExecutionResult(
539
465
  manifest: SandboxControlManifest,
540
466
  request: SandboxControlRequest,
541
467
  result: SandboxControlExecutionResult,
542
- broker: BrokerOwner,
543
468
  brokerOwns: () => boolean
544
469
  ): boolean {
545
470
  const normalized = sanitizeSandboxControlResult(manifest, result);
@@ -553,41 +478,26 @@ function publishExecutionResult(
553
478
  criticalRequestPhase(manifest, request, 'evidence-written', normalized.exitCode === 0 ? 'success' : 'failure');
554
479
  criticalRequestPhase(manifest, request, 'publish-authorized', normalized.exitCode === 0 ? 'success' : 'failure');
555
480
  }
556
- let terminal: SandboxControlResponse | null = null;
557
- if (request.family === 'task-finalization') {
558
- const finalization = finalizationRecoveryResponse(manifest, request.id, normalized.exitCode);
559
- if (finalization.status === 'deferred') return false;
560
- terminal = finalization.response ?? null;
561
- }
562
- if (!terminal) {
563
- const inline: SandboxControlResponse = {
564
- version: 2, id: request.id, phase: 'completed', exitCode: normalized.exitCode,
565
- stdout: normalized.stdout, stderr: normalized.stderr, error: null
566
- };
567
- if (sandboxControlEncodedJsonBytes(inline) <= SANDBOX_CONTROL_MAX_TERMINAL_RECORD_BYTES) {
568
- terminal = inline;
569
- }
481
+ let terminal: SandboxControlResponse = {
482
+ version: 2, id: request.id, phase: 'completed', exitCode: normalized.exitCode,
483
+ stdout: normalized.stdout, stderr: normalized.stderr, error: null
484
+ };
485
+ if (sandboxControlEncodedJsonBytes(terminal) > SANDBOX_CONTROL_MAX_TERMINAL_RECORD_BYTES) {
570
486
  let payload: ReturnType<typeof createSandboxControlPayload> | null = null;
571
- if (!terminal) {
572
- try {
573
- payload = createSandboxControlPayload(manifest, request.id, normalized);
574
- const usage = sandboxControlGenerationUsage(manifest);
575
- if (usage.bytes + sandboxControlEncodedJsonBytes(payload) <= SANDBOX_CONTROL_MAX_RESPONSE_BYTES) {
576
- writeSandboxControlPayload(manifest, request.id, normalized);
577
- } else {
578
- payload = null;
579
- }
580
- } catch {
487
+ try {
488
+ payload = createSandboxControlPayload(manifest, request.id, normalized);
489
+ const usage = sandboxControlGenerationUsage(manifest);
490
+ if (usage.bytes + sandboxControlEncodedJsonBytes(payload) <= SANDBOX_CONTROL_MAX_RESPONSE_BYTES) {
491
+ writeSandboxControlPayload(manifest, request.id, normalized);
492
+ } else {
581
493
  payload = null;
582
494
  }
583
- terminal = genericRecoveryResponse(request, normalized.exitCode, payload, 'publish');
495
+ } catch {
496
+ payload = null;
584
497
  }
498
+ terminal = genericRecoveryResponse(request, normalized.exitCode, payload, 'publish');
585
499
  }
586
500
  if (!brokerOwns()) return false;
587
- if (request.family === 'task-finalization' && normalized.exitCode === 0) {
588
- const view = publishFinalizationTaskView(manifest, broker, request.id, 'healthy', null, null);
589
- if (view.state === 'unknown') return false;
590
- }
591
501
  const committed = writeSandboxControlResponse(manifest, terminal);
592
502
  if (committed && fs.existsSync(path.join(manifest.processingDir, request.id, 'transitions'))) {
593
503
  criticalRequestPhase(manifest, request, 'published-committed', normalized.exitCode === 0 ? 'success' : 'failure');
@@ -780,7 +690,6 @@ async function recoverProcessing(manifest: SandboxControlManifest, manifestPath:
780
690
  if (!terminalResult && request.family !== 'task-create') continue;
781
691
  const reconciliation = await terminalMatchesEvidence(
782
692
  manifest,
783
- manifestPath,
784
693
  request,
785
694
  existingTerminalResponse!,
786
695
  resultEvidence,
@@ -791,10 +700,6 @@ async function recoverProcessing(manifest: SandboxControlManifest, manifestPath:
791
700
  if (!reconciliation.valid) continue;
792
701
  terminalReconciled = true;
793
702
  payloadReferenced = reconciliation.payloadReferenced;
794
- if (request.family === 'task-finalization' && resultEvidence.exitCode === 0) {
795
- const view = publishFinalizationTaskView(manifest, broker, request.id, 'starting', null, null);
796
- if (view.state === 'unknown') continue;
797
- }
798
703
  }
799
704
  if (!terminalResult && !terminalReconciled) {
800
705
  if (terminal) continue;
@@ -814,17 +719,13 @@ async function recoverProcessing(manifest: SandboxControlManifest, manifestPath:
814
719
  if (!resultEvidence || !request || payloadInvalid) continue;
815
720
  const recovered = await recoveryResponse(manifest, manifestPath, request, resultEvidence, payload, terminalResult!);
816
721
  if (!recovered) continue;
817
- if (request.family === 'task-finalization' && resultEvidence.exitCode === 0) {
818
- const view = publishFinalizationTaskView(manifest, broker, request.id, 'starting', null, null);
819
- if (view.state === 'unknown') continue;
820
- }
821
722
  writeSandboxControlResponse(manifest, recovered);
822
723
  const preserveRecoveryEvidence = recovered.error?.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN';
823
724
  if (transitionProtocolActive && !preserveRecoveryEvidence) {
824
725
  writeSandboxControlTransition(manifest, { requestId: entry.name, phase: 'recovered' });
825
726
  }
826
727
  if (preserveRecoveryEvidence) continue;
827
- payloadReferenced = Boolean(payload) && request.family !== 'task-finalization';
728
+ payloadReferenced = Boolean(payload);
828
729
  }
829
730
  } else {
830
731
  if (!brokerOwns()) return false;
@@ -922,14 +823,10 @@ export async function serveSandboxControl(
922
823
  try {
923
824
  startupReceipt = readTaskFinalizationReceipt(manifest.repoRoot, manifest.taskId);
924
825
  } catch {
925
- // Preserve malformed receipt evidence as unknown rather than treating it as no receipt.
926
826
  startupReceipt = {};
927
827
  }
928
828
  }
929
- let taskView = taskViewForManifest({
930
- ...manifest,
931
- receipt: startupReceipt
932
- });
829
+ let taskView = taskViewForManifest({ ...manifest, receipt: startupReceipt });
933
830
  try {
934
831
  const previous = readSandboxControlStatus(manifest.publicStatusDir);
935
832
  if (previous.generation === manifest.generation) taskView = mergeSandboxTaskView(taskView, previous.taskView);
@@ -1086,15 +983,7 @@ export async function serveSandboxControl(
1086
983
  if (!brokerOwns()) break;
1087
984
  let terminalCommitted = false;
1088
985
  if (settledExecution.result && settledExecution.resultEvidenceWritten) {
1089
- terminalCommitted = publishExecutionResult(manifest, settledExecution.request, settledExecution.result, broker, brokerOwns);
1090
- if (terminalCommitted && settledExecution.request.family === 'task-finalization') {
1091
- try {
1092
- const published = readSandboxControlStatus(manifest.publicStatusDir);
1093
- if (published.generation === manifest.generation) taskView = published.taskView;
1094
- } catch {
1095
- taskView = { state: 'unknown', taskId: manifest.taskId, observedSource: 'unknown', receipt: null, reasonCode: 'SANDBOX_TASK_VIEW_STATUS_INVALID' };
1096
- }
1097
- }
986
+ terminalCommitted = publishExecutionResult(manifest, settledExecution.request, settledExecution.result, brokerOwns);
1098
987
  } else {
1099
988
  terminalCommitted = writeSandboxControlResponse(manifest, unknown(settledExecution.request.id));
1100
989
  }
@@ -1341,7 +1230,7 @@ export async function serveSandboxControl(
1341
1230
  }
1342
1231
  }
1343
1232
  if (owned && active.result && active.resultEvidenceWritten) {
1344
- if (publishExecutionResult(manifest, active.request, active.result, broker, brokerOwns)) {
1233
+ if (publishExecutionResult(manifest, active.request, active.result, brokerOwns)) {
1345
1234
  if (brokerOwns()) {
1346
1235
  removeAcceptedResponse(manifest, active.request.id);
1347
1236
  fs.rmSync(path.join(manifest.processingDir, active.request.id), { recursive: true, force: true });
@@ -1350,22 +1239,8 @@ export async function serveSandboxControl(
1350
1239
  }
1351
1240
  }
1352
1241
  if (active) {
1353
- const terminationConfirmed = active.prepared.terminate(owned);
1354
- if (owned && brokerOwns() && active.request.family === 'task-finalization') {
1355
- const recovered = finalizationRecoveryResponse(manifest, active.request.id, 0);
1356
- const view = recovered.status === 'matched'
1357
- ? publishFinalizationTaskView(manifest, broker, active.request.id, 'healthy', null, null)
1358
- : null;
1359
- if (view?.state !== 'unknown' && recovered.status === 'matched' && recovered.response
1360
- && writeSandboxControlResponse(manifest, recovered.response)) {
1361
- if (terminationConfirmed && brokerOwns()) {
1362
- removeAcceptedResponse(manifest, active.request.id);
1363
- fs.rmSync(path.join(manifest.processingDir, active.request.id), { recursive: true, force: true });
1364
- active = null;
1365
- }
1366
- }
1367
- }
1368
- if (owned && brokerOwns() && active && active.request.family !== 'task-finalization' && !active.resultEvidenceWritten) {
1242
+ active.prepared.terminate(owned);
1243
+ if (owned && brokerOwns() && active && !active.resultEvidenceWritten) {
1369
1244
  writeSandboxControlResponse(manifest, unknown(active.request.id));
1370
1245
  }
1371
1246
  }
@@ -8,7 +8,6 @@ import { enumerateAllTaskDirs } from '../task/resolve-ref.ts';
8
8
  import { resolveSandboxCleanupTarget } from './workspace-identity.ts';
9
9
  import { readSandboxControlManifest } from './control/lifecycle.ts';
10
10
  import { readJsonFile, readSandboxControlStatus } from './control/state.ts';
11
- import { finalizationTerminalResponse } from './control/finalization-response.ts';
12
11
 
13
12
  const TASK_ID_RE = /^TASK-\d{8}-\d{6}$/;
14
13
 
@@ -83,13 +82,10 @@ function canonicalPath(input: string): string {
83
82
 
84
83
  function taskFinalizationReceiptComplete(
85
84
  receipt: TaskFinalizationReceipt,
86
- taskId: string,
87
- binding: SandboxControlBinding
85
+ taskId: string
88
86
  ): boolean {
89
87
  return receipt.taskId === taskId
90
- && taskFinalizationReceiptState(receipt) === 'complete'
91
- && receipt.controlBinding?.generation === binding.generation
92
- && receipt.controlBinding.requestId === binding.requestId;
88
+ && taskFinalizationReceiptState(receipt) === 'complete';
93
89
  }
94
90
 
95
91
  function terminalControlBindingEvidence(
@@ -114,24 +110,20 @@ function terminalControlBindingEvidence(
114
110
  if (errorCode(error) !== 'ENOENT') return false;
115
111
  }
116
112
  const receipt = readTaskFinalizationReceipt(repoRoot, taskId);
117
- if (!receipt || !taskFinalizationReceiptComplete(receipt, taskId, binding)) {
113
+ if (!receipt || !taskFinalizationReceiptComplete(receipt, taskId)) {
118
114
  return false;
119
115
  }
120
- const status = readSandboxControlStatus(manifest.publicStatusDir);
121
- const viewReceipt = status.taskView.receipt;
122
- const terminalTaskView = (status.taskView.state === 'current' && status.taskView.observedSource === 'completed')
123
- || (status.taskView.state === 'finalized-stale' && status.taskView.observedSource === 'active');
124
- if (status.generation !== binding.generation || status.state !== 'healthy'
125
- || status.activeRequestId !== null || !terminalTaskView || status.taskView.taskId !== taskId
126
- || !viewReceipt || viewReceipt.generation !== binding.generation
127
- || viewReceipt.requestId !== binding.requestId
128
- || viewReceipt.receiptId !== receipt.receiptId
129
- || viewReceipt.revision !== receipt.revision) return false;
130
116
  const responsePath = path.join(manifest.channelDir, 'responses', `${binding.requestId}.json`);
131
117
  if (!inspectOwnedPath(path.join(controlRoot, 'channel'), responsePath, 'file')) return false;
132
118
  const actual = readJsonFile(responsePath);
133
- const expected = finalizationTerminalResponse(taskId, binding.requestId, receipt);
134
- return JSON.stringify(actual) === JSON.stringify(expected);
119
+ const response = actual as Record<string, unknown>;
120
+ if (response.version !== 2 || response.id !== binding.requestId || response.phase !== 'completed'
121
+ || response.exitCode !== 0 || typeof response.stdout !== 'string') return false;
122
+ const envelope = JSON.parse(response.stdout) as Record<string, unknown>;
123
+ const result = envelope.result as Record<string, unknown> | null;
124
+ return envelope.status === 'completed' && envelope.accepted === true
125
+ && result !== null && typeof result === 'object'
126
+ && result.status === 'completed' && result.taskId === taskId;
135
127
  } catch {
136
128
  return false;
137
129
  }
@@ -157,7 +149,7 @@ function removedControlBindingEvidence(
157
149
  if (frontmatter.id !== taskId || frontmatter.branch !== journal.target.branch) return false;
158
150
  try {
159
151
  const receipt = readTaskFinalizationReceipt(repoRoot, taskId);
160
- return receipt !== null && taskFinalizationReceiptComplete(receipt, taskId, binding);
152
+ return receipt !== null && taskFinalizationReceiptComplete(receipt, taskId);
161
153
  } catch {
162
154
  return false;
163
155
  }
@@ -13,6 +13,9 @@ export type ArtifactCommand = Readonly<{
13
13
  family: string;
14
14
  artifact: string;
15
15
  locale?: 'zh-CN' | 'en';
16
+ sourceFinding?: string;
17
+ sourceArtifact?: string;
18
+ sourceSha256?: string;
16
19
  }>;
17
20
 
18
21
  /** One option contract for the CLI and trusted projection executor. */
@@ -23,7 +26,7 @@ export function parseArtifactCommand(args: readonly string[]): ArtifactCommand {
23
26
  }
24
27
  const fields: Record<string, string> = {};
25
28
  const allowed = {
26
- inspect: ['--family'],
29
+ inspect: ['--family', '--source-finding', '--source-artifact', '--source-sha256'],
27
30
  init: ['--family', '--artifact', '--locale'],
28
31
  'preflight': ['--family', '--artifact'],
29
32
  'finalize-local': ['--family', '--artifact']
@@ -41,8 +44,10 @@ export function parseArtifactCommand(args: readonly string[]): ArtifactCommand {
41
44
  const locale = fields['--locale'];
42
45
  if (locale !== undefined && locale !== 'zh-CN' && locale !== 'en') throw new Error("option '--locale' must be 'zh-CN' or 'en'");
43
46
  return {
44
- taskRef, operation: operation as ArtifactCommand['operation'], family: fields['--family'],
45
- artifact: fields['--artifact'] ?? '', locale
47
+ taskRef, operation: operation as ArtifactCommand['operation'], family: fields['--family'] ?? '',
48
+ artifact: fields['--artifact'] ?? '', locale,
49
+ sourceFinding: fields['--source-finding'], sourceArtifact: fields['--source-artifact'],
50
+ sourceSha256: fields['--source-sha256']
46
51
  };
47
52
  }
48
53
  /** Resolve authoritative task metadata separately from a trusted candidate directory. */
@@ -53,7 +58,12 @@ export function executeArtifactCommand(
53
58
  const { taskRef, operation, family, artifact, locale } = command;
54
59
  const fail = (code: string, message: string) => ({ status: 'failed', changed: false, error: { code, message } });
55
60
  if (operation === 'inspect') {
56
- const result = resolveArtifactContext(taskRef, family, { repoRoot: options.repoRoot });
61
+ const result = resolveArtifactContext(taskRef, family, {
62
+ repoRoot: options.repoRoot,
63
+ sourceFinding: command.sourceFinding,
64
+ sourceArtifact: command.sourceArtifact,
65
+ sourceSha256: command.sourceSha256
66
+ });
57
67
  return family === 'code' && result.codeMode ? {
58
68
  ...result, mode: result.codeMode.mode, code_max: result.codeMode.codeMax, rev_max: result.codeMode.reviewMax,
59
69
  verdict: result.codeMode.verdict, next_round: result.next?.round ?? null, next_artifact: result.next?.name ?? null,