@fitlab-ai/agent-infra 0.11.0 → 0.11.2

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 (146) hide show
  1. package/dist/lib/internal/cli-route-inventory.js +1 -1
  2. package/dist/lib/internal/manual-validation.js +6 -2
  3. package/dist/lib/internal/platform-comment.js +59 -0
  4. package/dist/lib/internal/sandbox-control.js +4 -131
  5. package/dist/lib/internal/task-artifact.js +1 -1
  6. package/dist/lib/internal/task-operation-registry.js +1 -0
  7. package/dist/lib/internal/task-review.js +5 -5
  8. package/dist/lib/internal/task-validate.js +0 -6
  9. package/dist/lib/platform/comment-safety.js +7 -1
  10. package/dist/lib/platform/github-provider.js +13 -3
  11. package/dist/lib/platform/issue-comments.js +120 -13
  12. package/dist/lib/platform/issues.js +3 -4
  13. package/dist/lib/platform/provider-contract.d.ts +2 -0
  14. package/dist/lib/platform/provider-validation.js +7 -2
  15. package/dist/lib/platform/verification-sync.js +161 -156
  16. package/dist/lib/sandbox/control/executor.js +0 -38
  17. package/dist/lib/sandbox/control/finalization-response.js +10 -2
  18. package/dist/lib/sandbox/control/task-view.js +3 -1
  19. package/dist/lib/sandbox/control/workflow-executor.js +32 -41
  20. package/dist/lib/task/artifact-command.js +6 -6
  21. package/dist/lib/task/artifact-lifecycle.js +15 -52
  22. package/dist/lib/task/artifact-operations.js +27 -1
  23. package/dist/lib/task/capabilities.js +0 -2
  24. package/dist/lib/task/codex-orchestration.js +10 -208
  25. package/dist/lib/task/commit-operation.js +3 -18
  26. package/dist/lib/task/control-authority.js +1 -8
  27. package/dist/lib/task/delegation-receipts.js +14 -76
  28. package/dist/lib/task/events.js +147 -278
  29. package/dist/lib/task/finalization-state.js +2 -1
  30. package/dist/lib/task/finalization.js +273 -49
  31. package/dist/lib/task/gate-policy.js +94 -0
  32. package/dist/lib/task/lifecycle-execution.js +2 -11
  33. package/dist/lib/task/local-artifact-finalization.js +31 -245
  34. package/dist/lib/task/orchestration.js +8 -24
  35. package/dist/lib/task/review-artifacts.js +12 -6
  36. package/dist/lib/task/review-command.js +0 -1
  37. package/dist/lib/task/review-finalization.js +47 -218
  38. package/dist/lib/task/verification-engine.js +28 -17
  39. package/dist/lib/task/verification.js +25 -9
  40. package/dist/lib/task/workflow-command.js +4 -1
  41. package/dist/lib/task/workflow-dispatch.js +55 -3
  42. package/dist/lifecycle-build-manifest.json +3 -3
  43. package/lib/internal/cli-route-inventory.ts +1 -1
  44. package/lib/internal/manual-validation.ts +6 -2
  45. package/lib/internal/platform-comment.ts +38 -0
  46. package/lib/internal/sandbox-control.ts +3 -145
  47. package/lib/internal/task-artifact.ts +1 -1
  48. package/lib/internal/task-operation-registry.ts +1 -0
  49. package/lib/internal/task-review.ts +5 -5
  50. package/lib/internal/task-validate.ts +0 -5
  51. package/lib/platform/comment-safety.ts +8 -0
  52. package/lib/platform/github-provider.ts +11 -2
  53. package/lib/platform/issue-comments.ts +132 -13
  54. package/lib/platform/issues.ts +3 -4
  55. package/lib/platform/provider-contract.ts +2 -0
  56. package/lib/platform/provider-validation.ts +6 -2
  57. package/lib/platform/verification-sync.ts +178 -179
  58. package/lib/sandbox/control/executor.ts +0 -44
  59. package/lib/sandbox/control/finalization-response.ts +10 -2
  60. package/lib/sandbox/control/task-view.ts +3 -1
  61. package/lib/sandbox/control/workflow-executor.ts +31 -48
  62. package/lib/task/artifact-command.ts +6 -7
  63. package/lib/task/artifact-lifecycle.ts +12 -49
  64. package/lib/task/artifact-operations.ts +27 -1
  65. package/lib/task/capabilities.ts +0 -1
  66. package/lib/task/codex-orchestration.ts +8 -241
  67. package/lib/task/commit-operation.ts +3 -23
  68. package/lib/task/control-authority.ts +1 -8
  69. package/lib/task/delegation-receipts.ts +12 -79
  70. package/lib/task/events.ts +155 -287
  71. package/lib/task/finalization-state.ts +2 -1
  72. package/lib/task/finalization.ts +277 -57
  73. package/lib/task/gate-policy.ts +133 -0
  74. package/lib/task/lifecycle-execution.ts +10 -21
  75. package/lib/task/local-artifact-finalization.ts +29 -285
  76. package/lib/task/orchestration.ts +12 -41
  77. package/lib/task/review-artifacts.ts +8 -2
  78. package/lib/task/review-command.ts +0 -1
  79. package/lib/task/review-finalization.ts +46 -200
  80. package/lib/task/verification-engine.ts +24 -14
  81. package/lib/task/verification-types.ts +7 -0
  82. package/lib/task/verification.ts +25 -9
  83. package/lib/task/workflow-command.ts +4 -1
  84. package/lib/task/workflow-dispatch.ts +46 -3
  85. package/package.json +2 -2
  86. package/templates/.agents/rules/issue-sync.en.md +2 -0
  87. package/templates/.agents/rules/issue-sync.zh-CN.md +5 -8
  88. package/templates/.agents/rules/lifecycle-orchestration.en.md +7 -7
  89. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +7 -7
  90. package/templates/.agents/rules/local-artifact-repair.en.md +14 -52
  91. package/templates/.agents/rules/local-artifact-repair.zh-CN.md +13 -52
  92. package/templates/.agents/skills/analyze-task/SKILL.en.md +3 -3
  93. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +3 -3
  94. package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -10
  95. package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -10
  96. package/templates/.agents/skills/block-task/config/verify.json +1 -3
  97. package/templates/.agents/skills/cancel-task/config/verify.json +1 -4
  98. package/templates/.agents/skills/code-task/SKILL.en.md +3 -3
  99. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +3 -3
  100. package/templates/.agents/skills/code-task/config/verify.en.json +1 -12
  101. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -12
  102. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +1 -1
  103. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +1 -1
  104. package/templates/.agents/skills/code-task/reference/report-template.en.md +11 -0
  105. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +11 -0
  106. package/templates/.agents/skills/commit/config/verify.json +1 -5
  107. package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +6 -6
  108. package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +6 -6
  109. package/templates/.agents/skills/complete-task/SKILL.en.md +3 -3
  110. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +3 -3
  111. package/templates/.agents/skills/complete-task/config/verify.en.json +3 -14
  112. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +3 -14
  113. package/templates/.agents/skills/create-pr/config/verify.json +1 -10
  114. package/templates/.agents/skills/create-task/config/verify.json +1 -4
  115. package/templates/.agents/skills/import-issue/config/verify.json +1 -2
  116. package/templates/.agents/skills/plan-task/SKILL.en.md +3 -3
  117. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +3 -3
  118. package/templates/.agents/skills/plan-task/config/verify.en.json +1 -10
  119. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -10
  120. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +4 -4
  121. package/templates/.agents/skills/review-analysis/SKILL.en.md +2 -2
  122. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +2 -2
  123. package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -10
  124. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -10
  125. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +2 -0
  126. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +2 -0
  127. package/templates/.agents/skills/review-code/SKILL.en.md +3 -3
  128. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +3 -3
  129. package/templates/.agents/skills/review-code/config/verify.en.json +1 -10
  130. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -10
  131. package/templates/.agents/skills/review-code/reference/report-template.en.md +2 -0
  132. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +2 -0
  133. package/templates/.agents/skills/review-plan/SKILL.en.md +2 -2
  134. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +2 -2
  135. package/templates/.agents/skills/review-plan/config/verify.en.json +1 -10
  136. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -10
  137. package/templates/.agents/skills/review-plan/reference/report-template.en.md +2 -0
  138. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +2 -0
  139. package/templates/.agents/skills/review-pr/config/verify.en.json +1 -8
  140. package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +1 -8
  141. package/templates/.agents/skills/run-manual-validation/config/verify.en.json +38 -13
  142. package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +38 -13
  143. package/templates/.agents/skills/run-task/SKILL.en.md +6 -6
  144. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +6 -6
  145. package/templates/.agents/skills/run-task/reference/host-validation.en.md +2 -2
  146. package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +2 -2
@@ -357,43 +357,6 @@ async function executeRequestInner(manifest, manifestPath, request, options = {}
357
357
  ? lifecycleFailure('LIFECYCLE_PAYLOAD_INVALID', message)
358
358
  : orchestrationFailure(code, message);
359
359
  }
360
- let controllerBinding = null;
361
- if (request.family === 'task-orchestration') {
362
- const codexPrepare = operation.family === 'task-orchestration'
363
- && operation.intent === 'prepare'
364
- && operation.input.client === 'codex';
365
- if (codexPrepare) {
366
- if (!request.controllerProof) {
367
- const authority = options.lifecycleRecoveryAttestation;
368
- if (!authority || authority.phase !== 'orchestration.prepare') {
369
- return orchestrationFailure('CODEX_SANDBOX_CONTROLLER_PROOF_REQUIRED', 'Codex prepare requires a current controller lease proof');
370
- }
371
- controllerBinding = {
372
- instanceDigest: authority.controllerInstanceDigest,
373
- controlGeneration: authority.controlGeneration
374
- };
375
- }
376
- else {
377
- try {
378
- controllerBinding = (options.resolveControllerBinding ?? resolveCodexControllerBinding)({
379
- manifest,
380
- manifestPath,
381
- proof: request.controllerProof,
382
- buildIdentity: (options.buildIdentity ?? computeLifecycleBuildIdentity)(manifest.repoRoot)
383
- });
384
- }
385
- catch (error) {
386
- const code = error instanceof CodexControllerRegistrationError
387
- ? error.code
388
- : 'CODEX_SANDBOX_CONTROLLER_PROOF_INVALID';
389
- return orchestrationFailure(code, `${code}: Codex controller proof was rejected`);
390
- }
391
- }
392
- }
393
- else if (request.controllerProof !== null) {
394
- return orchestrationFailure('CODEX_SANDBOX_CONTROLLER_PROOF_INVALID', 'Controller proof is only accepted for canonical Codex prepare');
395
- }
396
- }
397
360
  const diagnosticLog = (event, fields) => {
398
361
  appendExecutorAudit(manifest, event.startsWith('orchestration-') ? event : `orchestration-${event}`, {
399
362
  requestId: request.id,
@@ -411,7 +374,6 @@ async function executeRequestInner(manifest, manifestPath, request, options = {}
411
374
  manifestPath,
412
375
  requestId: request.id,
413
376
  diagnosticLog,
414
- ...(controllerBinding ? { controllerBinding } : {}),
415
377
  lifecycleRecoveryAttestation: options.lifecycleRecoveryAttestation ?? null
416
378
  });
417
379
  const format = (value) => {
@@ -1,9 +1,15 @@
1
1
  export function finalizationTerminalResponse(taskId, requestId, receipt) {
2
2
  const pendingSteps = [
3
3
  receipt.taskComment === 'pending' ? 'task-comment' : null,
4
- receipt.verification === 'pending' ? 'verification' : null
4
+ receipt.verification === 'pending' ? 'verification' : null,
5
+ receipt.summary === 'pending' ? 'summary' : null,
6
+ receipt.postSummaryVerification === 'pending' ? 'post-summary-verification' : null
5
7
  ].filter((step) => step !== null);
6
- const completedSteps = ['lifecycle', receipt.taskComment === 'pending' ? null : 'task-comment', receipt.verification === 'pending' ? null : 'verification']
8
+ const completedSteps = [
9
+ 'lifecycle', receipt.taskComment === 'pending' ? null : 'task-comment',
10
+ receipt.verification === 'pending' ? null : 'verification', receipt.summary === 'pending' ? null : 'summary',
11
+ receipt.postSummaryVerification === 'pending' ? null : 'post-summary-verification'
12
+ ]
7
13
  .filter((step) => step !== null);
8
14
  const warnings = receipt.warnings
9
15
  .filter((warning) => warning.status === 'open')
@@ -13,6 +19,8 @@ export function finalizationTerminalResponse(taskId, requestId, receipt) {
13
19
  lifecycle: { status: 'no-op', changed: false, error: null },
14
20
  taskComment: receipt.taskComment === 'pending' ? null : { status: 'no-op', changed: false, error: null },
15
21
  verification: receipt.verification === 'pending' ? null : { status: 'no-op', changed: false, error: null },
22
+ summary: receipt.summary === 'pending' ? null : { status: 'no-op', changed: false, error: null },
23
+ postSummaryVerification: receipt.postSummaryVerification === 'pending' ? null : { status: 'no-op', changed: false, error: null },
16
24
  completedSteps, pendingSteps,
17
25
  result: pendingSteps.length > 0 || receipt.warningProjection === 'pending' || warnings.length > 0
18
26
  ? 'completed_with_warnings' : 'completed',
@@ -13,9 +13,11 @@ function receiptIdentity(value, generation, requestId) {
13
13
  if (!binding || typeof binding !== 'object' || Array.isArray(binding))
14
14
  return null;
15
15
  const controlBinding = binding;
16
- if (receipt.version !== 2 || typeof receipt.receiptId !== 'string' || receipt.receiptId.length === 0
16
+ if (receipt.version !== 3 || typeof receipt.receiptId !== 'string' || receipt.receiptId.length === 0
17
17
  || !Number.isSafeInteger(receipt.revision) || receipt.revision < 0
18
18
  || receipt.lifecycle !== 'done'
19
+ || !['pending', 'done', 'skipped'].includes(String(receipt.summary))
20
+ || !['pending', 'done', 'skipped'].includes(String(receipt.postSummaryVerification))
19
21
  || controlBinding.generation !== generation
20
22
  || typeof controlBinding.requestId !== 'string'
21
23
  || !/^[a-f0-9-]{16,64}$/u.test(controlBinding.requestId)
@@ -1,9 +1,11 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { randomUUID } from 'node:crypto';
1
4
  import { TASK_WORKFLOW_COMMANDS } from '../../task/workflow-command.js';
2
5
  import { parseArtifactCommand, executeArtifactCommand } from '../../task/artifact-command.js';
3
6
  import { parseReviewCommand } from '../../task/review-command.js';
4
7
  import { prepareLocalArtifact, preflightLocalArtifact, commitLocalArtifactProvenance } from '../../task/local-artifact-finalization.js';
5
- import { prepareReviewSummaryCandidate, commitReviewSummaryProvenance } from '../../task/review-finalization.js';
6
- import { readArtifactRecoveryIntent } from '../../task/artifact-repair-intent.js';
8
+ import { prepareReviewSummaryCandidate, preflightReviewSummary } from '../../task/review-finalization.js';
7
9
  import { withTaskExecutionLock } from '../../task/task-execution-lock.js';
8
10
  import { dispatchWorkflowCommand } from '../../task/workflow-dispatch.js';
9
11
  import { assertSandboxTaskSource } from '../workspace-view.js';
@@ -14,20 +16,25 @@ import { parseTaskEventRequest } from '../../internal/task-event.js';
14
16
  function executionResult(result) {
15
17
  return { exitCode: result.status === 'failed' || result.status === 'refused' ? 1 : 0, stdout: `${JSON.stringify(result)}\n`, stderr: '' };
16
18
  }
19
+ function replaceCurrentArtifactAtomically(file, content) {
20
+ const temporary = path.join(path.dirname(file), `.${path.basename(file)}.${process.pid}.${randomUUID()}.tmp`);
21
+ try {
22
+ fs.writeFileSync(temporary, content, { encoding: 'utf8', flag: 'wx' });
23
+ fs.renameSync(temporary, file);
24
+ }
25
+ finally {
26
+ try {
27
+ fs.unlinkSync(temporary);
28
+ }
29
+ catch (error) {
30
+ if (error.code !== 'ENOENT')
31
+ throw error;
32
+ }
33
+ }
34
+ }
17
35
  /** The broker already authorized this host executor; domain operations own validation. */
18
36
  export async function executeTaskWorkflow(manifest, request, lifecycleRecoveryAttestation = null, options = {}) {
19
37
  let publicationCommitted = false;
20
- let recoveryObservation = null;
21
- const observeRecoveryPublication = () => {
22
- if (!recoveryObservation || recoveryObservation.wasPassed)
23
- return;
24
- const intent = readArtifactRecoveryIntent(manifest.repoRoot, request.taskId, recoveryObservation.family, recoveryObservation.artifact);
25
- publicationCommitted ||= intent?.state === 'preflight-ready'
26
- || intent?.state === 'preflight-commit-started'
27
- || intent?.state === 'preflight-passed'
28
- || intent?.state === 'passed'
29
- || intent?.state === 'commit-started';
30
- };
31
38
  let faultTriggered = false;
32
39
  const fault = (window) => {
33
40
  if (faultTriggered || options.faultWindow !== window)
@@ -91,46 +98,31 @@ export async function executeTaskWorkflow(manifest, request, lifecycleRecoveryAt
91
98
  const { family } = input;
92
99
  if (family !== 'analysis' && family !== 'plan' && family !== 'code')
93
100
  throw new Error('ARTIFACT_IDENTITY_INVALID');
94
- const local = { taskRef: request.taskId, family, artifact: input.artifact, repoRoot: manifest.repoRoot, recoveryId: input.recoveryId, lockAlreadyHeld: true };
101
+ const local = { taskRef: request.taskId, family, artifact: input.artifact, repoRoot: manifest.repoRoot, lockAlreadyHeld: true };
95
102
  if (input.operation === 'preflight') {
96
- // Preflight only seals a verified generation. It must not publish the
97
- // formal artifact, set publicationStarted, or append finalizer audit.
98
103
  const result = preflightLocalArtifact(local);
99
- recoveryObservation = { family, artifact: input.artifact, wasPassed: false };
100
- observeRecoveryPublication();
101
104
  return response(result);
102
105
  }
103
- recoveryObservation = {
104
- family,
105
- artifact: input.artifact,
106
- wasPassed: readArtifactRecoveryIntent(manifest.repoRoot, request.taskId, family, input.artifact)?.state === 'passed'
107
- };
108
- const prepared = prepareLocalArtifact(local, content, lifecycleRecoveryAttestation ?? undefined);
109
- observeRecoveryPublication();
110
- const commitOptions = { afterPublish: () => fault('after-atomic-rename') };
111
- if (prepared.result.status === 'failed')
112
- return executionResult(commitLocalArtifactProvenance(prepared, commitOptions));
113
- result = commitLocalArtifactProvenance(prepared, commitOptions);
106
+ const prepared = prepareLocalArtifact(local, content);
107
+ result = commitLocalArtifactProvenance(prepared);
114
108
  }
115
109
  else {
116
110
  if (input.overrideTicket)
117
111
  throw new Error('TASK_WORKFLOW_OVERRIDE_UNSUPPORTED');
118
112
  const family = `review-${input.stage}`;
119
- recoveryObservation = {
120
- family,
121
- artifact: input.artifact,
122
- wasPassed: readArtifactRecoveryIntent(manifest.repoRoot, request.taskId, family, input.artifact)?.state === 'passed'
123
- };
124
- const prepared = prepareReviewSummaryCandidate(input, content, { repoRoot: manifest.repoRoot, lockAlreadyHeld: true, startRecovery: true });
125
- observeRecoveryPublication();
113
+ if (request.operation === 'review-preflight') {
114
+ const result = preflightReviewSummary(input, { repoRoot: manifest.repoRoot, lockAlreadyHeld: true });
115
+ return response(result);
116
+ }
117
+ const prepared = prepareReviewSummaryCandidate(input, content, { repoRoot: manifest.repoRoot, lockAlreadyHeld: true });
126
118
  if (input.dryRun || prepared.result.status === 'planned')
127
119
  return executionResult(prepared.result);
128
- const commitOptions = { afterPublish: () => fault('after-atomic-rename') };
129
120
  if (prepared.result.status === 'failed')
130
- return executionResult(commitReviewSummaryProvenance(prepared, manifest.repoRoot, commitOptions));
131
- result = commitReviewSummaryProvenance(prepared, manifest.repoRoot, commitOptions);
121
+ return executionResult(prepared.result);
122
+ if (prepared.result.changed)
123
+ replaceCurrentArtifactAtomically(path.join(taskDir, input.artifact), prepared.content);
124
+ result = prepared.result;
132
125
  }
133
- observeRecoveryPublication();
134
126
  publicationCommitted ||= result.changed === true;
135
127
  if (result.status !== 'failed')
136
128
  appendDiagnosticAudit(manifest, 'task-workflow-artifact-finalized', {
@@ -142,7 +134,6 @@ export async function executeTaskWorkflow(manifest, request, lifecycleRecoveryAt
142
134
  });
143
135
  }
144
136
  catch (error) {
145
- observeRecoveryPublication();
146
137
  const message = error instanceof Error ? error.message : String(error);
147
138
  const code = error?.code ?? /^([A-Z][A-Z0-9_]+)/u.exec(message)?.[1] ?? 'TASK_WORKFLOW_REQUEST_INVALID';
148
139
  return executionResult({ status: 'failed', changed: publicationCommitted ? null : false, error: { code, message } });
@@ -15,8 +15,8 @@ export function parseArtifactCommand(args) {
15
15
  const allowed = {
16
16
  inspect: ['--family'],
17
17
  init: ['--family', '--artifact', '--locale'],
18
- 'preflight': ['--family', '--artifact', '--recovery-id'],
19
- 'finalize-local': ['--family', '--artifact', '--recovery-id']
18
+ 'preflight': ['--family', '--artifact'],
19
+ 'finalize-local': ['--family', '--artifact']
20
20
  }[operation];
21
21
  for (let index = 2; index < args.length; index += 1) {
22
22
  const flag = args[index];
@@ -38,12 +38,12 @@ export function parseArtifactCommand(args) {
38
38
  throw new Error("option '--locale' must be 'zh-CN' or 'en'");
39
39
  return {
40
40
  taskRef, operation: operation, family: fields['--family'],
41
- artifact: fields['--artifact'] ?? '', locale, recoveryId: fields['--recovery-id']
41
+ artifact: fields['--artifact'] ?? '', locale
42
42
  };
43
43
  }
44
44
  /** Resolve authoritative task metadata separately from a trusted candidate directory. */
45
45
  export function executeArtifactCommand(command, options = {}) {
46
- const { taskRef, operation, family, artifact, locale, recoveryId } = command;
46
+ const { taskRef, operation, family, artifact, locale } = command;
47
47
  const fail = (code, message) => ({ status: 'failed', changed: false, error: { code, message } });
48
48
  if (operation === 'inspect') {
49
49
  const result = resolveArtifactContext(taskRef, family, { repoRoot: options.repoRoot });
@@ -76,7 +76,7 @@ export function executeArtifactCommand(command, options = {}) {
76
76
  return fail('ARTIFACT_PAYLOAD_INVALID', "preflight and finalize-local only support 'analysis', 'plan', and 'code'");
77
77
  }
78
78
  if (operation === 'preflight')
79
- return { ...preflightLocalArtifact({ taskRef, family, artifact, repoRoot: resolved.repoRoot, recoveryId }) };
80
- return { ...finalizeLocalArtifact({ taskRef, family, artifact, repoRoot: resolved.repoRoot, recoveryId }) };
79
+ return { ...preflightLocalArtifact({ taskRef, family, artifact, repoRoot: resolved.repoRoot }) };
80
+ return { ...finalizeLocalArtifact({ taskRef, family, artifact, repoRoot: resolved.repoRoot }) };
81
81
  }
82
82
  //# sourceMappingURL=artifact-command.js.map
@@ -150,72 +150,35 @@ function inspectArtifactDirectory(taskDir, family, identity = {}) {
150
150
  };
151
151
  }
152
152
  function resolveReviewedInput(taskDir, review, expectedFamily, diagnostics) {
153
- let content;
154
- try {
155
- content = fs.readFileSync(review.path, 'utf8');
156
- }
157
- catch (error) {
158
- diagnostics.push(diagnostic('BROKEN_REFERENCE', review.family, review.name, String(error)));
159
- return null;
160
- }
161
- const parsed = parseReviewedInputReference(content, expectedFamily);
162
- if (!parsed) {
163
- diagnostics.push(diagnostic('BROKEN_REFERENCE', review.family, review.name, `${review.name} does not reference a canonical ${expectedFamily} artifact`));
164
- return null;
165
- }
166
- const abs = path.join(taskDir, parsed.name);
167
153
  try {
154
+ const taskContent = fs.readFileSync(path.join(taskDir, 'task.md'), 'utf8');
155
+ const receipt = receiptForOutput(taskContent, review.name);
156
+ const expectedEvent = reviewEventName(review.family);
157
+ if (!receipt || receipt.event !== expectedEvent)
158
+ throw new Error(`receipt for ${review.name} is missing or invalid`);
159
+ const input = parseArtifactName(receipt.input);
160
+ if (!input || input.family !== expectedFamily)
161
+ throw new Error(`receipt input '${receipt.input}' is not a canonical ${expectedFamily} artifact`);
162
+ const abs = path.join(taskDir, input.name);
168
163
  const stat = fs.lstatSync(abs);
169
164
  if (!stat.isFile() || stat.isSymbolicLink())
170
165
  throw new Error('referenced input is not a regular file');
171
- const taskContent = fs.readFileSync(path.join(taskDir, 'task.md'), 'utf8');
172
166
  const invalidation = parseInvalidationDocument(taskContent);
173
167
  if (!invalidation.ok)
174
168
  throw new Error(invalidation.message);
175
- if (isArtifactInvalidated(invalidation.document, expectedFamily, parsed.name)) {
176
- throw new Error(`${parsed.name} is invalidated`);
177
- }
178
- const receipt = receiptForOutput(taskContent, review.name);
179
- if (!receipt)
180
- throw new Error(`receipt for ${review.name} is missing`);
181
- const expectedEvent = reviewEventName(review.family);
182
- if (receipt.event !== expectedEvent || receipt.input !== parsed.name) {
183
- throw new Error(`receipt for ${review.name} does not match ${parsed.name}`);
184
- }
169
+ if (isArtifactInvalidated(invalidation.document, expectedFamily, input.name))
170
+ throw new Error(`${input.name} is invalidated`);
185
171
  const actualSha256 = sha256File(abs);
186
172
  if (actualSha256 !== receipt.inputSha256) {
187
- throw new Error(`${parsed.name} content digest does not match receipt`);
173
+ throw new Error(`${input.name} content digest does not match receipt`);
188
174
  }
189
- return { ...parsed, path: abs, size: stat.size, mtimeMs: stat.mtimeMs };
175
+ return { ...input, path: abs, size: stat.size, mtimeMs: stat.mtimeMs };
190
176
  }
191
177
  catch (error) {
192
- diagnostics.push(diagnostic('BROKEN_REFERENCE', review.family, review.name, `${parsed.name}: ${String(error)}`));
178
+ diagnostics.push(diagnostic('BROKEN_REFERENCE', review.family, review.name, String(error)));
193
179
  return null;
194
180
  }
195
181
  }
196
- function parseReviewedInputReference(content, expectedFamily) {
197
- const lines = content.split(/\r?\n/);
198
- const header = lines.findIndex((line) => /\*\*(?:审查输入|Review Input)\*\*[::]/.test(line));
199
- const referenceBlock = header >= 0 ? lines.slice(header, header + 12).join('\n') : '';
200
- const candidates = [...referenceBlock.matchAll(/`([^`]+\.md)`/g)].map((match) => match[1]);
201
- return candidates
202
- .map((name) => parseArtifactName(name))
203
- .find((item) => item?.family === expectedFamily) ?? null;
204
- }
205
- function parseCodePlanInputReference(content) {
206
- const lines = content.split(/\r?\n/);
207
- const sectionStart = lines.findIndex((line) => /^##\s+(?:实现输入|Implementation Input)\s*$/.test(line.trim()));
208
- if (sectionStart < 0)
209
- return null;
210
- const bodyLines = lines.slice(sectionStart + 1);
211
- const nextSection = bodyLines.findIndex((line) => /^##\s+/.test(line.trim()));
212
- const body = (nextSection < 0 ? bodyLines : bodyLines.slice(0, nextSection)).join('\n');
213
- const match = body.match(/\*\*(?:方案输入|Plan Input)\*\*[::]\s*`([^`]+\.md)`/);
214
- if (!match)
215
- return null;
216
- const parsed = parseArtifactName(match[1]);
217
- return parsed?.family === 'plan' ? { ...parsed, family: 'plan' } : null;
218
- }
219
182
  function reviewEventName(family) {
220
183
  if (family === 'review-analysis')
221
184
  return 'review-analysis.completed';
@@ -509,5 +472,5 @@ function buildArtifactLinkSection(content, artifact) {
509
472
  body = lines.join('\n').replace(/^\n+|\n+$/g, '');
510
473
  return { aliases, heading, body };
511
474
  }
512
- export { artifactFamilyCatalog, familySpec, inspectTaskArtifacts, inspectArtifactDirectory, resolveArtifactContext, parseReviewedInputReference, parseCodePlanInputReference, resolveCodePlanInput, reviewEventName, assertWritableInventory, validateCompletedArtifact, buildArtifactLinkSection };
475
+ export { artifactFamilyCatalog, familySpec, inspectTaskArtifacts, inspectArtifactDirectory, resolveArtifactContext, resolveCodePlanInput, reviewEventName, assertWritableInventory, validateCompletedArtifact, buildArtifactLinkSection };
513
476
  //# sourceMappingURL=artifact-lifecycle.js.map
@@ -6,6 +6,7 @@ import { parseArtifactName } from './artifact-name.js';
6
6
  import { scanVisibleMarkdown } from './markdown.js';
7
7
  import { getArtifactSchema, renderArtifactSkeleton } from './artifact-schema.js';
8
8
  import { withTaskExecutionLock } from './task-execution-lock.js';
9
+ import { buildQualificationAudit, expectedQualificationRelations, parseTaskQualification, renderQualificationAudit } from './qualification-audit.js';
9
10
  function sha256Content(content) {
10
11
  return createHash('sha256').update(content, 'utf8').digest('hex');
11
12
  }
@@ -193,6 +194,27 @@ function artifactRound(family, artifact) {
193
194
  const identity = parseArtifactName(artifact);
194
195
  return identity?.family === family ? identity.round : null;
195
196
  }
197
+ function qualificationAuditForInit(request) {
198
+ let taskContent;
199
+ try {
200
+ taskContent = readStableFileSync(path.join(request.taskDir, 'task.md'), { maxBytes: 1024 * 1024 }).bytes.toString('utf8');
201
+ }
202
+ catch (error) {
203
+ return { ok: false, code: 'ARTIFACT_INIT_TASK_READ_FAILED', message: error instanceof Error ? error.message : String(error) };
204
+ }
205
+ const qualification = parseTaskQualification(taskContent);
206
+ if (!qualification.ok)
207
+ return qualification;
208
+ if (!qualification.qualification.present)
209
+ return { ok: true, content: '' };
210
+ const relations = expectedQualificationRelations(taskContent, request.family);
211
+ if (!relations.ok)
212
+ return relations;
213
+ const audit = buildQualificationAudit(taskContent, { upstreamRelations: relations.relations ?? [] });
214
+ if (!audit.ok)
215
+ return audit;
216
+ return { ok: true, content: `\n## 资格审计\n\n${renderQualificationAudit(audit.audit)}\n` };
217
+ }
196
218
  function initializeArtifactSkeleton(request) {
197
219
  const schema = getArtifactSchema(request.family);
198
220
  if (!schema)
@@ -216,12 +238,16 @@ function initializeArtifactSkeleton(request) {
216
238
  if (error.code !== 'ENOENT')
217
239
  return resultFailure('ARTIFACT_INIT_TARGET_INVALID', String(error));
218
240
  }
219
- const content = renderArtifactSkeleton({
241
+ const skeleton = renderArtifactSkeleton({
220
242
  taskId: request.taskId,
221
243
  family: request.family,
222
244
  artifact: request.artifact,
223
245
  ...(request.locale ? { locale: request.locale } : {})
224
246
  });
247
+ const audit = qualificationAuditForInit(request);
248
+ if (!audit.ok)
249
+ return resultFailure(audit.code, audit.message);
250
+ const content = `${skeleton.trimEnd()}${audit.content}`;
225
251
  const tempPath = path.join(request.taskDir, `.${request.artifact}.init-${process.pid}-${Date.now()}.tmp`);
226
252
  try {
227
253
  fs.writeFileSync(tempPath, content, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
@@ -146,8 +146,6 @@ function qualificationRecoveryAction(facts) {
146
146
  return QUALIFICATION_RECOVERY_ORDER.find(({ pattern }) => stale.some((name) => pattern.test(name)))?.action ?? null;
147
147
  }
148
148
  function recommendNext(facts) {
149
- if (invalidationBlocks(facts.invalidation))
150
- return { action: null, reasonCode: 'INVALIDATION_INCOMPLETE', evidence: ['reconcile task invalidation before routing'] };
151
149
  if (facts.qualificationStale) {
152
150
  const action = qualificationRecoveryAction(facts);
153
151
  return {