@fitlab-ai/agent-infra 0.11.9 → 0.11.11
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/dist/bin/internal-cli.js +0 -6
- package/dist/lib/agent-clients/adapter.js +20 -0
- package/dist/lib/agent-clients/adapters/codex-sandbox.js +13 -0
- package/dist/lib/agent-clients/adapters/codex.js +4 -0
- package/dist/lib/git/workflow.js +1 -1
- package/dist/lib/internal/cli-route-inventory.js +0 -3
- package/dist/lib/internal/manual-validation.js +2 -2
- package/dist/lib/internal/platform-pr.js +4 -5
- package/dist/lib/internal/sandbox-control.js +30 -5
- package/dist/lib/internal/task-artifact.js +1 -1
- package/dist/lib/internal/task-create.js +15 -1
- package/dist/lib/internal/task-finalization.js +14 -25
- package/dist/lib/internal/task-operation-registry.js +6 -141
- package/dist/lib/internal/task-platform-recovery.js +9 -4
- package/dist/lib/platform/completion-backfill.js +2 -1
- package/dist/lib/platform/context.js +4 -2
- package/dist/lib/platform/github-client.js +20 -17
- package/dist/lib/platform/github-provider.js +42 -22
- package/dist/lib/platform/github-release-notes.js +4 -2
- package/dist/lib/platform/in-label-sync.js +2 -1
- package/dist/lib/platform/issue-comments.js +13 -9
- package/dist/lib/platform/operation-coordinator.js +1 -1
- package/dist/lib/platform/pr-review.js +2 -1
- package/dist/lib/platform/pr-summary.js +45 -8
- package/dist/lib/platform/provider-bridge.js +4 -5
- package/dist/lib/platform/provider-contract.d.ts +3 -6
- package/dist/lib/platform/provider-contract.js +3 -15
- package/dist/lib/platform/provider-validation.js +9 -4
- package/dist/lib/platform/pull-requests.js +83 -16
- package/dist/lib/platform/verification-sync.js +17 -14
- package/dist/lib/sandbox/commands/create.js +18 -2
- package/dist/lib/sandbox/commands/enter.js +47 -33
- package/dist/lib/sandbox/control/client.js +99 -17
- package/dist/lib/sandbox/control/executor.js +12 -14
- package/dist/lib/sandbox/control/host-recovery.js +218 -0
- package/dist/lib/sandbox/control/protocol.js +2 -3
- package/dist/lib/sandbox/control/server.js +42 -2
- package/dist/lib/sandbox/environment.js +150 -0
- package/dist/lib/sandbox/recovery.js +8 -0
- package/dist/lib/sandbox/removal.js +0 -1
- package/dist/lib/sandbox/workspace-identity.js +0 -14
- package/dist/lib/sandbox/workspace-view.js +33 -6
- package/dist/lib/task/artifact-command.js +18 -3
- package/dist/lib/task/artifact-lifecycle.js +121 -63
- package/dist/lib/task/artifact-selection.js +1 -1
- package/dist/lib/task/capabilities.js +59 -22
- package/dist/lib/task/commands/status.js +0 -27
- package/dist/lib/task/commit-identity.js +22 -7
- package/dist/lib/task/commit-intent.js +67 -26
- package/dist/lib/task/commit-operation.js +19 -8
- package/dist/lib/task/completion-facts.js +32 -10
- package/dist/lib/task/control-authority.js +2 -6
- package/dist/lib/task/current-contract.js +1 -5
- package/dist/lib/task/events.js +9 -275
- package/dist/lib/task/execution-inputs.js +14 -0
- package/dist/lib/task/finalization-envelope.js +4 -0
- package/dist/lib/task/finalization.js +43 -86
- package/dist/lib/task/frontmatter.js +25 -2
- package/dist/lib/task/ledger-intents.js +6 -8
- package/dist/lib/task/orchestration.js +11 -10
- package/dist/lib/task/platform-operation-journal.js +29 -10
- package/dist/lib/task/platform-operation-recovery.js +109 -35
- package/dist/lib/task/resolve-ref.js +8 -3
- package/dist/lib/task/review-fingerprint.js +4 -26
- package/dist/lib/task/verification-engine.js +95 -6
- package/dist/lib/task/write.js +0 -11
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapter.ts +22 -0
- package/lib/agent-clients/adapters/codex-sandbox.ts +13 -0
- package/lib/agent-clients/adapters/codex.ts +4 -0
- package/lib/git/workflow.ts +1 -1
- package/lib/internal/cli-route-inventory.ts +0 -2
- package/lib/internal/manual-validation.ts +2 -2
- package/lib/internal/platform-pr.ts +3 -4
- package/lib/internal/sandbox-control.ts +39 -6
- package/lib/internal/task-artifact.ts +1 -1
- package/lib/internal/task-create.ts +15 -2
- package/lib/internal/task-finalization.ts +16 -26
- package/lib/internal/task-operation-registry.ts +6 -153
- package/lib/internal/task-platform-recovery.ts +9 -4
- package/lib/platform/completion-backfill.ts +2 -1
- package/lib/platform/context.ts +8 -4
- package/lib/platform/github-client.ts +24 -20
- package/lib/platform/github-provider.ts +47 -26
- package/lib/platform/github-release-notes.ts +5 -3
- package/lib/platform/in-label-sync.ts +2 -1
- package/lib/platform/issue-comments.ts +14 -9
- package/lib/platform/operation-coordinator.ts +1 -1
- package/lib/platform/pr-review.ts +2 -1
- package/lib/platform/pr-summary.ts +43 -7
- package/lib/platform/provider-bridge.ts +10 -9
- package/lib/platform/provider-contract.ts +6 -21
- package/lib/platform/provider-loader.ts +7 -6
- package/lib/platform/provider-validation.ts +30 -8
- package/lib/platform/pull-requests.ts +78 -15
- package/lib/platform/repository-metadata.ts +3 -2
- package/lib/platform/security-alerts.ts +1 -1
- package/lib/platform/types.ts +2 -0
- package/lib/platform/verification-sync.ts +18 -14
- package/lib/sandbox/commands/create.ts +24 -2
- package/lib/sandbox/commands/enter.ts +42 -39
- package/lib/sandbox/control/client.ts +107 -19
- package/lib/sandbox/control/executor.ts +13 -15
- package/lib/sandbox/control/host-recovery.ts +195 -0
- package/lib/sandbox/control/protocol.ts +4 -5
- package/lib/sandbox/control/server.ts +42 -1
- package/lib/sandbox/environment.ts +162 -0
- package/lib/sandbox/recovery.ts +10 -0
- package/lib/sandbox/removal.ts +2 -10
- package/lib/sandbox/tool-types.ts +1 -0
- package/lib/sandbox/workspace-identity.ts +0 -26
- package/lib/sandbox/workspace-view.ts +33 -8
- package/lib/task/artifact-command.ts +19 -3
- package/lib/task/artifact-lifecycle.ts +102 -53
- package/lib/task/artifact-selection.ts +1 -1
- package/lib/task/capabilities.ts +52 -26
- package/lib/task/commands/status.ts +0 -31
- package/lib/task/commit-identity.ts +24 -7
- package/lib/task/commit-intent.ts +58 -24
- package/lib/task/commit-operation.ts +17 -8
- package/lib/task/completion-facts.ts +31 -9
- package/lib/task/control-authority.ts +1 -8
- package/lib/task/current-contract.ts +1 -6
- package/lib/task/events.ts +9 -248
- package/lib/task/execution-inputs.ts +25 -0
- package/lib/task/finalization-envelope.ts +21 -0
- package/lib/task/finalization.ts +40 -82
- package/lib/task/frontmatter.ts +26 -2
- package/lib/task/ledger-intents.ts +6 -8
- package/lib/task/orchestration.ts +11 -17
- package/lib/task/platform-operation-journal.ts +47 -13
- package/lib/task/platform-operation-recovery.ts +102 -37
- package/lib/task/resolve-ref.ts +8 -3
- package/lib/task/review-fingerprint.ts +4 -26
- package/lib/task/verification-engine.ts +103 -9
- package/lib/task/write.ts +0 -19
- package/package.json +1 -1
- package/templates/.agents/rules/README.en.md +1 -1
- package/templates/.agents/rules/README.zh-CN.md +1 -1
- package/templates/.agents/rules/commit-and-pr.en.md +5 -5
- package/templates/.agents/rules/commit-and-pr.zh-CN.md +5 -5
- package/templates/.agents/rules/decision-qualification.en.md +1 -1
- package/templates/.agents/rules/decision-qualification.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-sync.en.md +1 -1
- package/templates/.agents/rules/issue-sync.zh-CN.md +1 -1
- package/templates/.agents/rules/no-mid-flow-questions.en.md +19 -0
- package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +19 -0
- package/templates/.agents/rules/review-handshake.en.md +3 -0
- package/templates/.agents/rules/review-handshake.zh-CN.md +3 -0
- package/templates/.agents/rules/task-management.en.md +1 -1
- package/templates/.agents/rules/task-management.zh-CN.md +1 -1
- package/templates/.agents/skills/analyze-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/block-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/cancel-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/close-codescan/SKILL.en.md +3 -0
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +3 -0
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +3 -0
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +3 -0
- package/templates/.agents/skills/code-task/SKILL.en.md +25 -9
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +27 -10
- package/templates/.agents/skills/code-task/reference/branch-management.en.md +1 -1
- package/templates/.agents/skills/code-task/reference/branch-management.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/reference/code-rules.en.md +1 -1
- package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/reference/dual-mode.en.md +2 -0
- package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +2 -0
- package/templates/.agents/skills/code-task/reference/fix-mode.en.md +5 -16
- package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +5 -16
- package/templates/.agents/skills/code-task/reference/report-template.en.md +1 -1
- package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +1 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +15 -12
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +11 -10
- package/templates/.agents/skills/create-pr/SKILL.en.md +1 -1
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/reference/branch-strategy.en.md +2 -2
- package/templates/.agents/skills/create-pr/reference/branch-strategy.zh-CN.md +2 -2
- package/templates/.agents/skills/create-task/SKILL.en.md +3 -5
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +3 -4
- package/templates/.agents/skills/import-codescan/SKILL.en.md +1 -1
- package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +1 -1
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/import-issue/SKILL.en.md +1 -1
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/plan-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-code/SKILL.en.md +4 -4
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/review-pr/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/test/SKILL.en.md +4 -1
- package/templates/.agents/skills/test/SKILL.zh-CN.md +4 -1
- package/templates/.agents/skills/test-integration/SKILL.en.md +5 -2
- package/templates/.agents/skills/test-integration/SKILL.zh-CN.md +5 -2
- package/templates/.agents/skills/update-agent-infra/SKILL.en.md +5 -2
- package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +5 -2
- package/templates/.agents/skills/upgrade-dependency/SKILL.en.md +5 -2
- package/templates/.agents/skills/upgrade-dependency/SKILL.zh-CN.md +5 -2
- package/templates/.agents/skills/watch-pr/SKILL.en.md +2 -2
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +1 -1
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +1 -1
- package/templates/.agents/templates/task.zh-CN.md +0 -12
- package/templates/.github/workflows/pr-label.yml +1 -2
- package/dist/lib/internal/task-invalidation.js +0 -63
- package/dist/lib/task/finalization-handoff.js +0 -93
- package/dist/lib/task/intermediate-cleanup.js +0 -327
- package/dist/lib/task/invalidation-command.js +0 -88
- package/dist/lib/task/invalidation.js +0 -242
- package/lib/internal/task-invalidation.ts +0 -41
- package/lib/task/finalization-handoff.ts +0 -105
- package/lib/task/intermediate-cleanup.ts +0 -393
- package/lib/task/invalidation-command.ts +0 -88
- package/lib/task/invalidation.ts +0 -313
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { isValidAgentInfraVersion } from '../version.js';
|
|
2
2
|
import { parseTypedTaskFrontmatter } from './frontmatter.js';
|
|
3
3
|
import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, validateLedgerRows } from './ledger.js';
|
|
4
|
-
import { parseInvalidationDocument } from './invalidation.js';
|
|
5
4
|
function validateCurrentTaskContract(content) {
|
|
6
5
|
let metadata;
|
|
7
6
|
try {
|
|
@@ -30,10 +29,7 @@ function validateCurrentTaskContract(content) {
|
|
|
30
29
|
const invalid = validateLedgerRows(ledger.rows);
|
|
31
30
|
if (invalid)
|
|
32
31
|
return { ok: false, code: 'TASK_CURRENT_CONTRACT_INVALID', message: `${invalid.code}: ${invalid.message}` };
|
|
33
|
-
|
|
34
|
-
if (!invalidation.ok)
|
|
35
|
-
return { ok: false, code: 'TASK_CURRENT_CONTRACT_INVALID', message: `${invalidation.code}: ${invalidation.message}` };
|
|
36
|
-
return { ok: true, metadata, ledger, invalidation: invalidation.document };
|
|
32
|
+
return { ok: true, metadata, ledger };
|
|
37
33
|
}
|
|
38
34
|
export { validateCurrentTaskContract };
|
|
39
35
|
//# sourceMappingURL=current-contract.js.map
|
package/dist/lib/task/events.js
CHANGED
|
@@ -19,10 +19,8 @@ import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-
|
|
|
19
19
|
import { captureTaskWriteMetadata, writeTask } from './write.js';
|
|
20
20
|
import { validateLocalArtifact } from './local-artifact-finalization.js';
|
|
21
21
|
import { buildLifecycleFacts, canStart, effectiveReworkTarget } from './capabilities.js';
|
|
22
|
-
import { createInvalidationOperation, invalidationMutation, parseInvalidationDocument, targetIdFor, upsertInvalidation } from './invalidation.js';
|
|
23
|
-
import { reconcileTaskInvalidation } from './invalidation-command.js';
|
|
24
22
|
import { consumeReworkIntents, parseReworkIntentDocument, reworkIntentMutation, supersedeReworkIntents } from './rework-intent.js';
|
|
25
|
-
import {
|
|
23
|
+
import { parseTaskQualification } from './qualification-audit.js';
|
|
26
24
|
import { getArtifactSchema } from './artifact-schema.js';
|
|
27
25
|
import { canonicalSemanticDigest, inspectArtifactContract } from './artifact-operations.js';
|
|
28
26
|
import { inspectReviewIdentity } from './review-identity.js';
|
|
@@ -115,7 +113,7 @@ function validateTaskEventRequest(request) {
|
|
|
115
113
|
return { code: 'EVENT_PAYLOAD_INVALID', message: 'initiator is invalid' };
|
|
116
114
|
if (request.requestId !== undefined && (!request.requestId.trim() || /[\r\n]/.test(request.requestId)))
|
|
117
115
|
return { code: 'EVENT_PAYLOAD_INVALID', message: 'requestId must be a non-empty single line' };
|
|
118
|
-
if (request.reasonCode && !['user-request', 'new-requirement', 'upstream-fact-doubt', 'review-finding', 'retry', 'validation-rerun'].includes(request.reasonCode))
|
|
116
|
+
if (request.reasonCode && !['user-request', 'new-requirement', 'upstream-fact-doubt', 'review-finding', 'manual-review-supplement', 'retry', 'validation-rerun'].includes(request.reasonCode))
|
|
119
117
|
return { code: 'EVENT_PAYLOAD_INVALID', message: 'reasonCode is invalid' };
|
|
120
118
|
for (const [name, value] of [['sourceFinding', request.sourceFinding], ['sourceArtifact', request.sourceArtifact], ['sourceSha256', request.sourceSha256]]) {
|
|
121
119
|
if (value !== undefined && (!value.trim() || /[\r\n]/.test(value)))
|
|
@@ -370,235 +368,6 @@ function eventTrigger(request, family) {
|
|
|
370
368
|
explicitRequest: request.requestId !== undefined || request.reasonCode !== undefined || request.initiator !== undefined || request.orchestrated === true
|
|
371
369
|
};
|
|
372
370
|
}
|
|
373
|
-
function receiptGraphHasCycle(receipts) {
|
|
374
|
-
const edges = new Map();
|
|
375
|
-
for (const receipt of receipts) {
|
|
376
|
-
const outputs = edges.get(receipt.input) ?? [];
|
|
377
|
-
outputs.push(receipt.output);
|
|
378
|
-
edges.set(receipt.input, outputs);
|
|
379
|
-
}
|
|
380
|
-
const visiting = new Set();
|
|
381
|
-
const visited = new Set();
|
|
382
|
-
const visit = (node) => {
|
|
383
|
-
if (visiting.has(node))
|
|
384
|
-
return true;
|
|
385
|
-
if (visited.has(node))
|
|
386
|
-
return false;
|
|
387
|
-
visiting.add(node);
|
|
388
|
-
for (const output of edges.get(node) ?? [])
|
|
389
|
-
if (visit(output))
|
|
390
|
-
return true;
|
|
391
|
-
visiting.delete(node);
|
|
392
|
-
visited.add(node);
|
|
393
|
-
return false;
|
|
394
|
-
};
|
|
395
|
-
return [...edges.keys()].some(visit);
|
|
396
|
-
}
|
|
397
|
-
function qualificationInvalidationSeeds(content, taskDir, inventory, baselineDigest) {
|
|
398
|
-
const task = parseTaskQualification(content);
|
|
399
|
-
if (!task.ok)
|
|
400
|
-
return { changed: true, safe: false, seeds: [] };
|
|
401
|
-
if (baselineDigest !== undefined) {
|
|
402
|
-
if (!/^[a-f0-9]{64}$/u.test(baselineDigest))
|
|
403
|
-
return { changed: true, safe: false, seeds: [] };
|
|
404
|
-
if (baselineDigest === task.qualification.taskInputDigest)
|
|
405
|
-
return { changed: false, safe: true, seeds: [] };
|
|
406
|
-
}
|
|
407
|
-
const audits = [];
|
|
408
|
-
let observedQualification = false;
|
|
409
|
-
for (const artifact of inventory) {
|
|
410
|
-
let artifactContent;
|
|
411
|
-
try {
|
|
412
|
-
artifactContent = fs.readFileSync(path.join(taskDir, artifact.name), 'utf8');
|
|
413
|
-
}
|
|
414
|
-
catch {
|
|
415
|
-
return { changed: true, safe: false, seeds: [] };
|
|
416
|
-
}
|
|
417
|
-
const parsed = parseQualificationAudit(artifactContent);
|
|
418
|
-
if (!parsed.ok)
|
|
419
|
-
return { changed: true, safe: false, seeds: [] };
|
|
420
|
-
if (!parsed.audit.present)
|
|
421
|
-
continue;
|
|
422
|
-
observedQualification = true;
|
|
423
|
-
const snapshot = parsed.audit.snapshot;
|
|
424
|
-
if (!snapshot)
|
|
425
|
-
return { changed: true, safe: false, seeds: [] };
|
|
426
|
-
audits.push({
|
|
427
|
-
name: artifact.name,
|
|
428
|
-
snapshotTaskDigest: snapshot.taskInputDigest,
|
|
429
|
-
snapshotNonConstraintDigest: snapshot.nonConstraintInputDigest,
|
|
430
|
-
dependencies: parsed.audit.constraintDependencies,
|
|
431
|
-
candidates: parsed.audit.candidateQualifications
|
|
432
|
-
});
|
|
433
|
-
}
|
|
434
|
-
if (!task.qualification.present)
|
|
435
|
-
return { changed: false, safe: true, seeds: [] };
|
|
436
|
-
const allAuditsPresent = observedQualification && audits.length === inventory.length;
|
|
437
|
-
if (!allAuditsPresent)
|
|
438
|
-
return { changed: true, safe: false, seeds: [] };
|
|
439
|
-
if (baselineDigest === undefined && audits.every((audit) => audit.snapshotTaskDigest === task.qualification.taskInputDigest)) {
|
|
440
|
-
return { changed: false, safe: true, seeds: [] };
|
|
441
|
-
}
|
|
442
|
-
const candidateMap = new Map(task.qualification.candidates.map((candidate) => [candidate.candidateId, candidate]));
|
|
443
|
-
const currentConstraints = new Map(task.qualification.constraints.map((constraint) => [constraint.constraintId, constraint.digest]));
|
|
444
|
-
const snapshotsMatch = audits.every((audit) => audit.snapshotNonConstraintDigest === task.qualification.nonConstraintInputDigest);
|
|
445
|
-
const candidatesMatch = audits.every((audit) => audit.candidates.length === candidateMap.size
|
|
446
|
-
&& audit.candidates.every((row) => {
|
|
447
|
-
const current = candidateMap.get(row.candidateId);
|
|
448
|
-
return Boolean(current && row.status === current.status && row.impact === current.impact
|
|
449
|
-
&& row.evidence === current.evidence && row.constraintIds.join(',') === current.constraintIds.join(','));
|
|
450
|
-
}));
|
|
451
|
-
const changedConstraints = new Set();
|
|
452
|
-
let referencesValid = true;
|
|
453
|
-
for (const audit of audits) {
|
|
454
|
-
for (const dependency of audit.dependencies) {
|
|
455
|
-
const currentDigest = currentConstraints.get(dependency.constraintId);
|
|
456
|
-
if (!currentDigest)
|
|
457
|
-
referencesValid = false;
|
|
458
|
-
else if (currentDigest !== dependency.constraintDigest)
|
|
459
|
-
changedConstraints.add(dependency.constraintId);
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
const safe = allAuditsPresent && snapshotsMatch && candidatesMatch && referencesValid && changedConstraints.size > 0;
|
|
463
|
-
if (!safe)
|
|
464
|
-
return { changed: true, safe: false, seeds: [] };
|
|
465
|
-
const seeds = new Set();
|
|
466
|
-
for (const audit of audits) {
|
|
467
|
-
if (audit.dependencies.some((dependency) => changedConstraints.has(dependency.constraintId)))
|
|
468
|
-
seeds.add(audit.name);
|
|
469
|
-
}
|
|
470
|
-
return { changed: true, safe: true, seeds: [...seeds] };
|
|
471
|
-
}
|
|
472
|
-
function invalidationMutationForCompletion(content, taskDir, family, artifact, timestamp, frontmatter) {
|
|
473
|
-
if (!['analyze', 'plan', 'code'].includes(family))
|
|
474
|
-
return { mutation: null };
|
|
475
|
-
const parsed = parseInvalidationDocument(content);
|
|
476
|
-
if (!parsed.ok)
|
|
477
|
-
return { error: parsed.message };
|
|
478
|
-
const downstream = {
|
|
479
|
-
analyze: ['review-analysis', 'plan', 'review-plan', 'code', 'review-code'],
|
|
480
|
-
plan: ['review-plan', 'code', 'review-code'],
|
|
481
|
-
code: ['review-code']
|
|
482
|
-
};
|
|
483
|
-
const qualificationFallback = ['analysis', 'review-analysis', 'plan', 'review-plan', 'code', 'review-code'];
|
|
484
|
-
const sourceFamily = family;
|
|
485
|
-
const sourceArtifactFamily = sourceFamily === 'analyze' ? 'analysis' : sourceFamily;
|
|
486
|
-
let receipts = [];
|
|
487
|
-
try {
|
|
488
|
-
receipts = parseArtifactReceipts(content).rows;
|
|
489
|
-
}
|
|
490
|
-
catch (error) {
|
|
491
|
-
return { error: error instanceof Error ? error.message : String(error) };
|
|
492
|
-
}
|
|
493
|
-
const inventory = fs.readdirSync(taskDir).flatMap((name) => {
|
|
494
|
-
const identity = parseArtifactName(name);
|
|
495
|
-
if (!identity || !ARTIFACT_FAMILIES.includes(identity.family))
|
|
496
|
-
return [];
|
|
497
|
-
try {
|
|
498
|
-
const stat = fs.lstatSync(path.join(taskDir, name));
|
|
499
|
-
if (!stat.isFile() || stat.isSymbolicLink())
|
|
500
|
-
return [];
|
|
501
|
-
return [{ family: identity.family, name, round: identity.round, sha256: sha256File(path.join(taskDir, name)) }];
|
|
502
|
-
}
|
|
503
|
-
catch (error) {
|
|
504
|
-
throw new Error(`cannot inspect invalidation artifact '${name}': ${error instanceof Error ? error.message : String(error)}`);
|
|
505
|
-
}
|
|
506
|
-
});
|
|
507
|
-
const nodeMap = new Map(inventory.map((node) => [`${node.family}/${node.name}`, node]));
|
|
508
|
-
let graphUsable = receipts.length > 0;
|
|
509
|
-
for (const receipt of receipts) {
|
|
510
|
-
const output = parseArtifactName(receipt.output);
|
|
511
|
-
const input = nodeMap.get(`${parseArtifactName(receipt.input)?.family}/${receipt.input}`);
|
|
512
|
-
if (!output || !nodeMap.has(`${output.family}/${receipt.output}`) || !input || input.sha256 !== receipt.inputSha256) {
|
|
513
|
-
graphUsable = false;
|
|
514
|
-
break;
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
if (graphUsable && receiptGraphHasCycle(receipts))
|
|
518
|
-
graphUsable = false;
|
|
519
|
-
if (inventory.some((node) => node.family !== 'analysis' && node.name !== artifact.name
|
|
520
|
-
&& !receipts.some((receipt) => receipt.output === node.name)))
|
|
521
|
-
graphUsable = false;
|
|
522
|
-
const selected = new Set();
|
|
523
|
-
if (graphUsable) {
|
|
524
|
-
const currentIdentity = parseArtifactName(artifact.name);
|
|
525
|
-
const previousName = currentIdentity && currentIdentity.round > 1 ? artifactName(sourceArtifactFamily, currentIdentity.round - 1) : null;
|
|
526
|
-
const previous = previousName ? inventory.find((node) => node.family === sourceArtifactFamily && node.name === previousName) : null;
|
|
527
|
-
const previousFact = previousName ? parseCompletionFacts(frontmatter.completion_facts).find((fact) => fact.output === previousName && fact.event === `${sourceFamily === 'analyze' ? 'analysis' : sourceFamily}.completed`) : null;
|
|
528
|
-
if (!previous || !previousFact || previousFact.outputSha256 !== previous.sha256)
|
|
529
|
-
graphUsable = false;
|
|
530
|
-
if (graphUsable && previous) {
|
|
531
|
-
selected.add(`${previous.family}/${previous.name}`);
|
|
532
|
-
let changed = true;
|
|
533
|
-
while (changed) {
|
|
534
|
-
changed = false;
|
|
535
|
-
for (const receipt of receipts) {
|
|
536
|
-
const consumer = `${parseArtifactName(receipt.output).family}/${receipt.output}`;
|
|
537
|
-
if (selected.has(`${parseArtifactName(receipt.input).family}/${receipt.input}`) && !selected.has(consumer)) {
|
|
538
|
-
selected.add(consumer);
|
|
539
|
-
changed = true;
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
selected.delete(`${previous.family}/${previous.name}`);
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
const qualification = qualificationInvalidationSeeds(content, taskDir, inventory, typeof frontmatter.qualification_input_digest === 'string' ? frontmatter.qualification_input_digest : undefined);
|
|
547
|
-
let qualificationChange = qualification.changed;
|
|
548
|
-
if (qualificationChange && (!graphUsable || !qualification.safe))
|
|
549
|
-
graphUsable = false;
|
|
550
|
-
if (qualificationChange && graphUsable) {
|
|
551
|
-
for (const seed of qualification.seeds)
|
|
552
|
-
selected.add(`${parseArtifactName(seed)?.family}/${seed}`);
|
|
553
|
-
let changed = true;
|
|
554
|
-
while (changed) {
|
|
555
|
-
changed = false;
|
|
556
|
-
for (const receipt of receipts) {
|
|
557
|
-
const consumer = `${parseArtifactName(receipt.output).family}/${receipt.output}`;
|
|
558
|
-
if (selected.has(`${parseArtifactName(receipt.input).family}/${receipt.input}`) && !selected.has(consumer)) {
|
|
559
|
-
selected.add(consumer);
|
|
560
|
-
changed = true;
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
const reasonCode = qualificationChange ? 'qualification-changed' : 'upstream-replaced';
|
|
566
|
-
const fallbackFamilies = qualificationChange ? qualificationFallback : downstream[sourceFamily];
|
|
567
|
-
const targetNodes = (graphUsable
|
|
568
|
-
? inventory.filter((node) => selected.has(`${node.family}/${node.name}`))
|
|
569
|
-
: inventory.filter((node) => fallbackFamilies.includes(node.family)))
|
|
570
|
-
.filter((node) => !(node.family === FAMILY[family].artifact && node.name === artifact.name));
|
|
571
|
-
const targets = targetNodes.flatMap((node) => {
|
|
572
|
-
const shapes = [
|
|
573
|
-
{ targetKind: 'artifact', targetFamily: node.family, targetArtifact: node.name, targetRound: node.round, targetSha256: node.sha256 }
|
|
574
|
-
];
|
|
575
|
-
for (const receipt of receipts.filter((candidate) => candidate.output === node.name)) {
|
|
576
|
-
shapes.push({ targetKind: 'receipt', targetFamily: node.family, targetArtifact: node.name, targetInput: receipt.input, targetRound: node.round, targetSha256: receipt.inputSha256 });
|
|
577
|
-
}
|
|
578
|
-
if (node.family.startsWith('review-'))
|
|
579
|
-
shapes.push({ targetKind: 'approval', targetFamily: node.family, targetArtifact: node.name, targetRound: node.round, targetSha256: node.sha256 });
|
|
580
|
-
if (node.family === 'review-code')
|
|
581
|
-
shapes.push({ targetKind: 'reviewed-snapshot', targetFamily: node.family, targetArtifact: node.name, targetRound: node.round, targetSha256: node.sha256 });
|
|
582
|
-
return shapes.map((targetShape) => ({ ...targetShape, targetId: targetIdFor('pending', targetShape), operationId: 'pending', status: 'pending', reasonCode, updatedAt: timestamp }));
|
|
583
|
-
});
|
|
584
|
-
if (targets.length === 0)
|
|
585
|
-
return { mutation: null };
|
|
586
|
-
const source = {
|
|
587
|
-
sourceFamily: lifecycleAction(family), sourceArtifact: artifact.name,
|
|
588
|
-
sourceRound: parseArtifactName(artifact.name)?.round ?? 1, sourceSha256: sha256File(artifact.path),
|
|
589
|
-
createdAt: timestamp, updatedAt: timestamp
|
|
590
|
-
};
|
|
591
|
-
const operation = createInvalidationOperation(source);
|
|
592
|
-
const normalizedTargets = targets.map((target) => ({
|
|
593
|
-
...target, operationId: operation.operationId,
|
|
594
|
-
targetId: targetIdFor(operation.operationId, target)
|
|
595
|
-
}));
|
|
596
|
-
const operationWithTotal = createInvalidationOperation(source, normalizedTargets);
|
|
597
|
-
const next = upsertInvalidation(parsed.document, operationWithTotal, normalizedTargets);
|
|
598
|
-
if (!next.ok)
|
|
599
|
-
return { error: next.message };
|
|
600
|
-
return { mutation: next.changed ? invalidationMutation(content, next.document) : null };
|
|
601
|
-
}
|
|
602
371
|
function reworkIntentMutationForCompletion(content, taskDir, family, artifact, timestamp) {
|
|
603
372
|
const parsed = parseReworkIntentDocument(content);
|
|
604
373
|
if (!parsed.ok)
|
|
@@ -606,23 +375,15 @@ function reworkIntentMutationForCompletion(content, taskDir, family, artifact, t
|
|
|
606
375
|
const hash = sha256File(artifact.path);
|
|
607
376
|
let next = parsed.intents;
|
|
608
377
|
if (family === 'analyze' || family === 'plan' || family === 'code') {
|
|
609
|
-
const hashes = Object.fromEntries(
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
try {
|
|
613
|
-
return [[name, sha256File(path.join(taskDir, name))]];
|
|
614
|
-
}
|
|
615
|
-
catch {
|
|
616
|
-
return [];
|
|
617
|
-
}
|
|
378
|
+
const hashes = Object.fromEntries(['review-analysis', 'review-plan', 'review-code'].flatMap((reviewFamily) => {
|
|
379
|
+
const inventory = inspectArtifactDirectory(taskDir, reviewFamily);
|
|
380
|
+
return inventory.status === 'ready' ? inventory.completed.map((review) => [review.name, sha256File(review.path)]) : [];
|
|
618
381
|
}));
|
|
619
382
|
const action = family === 'analyze' ? 'analysis' : family;
|
|
620
383
|
let target = action;
|
|
621
384
|
if (family === 'analyze') {
|
|
622
|
-
const previousAnalysis =
|
|
623
|
-
.
|
|
624
|
-
.filter((identity) => identity?.family === 'analysis' && identity.name !== artifact.name)
|
|
625
|
-
.sort((left, right) => right.round - left.round || left.name.localeCompare(right.name))[0];
|
|
385
|
+
const previousAnalysis = inspectArtifactDirectory(taskDir, 'analysis').completed
|
|
386
|
+
.filter((identity) => identity.name !== artifact.name).at(-1);
|
|
626
387
|
const pathState = previousAnalysis
|
|
627
388
|
? parseLifecyclePathDecision(fs.readFileSync(path.join(taskDir, previousAnalysis.name), 'utf8'))
|
|
628
389
|
: undefined;
|
|
@@ -764,23 +525,6 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
764
525
|
return failed(request, { code: 'TASK_READ_FAILED', message: String(error) }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
|
|
765
526
|
}
|
|
766
527
|
const initialParts = eventParts(request.event);
|
|
767
|
-
if (initialParts.phase === 'started' && !request.dryRun) {
|
|
768
|
-
const reconciled = reconcileTaskInvalidation(request.taskRef, { repoRoot: resolved.repoRoot });
|
|
769
|
-
if (reconciled.status === 'failed') {
|
|
770
|
-
return failed(request, {
|
|
771
|
-
code: 'EVENT_TRANSITION_INVALID',
|
|
772
|
-
message: `INVALIDATION_RECONCILE_FAILED: ${reconciled.error?.code ?? 'UNKNOWN'}: ${reconciled.error?.message ?? ''}`
|
|
773
|
-
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
|
|
774
|
-
}
|
|
775
|
-
if (reconciled.changed) {
|
|
776
|
-
try {
|
|
777
|
-
content = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
778
|
-
}
|
|
779
|
-
catch (error) {
|
|
780
|
-
return failed(request, { code: 'TASK_READ_FAILED', message: String(error) }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
528
|
let frontmatter;
|
|
785
529
|
try {
|
|
786
530
|
frontmatter = parseTypedTaskFrontmatter(content);
|
|
@@ -946,8 +690,7 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
946
690
|
const safetyFailure = capability.reasonCode === 'TASK_NOT_ACTIVE'
|
|
947
691
|
|| capability.reasonCode === 'LIFECYCLE_EXECUTION_OPEN';
|
|
948
692
|
if (!capability.allowed && (normalized.initiator === 'orchestrator' || safetyFailure)) {
|
|
949
|
-
|
|
950
|
-
return failed(normalized, { code, message: `${capability.reasonCode}: ${capability.evidence.join(', ')}` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
693
|
+
return failed(normalized, { code: 'EVENT_TRANSITION_INVALID', message: `${capability.reasonCode}: ${capability.evidence.join(', ')}` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
951
694
|
}
|
|
952
695
|
}
|
|
953
696
|
const findingCountError = validateReviewFindingCounts(normalized, content, eventIdentity.family, completedArtifact?.path ?? null);
|
|
@@ -985,7 +728,6 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
985
728
|
}
|
|
986
729
|
let completionReceipts = [];
|
|
987
730
|
let currentFact = null;
|
|
988
|
-
let completionInvalidation = null;
|
|
989
731
|
let completionRework = null;
|
|
990
732
|
if (eventIdentity.phase === 'completed' && completedArtifact) {
|
|
991
733
|
const receipt = buildCompletionReceipt(content, resolved.taskDir, eventIdentity.family, completedArtifact, metadata.timestamp, frontmatter, normalized);
|
|
@@ -1006,10 +748,6 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
1006
748
|
return successNoOp(normalized, resolved.taskId, resolved.taskMdPath, currentStep, eventIdentity, completionReceipts[0]?.completedAt ?? metadata.timestamp, frontmatter, artifactContext);
|
|
1007
749
|
}
|
|
1008
750
|
try {
|
|
1009
|
-
const invalidation = invalidationMutationForCompletion(content, resolved.taskDir, eventIdentity.family, completedArtifact, metadata.timestamp, frontmatter);
|
|
1010
|
-
if ('error' in invalidation)
|
|
1011
|
-
return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: invalidation.error }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
1012
|
-
completionInvalidation = invalidation.mutation;
|
|
1013
751
|
const rework = reworkIntentMutationForCompletion(content, resolved.taskDir, eventIdentity.family, completedArtifact, metadata.timestamp);
|
|
1014
752
|
if ('error' in rework)
|
|
1015
753
|
return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: rework.error }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
@@ -1104,8 +842,6 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
1104
842
|
return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
1105
843
|
}
|
|
1106
844
|
}
|
|
1107
|
-
if (completionInvalidation)
|
|
1108
|
-
mutations.push(completionInvalidation);
|
|
1109
845
|
if (completionRework)
|
|
1110
846
|
mutations.push(completionRework);
|
|
1111
847
|
if (eventIdentity.phase === 'completed' && normalized.implementationInput) {
|
|
@@ -1123,9 +859,7 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
1123
859
|
});
|
|
1124
860
|
}
|
|
1125
861
|
mutations.push({ kind: 'section', aliases: ['活动日志', 'Activity Log'], heading: section.heading, body });
|
|
1126
|
-
const
|
|
1127
|
-
&& ['analyze', 'plan', 'code'].includes(eventIdentity.family);
|
|
1128
|
-
const result = writeTask({ taskRef: normalized.taskRef, expectedState: 'active', dryRun: normalized.dryRun, mutations }, { ...options, invalidationContext: sourceCompletion ? 'source-completion' : 'standard', metadataProvider: () => metadata });
|
|
862
|
+
const result = writeTask({ taskRef: normalized.taskRef, expectedState: 'active', dryRun: normalized.dryRun, mutations }, { ...options, metadataProvider: () => metadata });
|
|
1129
863
|
if (result.status === 'failed')
|
|
1130
864
|
return failed(normalized, result.error, { taskId: result.taskId, taskMdPath: result.taskMdPath, fromStep: currentStep, toStep: step, action: eventIdentity.action, phase: eventIdentity.phase, timestamp: result.timestamp, agentInfraVersion: result.agentInfraVersion, operations: result.operations, artifactContext });
|
|
1131
865
|
if (!normalized.dryRun && orchestrationCompletion) {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { pathIncludes } from './lifecycle-path.js';
|
|
2
|
+
function expectedExecutionInputFamilies(stage, pathState) {
|
|
3
|
+
if (pathState.status !== 'valid')
|
|
4
|
+
return [];
|
|
5
|
+
const input = stage === 'plan' ? 'analysis' : pathIncludes(pathState, 'plan') ? 'plan' : 'analysis';
|
|
6
|
+
const review = input === 'plan' ? 'review-plan' : 'review-analysis';
|
|
7
|
+
return [input, ...(pathIncludes(pathState, review) ? [review] : [])];
|
|
8
|
+
}
|
|
9
|
+
function executionInputsMatchLatest(stage, pathState, latest, captured) {
|
|
10
|
+
const expected = expectedExecutionInputFamilies(stage, pathState);
|
|
11
|
+
return expected.every((family) => Boolean(latest[family] && captured.includes(latest[family])));
|
|
12
|
+
}
|
|
13
|
+
export { expectedExecutionInputFamilies, executionInputsMatchLatest };
|
|
14
|
+
//# sourceMappingURL=execution-inputs.js.map
|
|
@@ -10,22 +10,45 @@ import { resourceIdentityEquals } from '../platform/resource-identity.js';
|
|
|
10
10
|
import { parseTaskFrontmatter } from './frontmatter.js';
|
|
11
11
|
import { recoverPlatformOperations } from './platform-operation-recovery.js';
|
|
12
12
|
import { applyTaskLifecycle, inspectTaskLifecycleProgress, previewTaskCompletion } from './lifecycle.js';
|
|
13
|
-
import { resolveTaskRef } from './resolve-ref.js';
|
|
13
|
+
import { enumerateAllTaskDirs, resolveTaskRef } from './resolve-ref.js';
|
|
14
14
|
import { inspectShortIdRegistry } from './short-id.js';
|
|
15
15
|
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
|
|
16
16
|
import { verifyTaskEvent } from './verification.js';
|
|
17
17
|
import { projectFinalizationWarning } from './workflow-warning-intents.js';
|
|
18
18
|
import { mergeOperationWarnings } from './operation-outcome.js';
|
|
19
|
-
import { readTaskFinalizationHandoff } from './finalization-handoff.js';
|
|
20
19
|
import { readSandboxControlManifest } from '../sandbox/control/lifecycle.js';
|
|
21
20
|
import { validateSandboxControlIdentity } from '../sandbox/control/identity-sentinel.js';
|
|
22
21
|
const RECEIPT_VERSION = 4;
|
|
23
22
|
const FINALIZATION_STEPS = ['lifecycle', 'task-comment', 'verification', 'summary'];
|
|
24
|
-
function
|
|
25
|
-
|
|
23
|
+
export function resolveTaskFinalizationDirectory(repoRoot, taskId) {
|
|
24
|
+
const tasks = enumerateAllTaskDirs(repoRoot).filter((task) => task.taskId === taskId);
|
|
25
|
+
if (tasks.length === 0)
|
|
26
|
+
return null;
|
|
27
|
+
if (tasks.length !== 1)
|
|
28
|
+
throw new Error(`FINALIZATION_TASK_IDENTITY_AMBIGUOUS: ${taskId}`);
|
|
29
|
+
const task = tasks[0];
|
|
30
|
+
let canonicalDir;
|
|
31
|
+
try {
|
|
32
|
+
canonicalDir = fs.realpathSync.native(task.taskDir);
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
throw new Error(`FINALIZATION_TASK_IDENTITY_UNAVAILABLE: ${taskId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
36
|
+
}
|
|
37
|
+
let frontmatter;
|
|
38
|
+
try {
|
|
39
|
+
frontmatter = parseTaskFrontmatter(fs.readFileSync(path.join(canonicalDir, 'task.md'), 'utf8'));
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
throw new Error(`FINALIZATION_TASK_IDENTITY_INVALID: ${taskId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
43
|
+
}
|
|
44
|
+
if (task.taskId !== taskId || frontmatter.id !== taskId) {
|
|
45
|
+
throw new Error(`FINALIZATION_TASK_IDENTITY_INVALID: task directory and task.md must identify ${taskId}`);
|
|
46
|
+
}
|
|
47
|
+
return canonicalDir;
|
|
26
48
|
}
|
|
27
49
|
function receiptPath(repoRoot, taskId) {
|
|
28
|
-
|
|
50
|
+
const taskDir = resolveTaskFinalizationDirectory(repoRoot, taskId);
|
|
51
|
+
return taskDir ? path.join(taskDir, '.task-finalization.json') : null;
|
|
29
52
|
}
|
|
30
53
|
function now() {
|
|
31
54
|
return new Date().toISOString();
|
|
@@ -89,7 +112,7 @@ function validWarning(value) {
|
|
|
89
112
|
&& (warning.status === 'open' || warning.status === 'resolved')
|
|
90
113
|
&& (warning.resolvedAt === null || typeof warning.resolvedAt === 'string');
|
|
91
114
|
}
|
|
92
|
-
function
|
|
115
|
+
export function validateTaskFinalizationReceipt(value, taskId) {
|
|
93
116
|
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
94
117
|
throw new Error('receipt must be an object');
|
|
95
118
|
const receipt = value;
|
|
@@ -128,18 +151,18 @@ function validateReceipt(value, taskId) {
|
|
|
128
151
|
}
|
|
129
152
|
function readReceipt(repoRoot, taskId) {
|
|
130
153
|
const file = receiptPath(repoRoot, taskId);
|
|
131
|
-
if (!fs.existsSync(file))
|
|
154
|
+
if (!file || !fs.existsSync(file))
|
|
132
155
|
return null;
|
|
133
156
|
const value = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
134
|
-
return
|
|
157
|
+
return validateTaskFinalizationReceipt(value, taskId);
|
|
135
158
|
}
|
|
136
159
|
function readTaskFinalizationReceipt(repoRoot, taskId) {
|
|
137
160
|
return readReceipt(path.resolve(repoRoot), taskId);
|
|
138
161
|
}
|
|
139
162
|
function writeReceipt(repoRoot, receipt) {
|
|
140
|
-
const directory = finalizationRoot(repoRoot);
|
|
141
|
-
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
142
163
|
const target = receiptPath(repoRoot, receipt.taskId);
|
|
164
|
+
if (!target)
|
|
165
|
+
throw new Error(`TASK_FINALIZATION_TASK_NOT_FOUND: ${receipt.taskId}`);
|
|
143
166
|
const temporary = `${target}.${process.pid}.${randomUUID()}.tmp`;
|
|
144
167
|
try {
|
|
145
168
|
fs.writeFileSync(temporary, `${JSON.stringify(receipt)}\n`, { mode: 0o600, flag: 'wx' });
|
|
@@ -670,7 +693,7 @@ async function prepareUnderLock(request, taskId, options) {
|
|
|
670
693
|
let receipt;
|
|
671
694
|
try {
|
|
672
695
|
const file = receiptPath(repoRoot, taskId);
|
|
673
|
-
const existed = fs.existsSync(file);
|
|
696
|
+
const existed = file !== null && fs.existsSync(file);
|
|
674
697
|
receipt = readReceipt(repoRoot, taskId) ?? emptyReceipt(taskId, options.controlBinding);
|
|
675
698
|
if (!existed)
|
|
676
699
|
writeReceipt(repoRoot, receipt);
|
|
@@ -678,16 +701,6 @@ async function prepareUnderLock(request, taskId, options) {
|
|
|
678
701
|
catch (error) {
|
|
679
702
|
return failed(taskId, errorOf(error, 'TASK_FINALIZATION_RECEIPT_INVALID'));
|
|
680
703
|
}
|
|
681
|
-
if (options.controlBinding && (receipt.controlBinding?.generation !== options.controlBinding.generation
|
|
682
|
-
|| receipt.controlBinding.requestId !== options.controlBinding.requestId)) {
|
|
683
|
-
const state = resolveTaskRef(taskId, { repoRoot });
|
|
684
|
-
if (!(receipt.lifecycle === 'done' && state.ok && state.state === 'completed')) {
|
|
685
|
-
return failed(taskId, {
|
|
686
|
-
code: 'TASK_FINALIZATION_RECOVERY_PROOF_UNAVAILABLE',
|
|
687
|
-
message: 'canonical receipt is not bound to the current sandbox request', retryable: false
|
|
688
|
-
});
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
704
|
const preflightState = resolveTaskRef(taskId, { repoRoot });
|
|
692
705
|
if (options.preflight && preflightState.ok && preflightState.state === 'active') {
|
|
693
706
|
try {
|
|
@@ -710,7 +723,7 @@ async function prepareUnderLock(request, taskId, options) {
|
|
|
710
723
|
if (preflightState.ok && preflightState.state === 'active') {
|
|
711
724
|
const recovery = await recoverPlatformOperations(taskId, 'all', { agent: request.agent, cwd: repoRoot });
|
|
712
725
|
if (recovery.status !== 'applied' && recovery.status !== 'no-op') {
|
|
713
|
-
return failed(taskId, recovery.error
|
|
726
|
+
return failed(taskId, recovery.error ? { ...recovery.error, retryable: true } : {
|
|
714
727
|
code: 'PLATFORM_OPERATION_RECOVERY_PENDING',
|
|
715
728
|
message: 'Pending platform operations remain unresolved',
|
|
716
729
|
retryable: true
|
|
@@ -720,12 +733,10 @@ async function prepareUnderLock(request, taskId, options) {
|
|
|
720
733
|
const taskFrontmatter = parseTaskFrontmatter(taskContent);
|
|
721
734
|
const issueIdentity = taskIssueIdentity(taskFrontmatter);
|
|
722
735
|
if (issueIdentity) {
|
|
723
|
-
const base = typeof taskFrontmatter.delivery_base_ref === 'string' ? taskFrontmatter.delivery_base_ref.trim() : '';
|
|
724
736
|
let synced;
|
|
725
737
|
try {
|
|
726
738
|
synced = await issueSync(taskId, {
|
|
727
|
-
agent: request.agent, cwd: repoRoot, requirements: true, issueType: true, fields: true,
|
|
728
|
-
...(base ? { inLabels: 'from-diff', base } : {}), dependency: 'required'
|
|
739
|
+
agent: request.agent, cwd: repoRoot, requirements: true, issueType: true, fields: true, dependency: 'required'
|
|
729
740
|
});
|
|
730
741
|
}
|
|
731
742
|
catch (error) {
|
|
@@ -733,7 +744,7 @@ async function prepareUnderLock(request, taskId, options) {
|
|
|
733
744
|
return failed(taskId, detail);
|
|
734
745
|
}
|
|
735
746
|
const issueOperations = synced.operations;
|
|
736
|
-
const badOperation = issueOperations?.find((operation) => ['requirements', 'issue-type', 'fields'
|
|
747
|
+
const badOperation = issueOperations?.find((operation) => ['requirements', 'issue-type', 'fields'].includes(operation.name)
|
|
737
748
|
&& operation.status !== 'applied' && operation.status !== 'no-op'
|
|
738
749
|
&& !(operation.name === 'requirements' && operation.status === 'skipped' && operation.reasonCode === 'NO_REQUIREMENTS_ANCHOR'));
|
|
739
750
|
if ((synced.status !== 'applied' && synced.status !== 'no-op') || synced.error || badOperation) {
|
|
@@ -759,7 +770,7 @@ async function prepareUnderLock(request, taskId, options) {
|
|
|
759
770
|
retryable: true
|
|
760
771
|
});
|
|
761
772
|
}
|
|
762
|
-
for (const name of ['requirements', 'issue-type', 'fields'
|
|
773
|
+
for (const name of ['requirements', 'issue-type', 'fields']) {
|
|
763
774
|
const operation = issueOperations?.find((candidate) => candidate.name === name);
|
|
764
775
|
if (!operation)
|
|
765
776
|
return failed(taskId, {
|
|
@@ -782,14 +793,6 @@ async function prepareUnderLock(request, taskId, options) {
|
|
|
782
793
|
code: 'FINALIZATION_ISSUE_TYPE_UNCONFIRMED', message: 'Issue Type did not match the verified target', retryable: true
|
|
783
794
|
});
|
|
784
795
|
}
|
|
785
|
-
else if (name === 'labels:in') {
|
|
786
|
-
const expected = Array.isArray(operation.value) ? operation.value : before?.labels;
|
|
787
|
-
if (!expected || expected.filter((label) => label.startsWith('in:')).sort().join('\0')
|
|
788
|
-
!== remote.labels.filter((label) => label.startsWith('in:')).sort().join('\0'))
|
|
789
|
-
return failed(taskId, {
|
|
790
|
-
code: 'FINALIZATION_IN_LABELS_UNCONFIRMED', message: 'Issue in-labels did not match the verified target', retryable: true
|
|
791
|
-
});
|
|
792
|
-
}
|
|
793
796
|
else {
|
|
794
797
|
const expected = operation.value && typeof operation.value === 'object'
|
|
795
798
|
? operation.value
|
|
@@ -989,70 +992,24 @@ async function commitPreparedTaskFinalization(request, options) {
|
|
|
989
992
|
}
|
|
990
993
|
}
|
|
991
994
|
let receipt = readReceipt(repoRoot, resolved.taskId);
|
|
992
|
-
if (!receipt && options.controlBinding && options.handoffDirectory && request.handoffSha256) {
|
|
993
|
-
try {
|
|
994
|
-
const handoff = readTaskFinalizationHandoff(options.handoffDirectory, resolved.taskId, options.controlBinding, request.handoffSha256);
|
|
995
|
-
receipt = validateReceipt(handoff.receipt, resolved.taskId);
|
|
996
|
-
if (receipt.lifecycle !== 'pending')
|
|
997
|
-
throw new Error('handoff lifecycle must be pending');
|
|
998
|
-
if (inspectTaskLifecycleProgress(repoRoot, resolved.taskId, request.agent) === 'unknown') {
|
|
999
|
-
throw new Error('TASK_FINALIZATION_RECOVERY_PROOF_UNAVAILABLE');
|
|
1000
|
-
}
|
|
1001
|
-
writeReceipt(repoRoot, receipt);
|
|
1002
|
-
try {
|
|
1003
|
-
handoff.cleanup();
|
|
1004
|
-
}
|
|
1005
|
-
catch { /* keep the persisted canonical receipt usable */ }
|
|
1006
|
-
}
|
|
1007
|
-
catch (error) {
|
|
1008
|
-
return failed(resolved.taskId, {
|
|
1009
|
-
code: 'TASK_FINALIZATION_HANDOFF_INVALID',
|
|
1010
|
-
message: error instanceof Error ? error.message : String(error), retryable: false
|
|
1011
|
-
});
|
|
1012
|
-
}
|
|
1013
|
-
}
|
|
1014
995
|
if (!receipt)
|
|
1015
996
|
return failed(resolved.taskId, {
|
|
1016
997
|
code: 'TASK_FINALIZATION_PREPARATION_REQUIRED', message: 'sandbox preparation receipt is unavailable', retryable: true
|
|
1017
998
|
});
|
|
999
|
+
if (options.controlBinding && !receipt.controlBinding) {
|
|
1000
|
+
receipt = updateReceipt(repoRoot, receipt, { controlBinding: options.controlBinding });
|
|
1001
|
+
}
|
|
1018
1002
|
if (receipt.lifecycle === 'done' && receipt.verification !== 'pending' && resolved.state === 'completed') {
|
|
1019
1003
|
return terminalResult(resolved.taskId, receipt, { lifecycle: { status: 'no-op', changed: false, error: null } }, false);
|
|
1020
1004
|
}
|
|
1021
1005
|
if (options.controlBinding) {
|
|
1022
1006
|
const proof = inspectTaskLifecycleProgress(repoRoot, resolved.taskId, request.agent);
|
|
1023
|
-
|
|
1024
|
-
if (!oldBinding || oldBinding.generation !== options.controlBinding.generation || proof === 'unknown') {
|
|
1007
|
+
if (proof === 'unknown') {
|
|
1025
1008
|
return failed(resolved.taskId, {
|
|
1026
1009
|
code: 'TASK_FINALIZATION_RECOVERY_PROOF_UNAVAILABLE',
|
|
1027
|
-
message: '
|
|
1010
|
+
message: 'task lifecycle progress cannot be verified', retryable: false
|
|
1028
1011
|
});
|
|
1029
1012
|
}
|
|
1030
|
-
if (oldBinding.requestId !== options.controlBinding.requestId && proof === 'not-started') {
|
|
1031
|
-
if (receipt.lifecycle !== 'pending' || !options.handoffDirectory || !request.handoffSha256) {
|
|
1032
|
-
return failed(resolved.taskId, {
|
|
1033
|
-
code: 'TASK_FINALIZATION_RECOVERY_PROOF_UNAVAILABLE',
|
|
1034
|
-
message: 'pending receipt cannot be rebound without a current handoff', retryable: false
|
|
1035
|
-
});
|
|
1036
|
-
}
|
|
1037
|
-
try {
|
|
1038
|
-
const handoff = readTaskFinalizationHandoff(options.handoffDirectory, resolved.taskId, options.controlBinding, request.handoffSha256);
|
|
1039
|
-
const nextReceipt = validateReceipt(handoff.receipt, resolved.taskId);
|
|
1040
|
-
if (nextReceipt.receiptId !== receipt.receiptId || nextReceipt.intent !== receipt.intent
|
|
1041
|
-
|| nextReceipt.lifecycle !== 'pending')
|
|
1042
|
-
throw new Error('handoff is not continuous with canonical receipt');
|
|
1043
|
-
receipt = updateReceipt(repoRoot, receipt, { controlBinding: options.controlBinding });
|
|
1044
|
-
try {
|
|
1045
|
-
handoff.cleanup();
|
|
1046
|
-
}
|
|
1047
|
-
catch { /* keep the persisted canonical receipt usable */ }
|
|
1048
|
-
}
|
|
1049
|
-
catch (error) {
|
|
1050
|
-
return failed(resolved.taskId, {
|
|
1051
|
-
code: 'TASK_FINALIZATION_HANDOFF_INVALID',
|
|
1052
|
-
message: error instanceof Error ? error.message : String(error), retryable: false
|
|
1053
|
-
});
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1056
1013
|
}
|
|
1057
1014
|
if (receipt.taskComment === 'pending' || receipt.summary === 'pending' || receipt.warningProjection === 'pending') {
|
|
1058
1015
|
return failed(resolved.taskId, {
|