@fitlab-ai/agent-infra 0.11.11 → 0.11.13
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 +2 -2
- package/dist/lib/agent-clients/adapter.js +7 -1
- package/dist/lib/agent-clients/adapters/claude-code.js +5 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +78 -63
- package/dist/lib/agent-clients/adapters/codex-lifecycle/binding.js +114 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/cli.js +447 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-client.js +130 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-operation.js +131 -0
- package/dist/lib/{sandbox/control → agent-clients/adapters/codex-lifecycle}/controller-registration.js +2 -2
- package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +48 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +124 -62
- package/dist/lib/{internal/codex-sandbox-controller.js → agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.js} +3 -3
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +3 -5
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +211 -213
- package/dist/lib/agent-clients/adapters/codex-orchestration.js +468 -0
- package/dist/lib/agent-clients/adapters/codex.js +40 -1
- package/dist/lib/agent-clients/adapters/index.js +2 -10
- package/dist/lib/agent-clients/tokens.js +3 -2
- package/dist/lib/internal/agent-client.js +132 -72
- package/dist/lib/internal/cli-route-inventory.js +2 -2
- package/dist/lib/internal/sandbox-control.js +1 -81
- package/dist/lib/internal/task-finalization.js +26 -19
- package/dist/lib/internal/task-lifecycle.js +1 -1
- package/dist/lib/internal/task-operation-registry.js +1 -1
- package/dist/lib/internal/task-validate.js +1 -1
- package/dist/lib/platform/github-client.js +33 -3
- package/dist/lib/platform/issues.js +18 -6
- package/dist/lib/runtime/agent-runtime.js +6 -29
- package/dist/lib/sandbox/commands/control-status.js +1 -0
- package/dist/lib/sandbox/commands/create.js +7 -2
- package/dist/lib/sandbox/commands/ls.js +4 -5
- package/dist/lib/sandbox/commands/rm.js +1 -1
- package/dist/lib/sandbox/control/agent-operation.js +34 -0
- package/dist/lib/sandbox/control/client.js +14 -201
- package/dist/lib/sandbox/control/executor.js +12 -125
- package/dist/lib/sandbox/control/lifecycle.js +66 -4
- package/dist/lib/sandbox/control/protocol.js +15 -54
- package/dist/lib/sandbox/control/server.js +216 -281
- package/dist/lib/sandbox/control/task-view.js +0 -13
- package/dist/lib/sandbox/mounts.js +9 -1
- package/dist/lib/sandbox/recovery.js +31 -10
- package/dist/lib/sandbox/removal.js +28 -5
- package/dist/lib/sandbox/workspace-identity.js +18 -6
- package/dist/lib/sandbox/workspace-view.js +56 -19
- package/dist/lib/task/commands/ls.js +3 -3
- package/dist/lib/task/control-authority.js +56 -264
- package/dist/lib/task/control-recovery.js +2 -3
- package/dist/lib/task/create.js +2 -2
- package/dist/lib/task/delegation-receipts.js +74 -161
- package/dist/lib/task/events.js +88 -44
- package/dist/lib/task/finalization.js +35 -6
- package/dist/lib/task/lifecycle.js +274 -115
- package/dist/lib/task/orchestration.js +201 -194
- package/dist/lib/task/runtime-paths.js +10 -0
- package/dist/lib/task/short-id.js +37 -27
- package/dist/lib/task/verification-engine.js +32 -26
- package/dist/lib/task/workspace-snapshot.js +1 -1
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapter.ts +39 -0
- package/lib/agent-clients/adapters/claude-code.ts +5 -1
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +105 -80
- package/lib/agent-clients/adapters/codex-lifecycle/binding.ts +147 -0
- package/lib/agent-clients/adapters/codex-lifecycle/cli.ts +510 -0
- package/lib/agent-clients/adapters/codex-lifecycle/controller-client.ts +158 -0
- package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +1 -1
- package/lib/agent-clients/adapters/codex-lifecycle/controller-operation.ts +151 -0
- package/lib/{sandbox/control → agent-clients/adapters/codex-lifecycle}/controller-registration.ts +4 -4
- package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +43 -0
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +8 -5
- package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +190 -65
- package/lib/{internal/codex-sandbox-controller.ts → agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.ts} +2 -2
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +3 -5
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +270 -224
- package/lib/agent-clients/adapters/codex-orchestration.ts +632 -0
- package/lib/agent-clients/adapters/codex.ts +44 -1
- package/lib/agent-clients/adapters/index.ts +7 -20
- package/lib/agent-clients/tokens.ts +4 -3
- package/lib/internal/agent-client.ts +142 -73
- package/lib/internal/cli-route-inventory.ts +2 -2
- package/lib/internal/sandbox-control.ts +0 -97
- package/lib/internal/task-finalization.ts +30 -18
- package/lib/internal/task-lifecycle.ts +1 -1
- package/lib/internal/task-operation-registry.ts +1 -1
- package/lib/internal/task-validate.ts +1 -1
- package/lib/platform/github-client.ts +34 -3
- package/lib/platform/issues.ts +18 -6
- package/lib/runtime/agent-runtime.ts +6 -41
- package/lib/sandbox/commands/control-status.ts +1 -0
- package/lib/sandbox/commands/create.ts +7 -1
- package/lib/sandbox/commands/ls.ts +4 -5
- package/lib/sandbox/commands/rm.ts +1 -1
- package/lib/sandbox/control/agent-operation.ts +49 -0
- package/lib/sandbox/control/client.ts +20 -254
- package/lib/sandbox/control/executor.ts +13 -136
- package/lib/sandbox/control/lifecycle.ts +67 -4
- package/lib/sandbox/control/protocol.ts +22 -66
- package/lib/sandbox/control/server.ts +21 -95
- package/lib/sandbox/control/task-view.ts +0 -19
- package/lib/sandbox/mounts.ts +10 -1
- package/lib/sandbox/recovery.ts +38 -17
- package/lib/sandbox/removal.ts +33 -7
- package/lib/sandbox/workspace-identity.ts +16 -6
- package/lib/sandbox/workspace-view.ts +50 -18
- package/lib/task/commands/ls.ts +3 -3
- package/lib/task/control-authority.ts +66 -378
- package/lib/task/control-recovery.ts +3 -4
- package/lib/task/create.ts +3 -3
- package/lib/task/delegation-receipts.ts +87 -231
- package/lib/task/events.ts +100 -42
- package/lib/task/finalization.ts +37 -7
- package/lib/task/lifecycle.ts +257 -109
- package/lib/task/orchestration.ts +260 -237
- package/lib/task/runtime-paths.ts +12 -0
- package/lib/task/short-id.ts +33 -40
- package/lib/task/verification-engine.ts +30 -25
- package/lib/task/workspace-snapshot.ts +1 -1
- package/package.json +1 -1
- package/templates/.agents/hooks/lifecycle-delegation.js +1 -25
- package/templates/.agents/rules/README.en.md +2 -1
- package/templates/.agents/rules/README.zh-CN.md +2 -1
- package/templates/.agents/rules/next-step-output.en.md +6 -19
- package/templates/.agents/rules/next-step-output.zh-CN.md +6 -19
- package/templates/.agents/rules/strategy-pattern.en.md +25 -0
- package/templates/.agents/rules/strategy-pattern.zh-CN.md +25 -0
- 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/check-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/reference/code-rules.en.md +4 -0
- package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +4 -0
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +0 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +0 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +5 -29
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +5 -26
- package/templates/.agents/skills/plan-task/SKILL.en.md +7 -4
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +7 -4
- package/templates/.agents/skills/review-analysis/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-code/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-plan/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +0 -546
- package/dist/lib/internal/codex-lifecycle.js +0 -385
- package/dist/lib/task/codex-orchestration.js +0 -206
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +0 -776
- package/lib/internal/codex-lifecycle.ts +0 -390
- package/lib/task/codex-orchestration.ts +0 -276
|
@@ -21,77 +21,37 @@ function nullableText(value) {
|
|
|
21
21
|
function nullableSafeInteger(value) {
|
|
22
22
|
return value === null || (Number.isSafeInteger(value) && value >= 0);
|
|
23
23
|
}
|
|
24
|
-
const LIFECYCLE_PROVENANCE_KEYS = [
|
|
25
|
-
'protocolVersion', 'packageVersion', 'internalExecutableBuildHash', 'lifecycleContractHash',
|
|
26
|
-
'hookDefinitionHash', 'hookSource', 'hookSourcePathDigest', 'hookSourceHash',
|
|
27
|
-
'capabilitySessionId', 'capabilityTurnId', 'capabilityToolUseId',
|
|
28
|
-
'controllerInstanceDigest', 'controlGeneration'
|
|
29
|
-
];
|
|
30
|
-
function isDelegationLifecycleProvenance(value) {
|
|
31
|
-
if (!hasExactKeys(value, LIFECYCLE_PROVENANCE_KEYS))
|
|
32
|
-
return false;
|
|
33
|
-
return Number.isSafeInteger(value.protocolVersion)
|
|
34
|
-
&& value.protocolVersion > 0
|
|
35
|
-
&& exactText(value.packageVersion)
|
|
36
|
-
&& exactText(value.internalExecutableBuildHash)
|
|
37
|
-
&& exactText(value.lifecycleContractHash)
|
|
38
|
-
&& exactText(value.hookDefinitionHash)
|
|
39
|
-
&& ['project', 'managed', 'isolated-user'].includes(value.hookSource)
|
|
40
|
-
&& exactText(value.hookSourcePathDigest)
|
|
41
|
-
&& exactText(value.hookSourceHash)
|
|
42
|
-
&& exactText(value.capabilitySessionId)
|
|
43
|
-
&& exactText(value.capabilityTurnId)
|
|
44
|
-
&& exactText(value.capabilityToolUseId)
|
|
45
|
-
&& nullableText(value.controllerInstanceDigest)
|
|
46
|
-
&& nullableText(value.controlGeneration);
|
|
47
|
-
}
|
|
48
|
-
const HOST_EVIDENCE_REQUIRED_KEYS = [
|
|
49
|
-
'kind', 'hookDefinitionHash', 'startRevision', 'stopRevision', 'consumer', 'consumedAt'
|
|
50
|
-
];
|
|
51
|
-
const HOST_EVIDENCE_OPTIONAL_KEYS = [
|
|
52
|
-
'protocolVersion', 'packageVersion', 'internalExecutableBuildHash', 'lifecycleContractHash',
|
|
53
|
-
'hookSource', 'hookSourcePathDigest', 'hookSourceHash', 'capabilitySessionId',
|
|
54
|
-
'capabilityTurnId', 'capabilityToolUseId', 'spawnToolUseId', 'spawnObservedAt',
|
|
55
|
-
'controllerInstanceDigest', 'controlGeneration'
|
|
56
|
-
];
|
|
57
|
-
function isDelegationHostEvidence(value) {
|
|
58
|
-
if (!hasExactKeys(value, HOST_EVIDENCE_REQUIRED_KEYS, HOST_EVIDENCE_OPTIONAL_KEYS))
|
|
59
|
-
return false;
|
|
60
|
-
if (!['codex-lifecycle-v1', 'codex-lifecycle-v2'].includes(value.kind))
|
|
61
|
-
return false;
|
|
62
|
-
if (!exactText(value.hookDefinitionHash)
|
|
63
|
-
|| !Number.isSafeInteger(value.startRevision) || value.startRevision < 1
|
|
64
|
-
|| !nullableSafeInteger(value.stopRevision)
|
|
65
|
-
|| !nullableText(value.consumer)
|
|
66
|
-
|| !nullableText(value.consumedAt))
|
|
67
|
-
return false;
|
|
68
|
-
if (value.kind === 'codex-lifecycle-v1')
|
|
69
|
-
return true;
|
|
70
|
-
return Number.isSafeInteger(value.protocolVersion)
|
|
71
|
-
&& value.protocolVersion > 0
|
|
72
|
-
&& exactText(value.packageVersion)
|
|
73
|
-
&& exactText(value.internalExecutableBuildHash)
|
|
74
|
-
&& exactText(value.lifecycleContractHash)
|
|
75
|
-
&& ['project', 'managed', 'isolated-user'].includes(value.hookSource)
|
|
76
|
-
&& exactText(value.hookSourcePathDigest)
|
|
77
|
-
&& exactText(value.hookSourceHash)
|
|
78
|
-
&& exactText(value.capabilitySessionId)
|
|
79
|
-
&& exactText(value.capabilityTurnId)
|
|
80
|
-
&& exactText(value.spawnToolUseId)
|
|
81
|
-
&& exactText(value.spawnObservedAt)
|
|
82
|
-
&& nullableText(value.controllerInstanceDigest)
|
|
83
|
-
&& nullableText(value.controlGeneration);
|
|
84
|
-
}
|
|
85
24
|
const RECEIPT_KEYS = [
|
|
86
25
|
'id', 'taskId', 'runId', 'role', 'stage', 'round', 'artifact', 'client',
|
|
87
26
|
'requestedModel', 'requestedReasoningEffort', 'actualModel', 'actualReasoningEffort',
|
|
88
27
|
'modelFallbackReason', 'reasoningEffortFallbackReason', 'parentId', 'childId',
|
|
89
|
-
'spawnMode', 'agent', 'status', 'workspaceSnapshotScope',
|
|
90
|
-
'
|
|
28
|
+
'spawnMode', 'agent', 'status', 'workspaceSnapshotScope',
|
|
29
|
+
'beforeFingerprint', 'afterFingerprint', 'changedPaths', 'createdAt',
|
|
91
30
|
'preparedMonotonicMs', 'spawnDispatchMonotonicMs', 'activationDeadlineMonotonicMs',
|
|
92
31
|
'spawnDispatchedAt', 'activationDeadlineAt', 'startEvidenceMonotonicMs',
|
|
93
32
|
'activatedMonotonicMs', 'activatedAt', 'sealedAt', 'consumedAt'
|
|
94
33
|
];
|
|
34
|
+
const RECEIPT_OPTIONAL_KEYS = ['adapterEvidence'];
|
|
35
|
+
function isJsonRecord(value) {
|
|
36
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
37
|
+
return false;
|
|
38
|
+
try {
|
|
39
|
+
return JSON.parse(JSON.stringify(value)) !== null;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function withClientEvidence(receipt, evidence) {
|
|
46
|
+
if (evidence === undefined)
|
|
47
|
+
return receipt.adapterEvidence ?? Object.freeze({});
|
|
48
|
+
if (!isJsonRecord(evidence))
|
|
49
|
+
return null;
|
|
50
|
+
return Object.freeze({
|
|
51
|
+
...(receipt.adapterEvidence ?? {}),
|
|
52
|
+
[receipt.client]: JSON.parse(JSON.stringify(evidence))
|
|
53
|
+
});
|
|
54
|
+
}
|
|
95
55
|
function hasStatusBoundEvidence(receipt) {
|
|
96
56
|
const dispatchFields = [
|
|
97
57
|
receipt.spawnDispatchMonotonicMs,
|
|
@@ -114,7 +74,6 @@ function hasStatusBoundEvidence(receipt) {
|
|
|
114
74
|
&& receipt.modelFallbackReason === null
|
|
115
75
|
&& receipt.reasoningEffortFallbackReason === null
|
|
116
76
|
&& receipt.agent === null
|
|
117
|
-
&& receipt.hostEvidence === null
|
|
118
77
|
&& receipt.startEvidenceMonotonicMs === null
|
|
119
78
|
&& receipt.activatedMonotonicMs === null
|
|
120
79
|
&& receipt.activatedAt === null
|
|
@@ -139,18 +98,12 @@ function hasStatusBoundEvidence(receipt) {
|
|
|
139
98
|
&& receipt.consumedAt === null;
|
|
140
99
|
}
|
|
141
100
|
if (receipt.status === 'aborted') {
|
|
142
|
-
const host = receipt.hostEvidence;
|
|
143
101
|
return receipt.agent === null
|
|
144
102
|
&& receipt.afterFingerprint === null
|
|
145
103
|
&& receipt.changedPaths.length === 0
|
|
146
104
|
&& receipt.sealedAt === null
|
|
147
105
|
&& receipt.consumedAt === null
|
|
148
|
-
&&
|
|
149
|
-
&& host !== undefined
|
|
150
|
-
&& Number.isSafeInteger(host.stopRevision)
|
|
151
|
-
&& host.stopRevision > host.startRevision
|
|
152
|
-
&& host.consumer === receipt.id
|
|
153
|
-
&& exactText(host.consumedAt);
|
|
106
|
+
&& receipt.childId !== null;
|
|
154
107
|
}
|
|
155
108
|
if (!exactText(receipt.agent)
|
|
156
109
|
|| normalizeAgentToken(receipt.agent) !== normalizeAgentToken(receipt.client))
|
|
@@ -168,7 +121,7 @@ function hasStatusBoundEvidence(receipt) {
|
|
|
168
121
|
: receipt.status === 'consumed' && exactText(receipt.consumedAt);
|
|
169
122
|
}
|
|
170
123
|
function isDelegationReceipt(value) {
|
|
171
|
-
if (!hasExactKeys(value, RECEIPT_KEYS))
|
|
124
|
+
if (!hasExactKeys(value, RECEIPT_KEYS, RECEIPT_OPTIONAL_KEYS))
|
|
172
125
|
return false;
|
|
173
126
|
const structurallyValid = exactText(value.id)
|
|
174
127
|
&& exactText(value.taskId)
|
|
@@ -191,8 +144,7 @@ function isDelegationReceipt(value) {
|
|
|
191
144
|
&& nullableText(value.agent)
|
|
192
145
|
&& ['prepared', 'activated', 'stage-completed', 'sealed', 'consumed', 'aborted', 'expired'].includes(value.status)
|
|
193
146
|
&& value.workspaceSnapshotScope === 'task'
|
|
194
|
-
&& (value.
|
|
195
|
-
&& (value.hostEvidence === null || isDelegationHostEvidence(value.hostEvidence))
|
|
147
|
+
&& (value.adapterEvidence === undefined || isJsonRecord(value.adapterEvidence))
|
|
196
148
|
&& exactText(value.beforeFingerprint)
|
|
197
149
|
&& nullableText(value.afterFingerprint)
|
|
198
150
|
&& Array.isArray(value.changedPaths)
|
|
@@ -214,14 +166,6 @@ function isDelegationReceipt(value) {
|
|
|
214
166
|
const receipt = value;
|
|
215
167
|
if (!hasStatusBoundEvidence(receipt))
|
|
216
168
|
return false;
|
|
217
|
-
const host = receipt.hostEvidence;
|
|
218
|
-
if (receipt.client === 'codex' && receipt.activatedAt !== null) {
|
|
219
|
-
if (host?.kind !== 'codex-lifecycle-v2' || host.capabilitySessionId !== receipt.parentId)
|
|
220
|
-
return false;
|
|
221
|
-
if (['sealed', 'consumed'].includes(receipt.status)) {
|
|
222
|
-
return Number.isSafeInteger(host.stopRevision) && host.stopRevision > host.startRevision;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
169
|
return true;
|
|
226
170
|
}
|
|
227
171
|
const MANAGED_AGENTS = {
|
|
@@ -238,10 +182,35 @@ function fail(code, message) {
|
|
|
238
182
|
function foldBlankToNull(value) {
|
|
239
183
|
return value?.trim() ? value : null;
|
|
240
184
|
}
|
|
185
|
+
function validateSealRequest(receipt, event) {
|
|
186
|
+
if (receipt.status !== 'stage-completed') {
|
|
187
|
+
return fail('DELEGATION_STATE_INVALID', `delegation ${receipt.id} is ${receipt.status}, expected stage-completed`);
|
|
188
|
+
}
|
|
189
|
+
if (event.childId !== receipt.childId || event.exitCode !== 0) {
|
|
190
|
+
return fail('DELEGATION_STOP_INVALID', 'native stop identity or exit status is invalid');
|
|
191
|
+
}
|
|
192
|
+
const reviewerPathFailure = validateReviewerSealPaths(receipt, event.changedPaths);
|
|
193
|
+
return reviewerPathFailure;
|
|
194
|
+
}
|
|
195
|
+
function validateReviewerSealPaths(receipt, changedPaths) {
|
|
196
|
+
if (receipt.role !== 'reviewer')
|
|
197
|
+
return null;
|
|
198
|
+
const taskRoot = `.agents/workspace/active/${receipt.taskId}/`;
|
|
199
|
+
const allowed = new Set([
|
|
200
|
+
`${taskRoot}${receipt.artifact}`,
|
|
201
|
+
`${taskRoot}task.md`,
|
|
202
|
+
`${taskRoot}.runtime/orchestration.json`
|
|
203
|
+
]);
|
|
204
|
+
const disallowed = changedPaths.find((entry) => !allowed.has(entry));
|
|
205
|
+
return disallowed
|
|
206
|
+
? fail('DELEGATION_REVIEWER_WRITE_FORBIDDEN', `reviewer changed forbidden path '${disallowed}'`)
|
|
207
|
+
: null;
|
|
208
|
+
}
|
|
241
209
|
function prepareDelegation(input, options = {}) {
|
|
242
210
|
const monotonic = (options.monotonicNow ?? (() => Number(process.hrtime.bigint() / 1000000n)))();
|
|
243
211
|
return Object.freeze({
|
|
244
212
|
...input,
|
|
213
|
+
adapterEvidence: Object.freeze({ ...(input.adapterEvidence ?? {}) }),
|
|
245
214
|
id: (options.id ?? randomUUID)(),
|
|
246
215
|
actualModel: null,
|
|
247
216
|
actualReasoningEffort: null,
|
|
@@ -252,7 +221,6 @@ function prepareDelegation(input, options = {}) {
|
|
|
252
221
|
spawnMode: null,
|
|
253
222
|
agent: null,
|
|
254
223
|
status: 'prepared',
|
|
255
|
-
hostEvidence: null,
|
|
256
224
|
afterFingerprint: null,
|
|
257
225
|
changedPaths: Object.freeze([]),
|
|
258
226
|
createdAt: (options.now ?? (() => new Date().toISOString()))(),
|
|
@@ -310,33 +278,29 @@ function activateDelegation(receipt, event, options = {}) {
|
|
|
310
278
|
if (!event.parentId || (receipt.parentId !== null && event.parentId !== receipt.parentId) || !event.childId || event.childId === event.parentId) {
|
|
311
279
|
return fail('DELEGATION_IDENTITY_INVALID', 'native parent/child identity does not match the prepared delegation');
|
|
312
280
|
}
|
|
313
|
-
|
|
314
|
-
if (
|
|
281
|
+
const evidencePolicy = options.evidencePolicy ?? {};
|
|
282
|
+
if (evidencePolicy.spawnModeRequired !== false && event.spawnMode !== 'fresh') {
|
|
315
283
|
return fail('DELEGATION_FORK_FORBIDDEN', `spawn mode '${event.spawnMode}' is not fresh`);
|
|
316
284
|
}
|
|
317
|
-
// model/effort 维度:claude-code 如实记录、不做门禁;其余 client 维持现状硬校验(HDR-2)
|
|
318
285
|
const actualModel = foldBlankToNull(event.actualModel);
|
|
319
286
|
const actualReasoningEffort = foldBlankToNull(event.actualReasoningEffort);
|
|
320
|
-
if (
|
|
287
|
+
if (evidencePolicy.actualModelRequired !== false) {
|
|
321
288
|
if (!actualModel || actualModel.trim() !== actualModel) {
|
|
322
289
|
return fail('DELEGATION_MODEL_IDENTITY_MISSING', 'native start event must provide a non-empty actual model identity');
|
|
323
290
|
}
|
|
324
291
|
}
|
|
325
292
|
if (actualModel !== null && actualModel !== receipt.requestedModel) {
|
|
326
|
-
if (
|
|
293
|
+
if (evidencePolicy.fallbackReasonRequired !== false && (!event.modelFallbackReason || event.modelFallbackReason.trim() === '')) {
|
|
327
294
|
return fail('DELEGATION_MODEL_FALLBACK_UNRECORDED', 'actual model differs from requested model without a fallback reason');
|
|
328
295
|
}
|
|
329
296
|
}
|
|
330
|
-
if (
|
|
297
|
+
if (evidencePolicy.actualReasoningEffortRequired !== false) {
|
|
331
298
|
if (!actualReasoningEffort || actualReasoningEffort.trim() !== actualReasoningEffort) {
|
|
332
299
|
return fail('DELEGATION_REASONING_EFFORT_MISSING', 'native start event must provide a non-empty actual reasoning effort');
|
|
333
300
|
}
|
|
334
301
|
}
|
|
335
|
-
if (receipt.client === 'codex'
|
|
336
|
-
&& event.hostEvidence?.kind !== 'codex-lifecycle-v2')
|
|
337
|
-
return fail('DELEGATION_HOST_EVIDENCE_REQUIRED', 'Codex activation requires trusted lifecycle-v2 host evidence');
|
|
338
302
|
if (actualReasoningEffort !== null && actualReasoningEffort !== receipt.requestedReasoningEffort) {
|
|
339
|
-
if (
|
|
303
|
+
if (evidencePolicy.fallbackReasonRequired !== false && (!event.reasoningEffortFallbackReason || event.reasoningEffortFallbackReason.trim() === '')) {
|
|
340
304
|
return fail('DELEGATION_REASONING_EFFORT_FALLBACK_UNRECORDED', 'actual reasoning effort differs from requested effort without a fallback reason');
|
|
341
305
|
}
|
|
342
306
|
}
|
|
@@ -349,16 +313,9 @@ function activateDelegation(receipt, event, options = {}) {
|
|
|
349
313
|
&& event.reasoningEffortFallbackReason) {
|
|
350
314
|
return fail('DELEGATION_REASONING_EFFORT_FALLBACK_INVALID', 'reasoning-effort fallback reason is only valid when actual effort differs');
|
|
351
315
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|| !Number.isSafeInteger(event.hostEvidence.startRevision)
|
|
356
|
-
|| event.hostEvidence.startRevision < 1))
|
|
357
|
-
return fail('DELEGATION_HOST_EVIDENCE_INVALID', 'Codex start evidence reference is invalid');
|
|
358
|
-
if (event.hostEvidence?.kind === 'codex-lifecycle-v2'
|
|
359
|
-
&& event.hostEvidence.capabilitySessionId !== event.parentId) {
|
|
360
|
-
return fail('DELEGATION_HOST_EVIDENCE_INVALID', 'Codex start evidence does not match the observed parent');
|
|
361
|
-
}
|
|
316
|
+
const adapterEvidence = withClientEvidence(receipt, event.clientEvidence);
|
|
317
|
+
if (!adapterEvidence)
|
|
318
|
+
return fail('DELEGATION_ADAPTER_EVIDENCE_INVALID', 'adapter evidence must be a JSON object');
|
|
362
319
|
return { ok: true, receipt: Object.freeze({
|
|
363
320
|
...receipt,
|
|
364
321
|
status: 'activated',
|
|
@@ -369,12 +326,7 @@ function activateDelegation(receipt, event, options = {}) {
|
|
|
369
326
|
actualReasoningEffort,
|
|
370
327
|
modelFallbackReason: event.modelFallbackReason ?? null,
|
|
371
328
|
reasoningEffortFallbackReason: event.reasoningEffortFallbackReason ?? null,
|
|
372
|
-
|
|
373
|
-
...event.hostEvidence,
|
|
374
|
-
stopRevision: null,
|
|
375
|
-
consumer: null,
|
|
376
|
-
consumedAt: null
|
|
377
|
-
}) : receipt.hostEvidence ?? null,
|
|
329
|
+
adapterEvidence,
|
|
378
330
|
startEvidenceMonotonicMs: monotonic,
|
|
379
331
|
activatedMonotonicMs: (options.monotonicNow ?? (() => Number(process.hrtime.bigint() / 1000000n)))(),
|
|
380
332
|
activatedAt: new Date(wallNow).toISOString()
|
|
@@ -393,22 +345,11 @@ function abortActivatedDelegation(receipt, event) {
|
|
|
393
345
|
if (receipt.status !== 'activated') {
|
|
394
346
|
return fail('DELEGATION_STATE_INVALID', `delegation ${receipt.id} is ${receipt.status}, expected activated`);
|
|
395
347
|
}
|
|
396
|
-
if (
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|| typeof receipt.hostEvidence.controllerInstanceDigest !== 'string'
|
|
402
|
-
|| typeof receipt.hostEvidence.controlGeneration !== 'string') {
|
|
403
|
-
return fail('DELEGATION_RECOVERY_EVIDENCE_INVALID', 'activated delegation lacks the current Codex controller binding');
|
|
404
|
-
}
|
|
405
|
-
if (event.childId !== receipt.childId
|
|
406
|
-
|| !Number.isSafeInteger(event.stopRevision)
|
|
407
|
-
|| event.stopRevision <= receipt.hostEvidence.startRevision
|
|
408
|
-
|| event.consumer !== receipt.id
|
|
409
|
-
|| !exactText(event.consumedAt)) {
|
|
410
|
-
return fail('DELEGATION_RECOVERY_EVIDENCE_INVALID', 'recovery stop evidence does not match the activated delegation');
|
|
411
|
-
}
|
|
348
|
+
if (event.childId !== receipt.childId)
|
|
349
|
+
return fail('DELEGATION_RECOVERY_EVIDENCE_INVALID', 'recovery child does not match the activated delegation');
|
|
350
|
+
const adapterEvidence = withClientEvidence(receipt, event.clientEvidence);
|
|
351
|
+
if (!adapterEvidence)
|
|
352
|
+
return fail('DELEGATION_ADAPTER_EVIDENCE_INVALID', 'adapter evidence must be a JSON object');
|
|
412
353
|
return {
|
|
413
354
|
ok: true,
|
|
414
355
|
receipt: Object.freeze({
|
|
@@ -419,12 +360,7 @@ function abortActivatedDelegation(receipt, event) {
|
|
|
419
360
|
changedPaths: Object.freeze([]),
|
|
420
361
|
sealedAt: null,
|
|
421
362
|
consumedAt: null,
|
|
422
|
-
|
|
423
|
-
...receipt.hostEvidence,
|
|
424
|
-
stopRevision: event.stopRevision,
|
|
425
|
-
consumer: event.consumer,
|
|
426
|
-
consumedAt: event.consumedAt
|
|
427
|
-
})
|
|
363
|
+
adapterEvidence
|
|
428
364
|
})
|
|
429
365
|
};
|
|
430
366
|
}
|
|
@@ -440,32 +376,12 @@ function completeDelegationStage(receipt, event) {
|
|
|
440
376
|
return { ok: true, receipt: Object.freeze({ ...receipt, status: 'stage-completed', agent: event.agent }) };
|
|
441
377
|
}
|
|
442
378
|
function sealDelegation(receipt, event, options = {}) {
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
return fail('DELEGATION_STOP_INVALID', 'native stop identity or exit status is invalid');
|
|
450
|
-
if (receipt.role === 'reviewer') {
|
|
451
|
-
const taskRoot = `.agents/workspace/active/${receipt.taskId}/`;
|
|
452
|
-
const allowed = new Set([
|
|
453
|
-
`${taskRoot}${receipt.artifact}`,
|
|
454
|
-
`${taskRoot}task.md`,
|
|
455
|
-
`${taskRoot}orchestration.json`
|
|
456
|
-
]);
|
|
457
|
-
const disallowed = event.changedPaths.find((entry) => !allowed.has(entry));
|
|
458
|
-
if (disallowed)
|
|
459
|
-
return fail('DELEGATION_REVIEWER_WRITE_FORBIDDEN', `reviewer changed forbidden path '${disallowed}'`);
|
|
460
|
-
}
|
|
461
|
-
if (event.hostEvidence) {
|
|
462
|
-
if (!receipt.hostEvidence
|
|
463
|
-
|| !Number.isSafeInteger(event.hostEvidence.stopRevision)
|
|
464
|
-
|| event.hostEvidence.stopRevision <= receipt.hostEvidence.startRevision
|
|
465
|
-
|| event.hostEvidence.consumer !== receipt.id
|
|
466
|
-
|| !event.hostEvidence.consumedAt.trim())
|
|
467
|
-
return fail('DELEGATION_HOST_EVIDENCE_INVALID', 'Codex stop evidence reference is invalid');
|
|
468
|
-
}
|
|
379
|
+
const validationFailure = validateSealRequest(receipt, event);
|
|
380
|
+
if (validationFailure)
|
|
381
|
+
return validationFailure;
|
|
382
|
+
const adapterEvidence = withClientEvidence(receipt, event.clientEvidence);
|
|
383
|
+
if (!adapterEvidence)
|
|
384
|
+
return fail('DELEGATION_ADAPTER_EVIDENCE_INVALID', 'adapter evidence must be a JSON object');
|
|
469
385
|
return { ok: true, receipt: Object.freeze({
|
|
470
386
|
...receipt,
|
|
471
387
|
status: 'sealed',
|
|
@@ -475,10 +391,7 @@ function sealDelegation(receipt, event, options = {}) {
|
|
|
475
391
|
actualReasoningEffort: receipt.actualReasoningEffort ?? foldBlankToNull(event.actualReasoningEffort),
|
|
476
392
|
modelFallbackReason: receipt.modelFallbackReason ?? event.modelFallbackReason ?? null,
|
|
477
393
|
reasoningEffortFallbackReason: receipt.reasoningEffortFallbackReason ?? event.reasoningEffortFallbackReason ?? null,
|
|
478
|
-
|
|
479
|
-
...receipt.hostEvidence,
|
|
480
|
-
...event.hostEvidence
|
|
481
|
-
}) : receipt.hostEvidence ?? null,
|
|
394
|
+
adapterEvidence,
|
|
482
395
|
sealedAt: (options.now ?? (() => new Date().toISOString()))()
|
|
483
396
|
}) };
|
|
484
397
|
}
|
package/dist/lib/task/events.js
CHANGED
|
@@ -25,7 +25,6 @@ import { getArtifactSchema } from './artifact-schema.js';
|
|
|
25
25
|
import { canonicalSemanticDigest, inspectArtifactContract } from './artifact-operations.js';
|
|
26
26
|
import { inspectReviewIdentity } from './review-identity.js';
|
|
27
27
|
import { parseCompletionFacts } from './completion-facts.js';
|
|
28
|
-
import { consumeLifecycleRecoveryAttestation, lifecycleRecoveryAttestationDigest, validateLifecycleRecoveryAttestation } from './control-authority.js';
|
|
29
28
|
import { readManualValidationCompletion } from './manual-validation-completion.js';
|
|
30
29
|
import { parseLifecyclePathDecision } from './lifecycle-path.js';
|
|
31
30
|
const eventCatalog = [
|
|
@@ -349,6 +348,65 @@ function openStartedIdentity(rows, family) {
|
|
|
349
348
|
});
|
|
350
349
|
return matches.length === 1 ? matches[0] : matches.length > 1 ? { conflict: true } : null;
|
|
351
350
|
}
|
|
351
|
+
function repairMissingCodeFixQualifier(request, family, open, repoRoot) {
|
|
352
|
+
if (family !== 'code' || open.fixFor !== undefined || !request.fixFor || request.implementationInput !== undefined) {
|
|
353
|
+
return {
|
|
354
|
+
error: {
|
|
355
|
+
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
356
|
+
message: `open code start cannot be repaired (family=${family}, openFixFor=${open.fixFor ?? 'none'}, requestedFixFor=${request.fixFor ?? 'none'}, implementationInput=${request.implementationInput ?? 'none'})`
|
|
357
|
+
},
|
|
358
|
+
context: null
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
const repair = normalizeStarted(request, repoRoot);
|
|
362
|
+
if ('error' in repair)
|
|
363
|
+
return { error: repair.error, context: repair.context };
|
|
364
|
+
if (repair.request.round !== open.round || repair.request.fixFor !== request.fixFor || repair.request.implementationInput !== undefined) {
|
|
365
|
+
return { error: { code: 'EVENT_ARTIFACT_CONFLICT', message: 'current code artifact context does not match the open round and requested fix input' }, context: repair.context };
|
|
366
|
+
}
|
|
367
|
+
return repair;
|
|
368
|
+
}
|
|
369
|
+
function resolveOpenStartedEvent(request, family, open, repoRoot) {
|
|
370
|
+
if (open.row.agent !== request.agent)
|
|
371
|
+
return { status: 'error', error: { code: 'EVENT_LOG_CONFLICT', message: 'open started event has a different agent' } };
|
|
372
|
+
if (family === 'manual-validation' && request.transactionId !== undefined) {
|
|
373
|
+
const existingTransactionId = /(?:^|;\s*)transaction=([A-Za-z0-9][A-Za-z0-9._-]{0,127})(?:;|$)/u.exec(open.row.note)?.[1];
|
|
374
|
+
if (existingTransactionId !== undefined && existingTransactionId !== request.transactionId) {
|
|
375
|
+
return { status: 'error', error: { code: 'EVENT_LOG_CONFLICT', message: 'open manual-validation started event has a different transactionId' } };
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
if (request.round !== undefined && request.round !== open.round) {
|
|
379
|
+
return { status: 'error', error: { code: 'EVENT_ARTIFACT_CONFLICT', message: `round ${request.round} conflicts with open round ${open.round}` } };
|
|
380
|
+
}
|
|
381
|
+
if (request.fixFor !== undefined && request.fixFor !== open.fixFor) {
|
|
382
|
+
const repair = repairMissingCodeFixQualifier(request, family, open, repoRoot);
|
|
383
|
+
if (!repair)
|
|
384
|
+
return { status: 'error', error: { code: 'EVENT_ARTIFACT_CONFLICT', message: `fixFor '${request.fixFor}' conflicts with open event` } };
|
|
385
|
+
if ('error' in repair)
|
|
386
|
+
return { status: 'error', error: repair.error, context: repair.context };
|
|
387
|
+
return { status: 'repair', repair: repair.request, context: repair.context, superseded: { action: open.row.step } };
|
|
388
|
+
}
|
|
389
|
+
if (request.implementationInput !== open.implementationInput) {
|
|
390
|
+
return { status: 'error', error: { code: 'EVENT_ARTIFACT_CONFLICT', message: `implementationInput '${request.implementationInput ?? ''}' conflicts with open event` } };
|
|
391
|
+
}
|
|
392
|
+
return {
|
|
393
|
+
status: 'replay',
|
|
394
|
+
replay: {
|
|
395
|
+
...request,
|
|
396
|
+
round: open.round,
|
|
397
|
+
artifact: artifactName(FAMILY[family].artifact, open.round),
|
|
398
|
+
fixFor: open.fixFor,
|
|
399
|
+
implementationInput: open.implementationInput
|
|
400
|
+
},
|
|
401
|
+
startedAt: open.row.started
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
function hasOtherOpenStarted(rows, allowedAction) {
|
|
405
|
+
return rows.some((row) => row.started && !row.done && row.step !== allowedAction);
|
|
406
|
+
}
|
|
407
|
+
function hasConflictingOpenStarted(rows, recovery) {
|
|
408
|
+
return recovery ? hasOtherOpenStarted(rows, recovery.action) : rows.some((row) => row.started && !row.done);
|
|
409
|
+
}
|
|
352
410
|
function reviewInputFamily(family) {
|
|
353
411
|
return family === 'review-analysis' ? 'analysis' : family === 'review-plan' ? 'plan' : 'code';
|
|
354
412
|
}
|
|
@@ -538,38 +596,34 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
538
596
|
const rows = startedBackedRows(pairEntries(section.entries));
|
|
539
597
|
let normalized = request;
|
|
540
598
|
let artifactContext = null;
|
|
599
|
+
let supersededOpenStart = null;
|
|
541
600
|
if (initialParts.phase === 'started') {
|
|
542
601
|
const openIdentity = openStartedIdentity(rows, initialParts.family);
|
|
543
602
|
if (openIdentity && 'conflict' in openIdentity)
|
|
544
603
|
return failed(request, { code: 'EVENT_LOG_CONFLICT', message: 'artifact family has more than one open started event' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
|
|
545
604
|
if (openIdentity) {
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
return failed(request, { code: 'EVENT_ARTIFACT_CONFLICT', message: `fixFor '${request.fixFor}' conflicts with open event` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
|
|
557
|
-
if (request.implementationInput !== openIdentity.implementationInput)
|
|
558
|
-
return failed(request, { code: 'EVENT_ARTIFACT_CONFLICT', message: `implementationInput '${request.implementationInput ?? ''}' conflicts with open event` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
|
|
559
|
-
normalized = { ...request, round: openIdentity.round, artifact: artifactName(FAMILY[initialParts.family].artifact, openIdentity.round), fixFor: openIdentity.fixFor, implementationInput: openIdentity.implementationInput };
|
|
560
|
-
return successNoOp(normalized, resolved.taskId, resolved.taskMdPath, typeof frontmatter.current_step === 'string' ? frontmatter.current_step : '', identity(normalized), openIdentity.row.started, frontmatter, null);
|
|
561
|
-
}
|
|
562
|
-
if (rows.some((item) => item.started && !item.done)) {
|
|
605
|
+
const resolvedStart = resolveOpenStartedEvent(request, initialParts.family, openIdentity, resolved.repoRoot);
|
|
606
|
+
if (resolvedStart.status === 'error')
|
|
607
|
+
return failed(request, resolvedStart.error, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
|
|
608
|
+
if (resolvedStart.status === 'replay')
|
|
609
|
+
return successNoOp(resolvedStart.replay, resolved.taskId, resolved.taskMdPath, typeof frontmatter.current_step === 'string' ? frontmatter.current_step : '', identity(resolvedStart.replay), resolvedStart.startedAt, frontmatter, null);
|
|
610
|
+
normalized = resolvedStart.repair;
|
|
611
|
+
artifactContext = resolvedStart.context;
|
|
612
|
+
supersededOpenStart = resolvedStart.superseded;
|
|
613
|
+
}
|
|
614
|
+
if (hasConflictingOpenStarted(rows, supersededOpenStart)) {
|
|
563
615
|
return failed(request, {
|
|
564
616
|
code: 'EVENT_TRANSITION_INVALID',
|
|
565
617
|
message: 'EXECUTION_BUSY: another lifecycle execution is open'
|
|
566
618
|
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
|
|
567
619
|
}
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
620
|
+
if (!supersededOpenStart) {
|
|
621
|
+
const result = normalizeStarted(request, resolved.repoRoot);
|
|
622
|
+
artifactContext = result.context;
|
|
623
|
+
if ('error' in result)
|
|
624
|
+
return failed(request, result.error, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, artifactContext });
|
|
625
|
+
normalized = result.request;
|
|
626
|
+
}
|
|
573
627
|
}
|
|
574
628
|
else if (initialParts.phase === 'completed') {
|
|
575
629
|
const identity = request.artifact ? parseArtifactName(request.artifact) : null;
|
|
@@ -577,25 +631,6 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
577
631
|
normalized = { ...request, round: identity.round };
|
|
578
632
|
}
|
|
579
633
|
const eventIdentity = identity(normalized);
|
|
580
|
-
const lifecycleAuthority = options.lifecycleRecoveryAttestation ?? null;
|
|
581
|
-
if (lifecycleAuthority) {
|
|
582
|
-
try {
|
|
583
|
-
validateLifecycleRecoveryAttestation(lifecycleAuthority);
|
|
584
|
-
}
|
|
585
|
-
catch (error) {
|
|
586
|
-
return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: error instanceof Error ? error.message : String(error) }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
|
|
587
|
-
}
|
|
588
|
-
const expectedFamily = eventIdentity.family === 'analyze' ? 'analysis' : eventIdentity.family;
|
|
589
|
-
if (eventIdentity.phase !== 'completed'
|
|
590
|
-
|| lifecycleAuthority.phase !== 'task-event.completed'
|
|
591
|
-
|| lifecycleAuthority.taskId !== resolved.taskId
|
|
592
|
-
|| lifecycleAuthority.family !== expectedFamily
|
|
593
|
-
|| lifecycleAuthority.artifact !== normalized.artifact
|
|
594
|
-
|| lifecycleAuthority.round !== normalized.round
|
|
595
|
-
|| (normalized.requestId !== undefined && lifecycleAuthority.lifecycleRequestId !== normalized.requestId)) {
|
|
596
|
-
return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: 'lifecycle completion authority does not match the event tuple' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
634
|
const currentStep = typeof frontmatter.current_step === 'string' ? frontmatter.current_step : '';
|
|
600
635
|
const matchingRows = rows.filter((item) => item.step === eventIdentity.action);
|
|
601
636
|
const manual = eventIdentity.family === 'manual-validation';
|
|
@@ -689,7 +724,8 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
689
724
|
const capability = canStart(lifecycleAction(eventIdentity.family), facts.facts, trigger);
|
|
690
725
|
const safetyFailure = capability.reasonCode === 'TASK_NOT_ACTIVE'
|
|
691
726
|
|| capability.reasonCode === 'LIFECYCLE_EXECUTION_OPEN';
|
|
692
|
-
if (!capability.allowed && (normalized.initiator === 'orchestrator' || safetyFailure)
|
|
727
|
+
if (!capability.allowed && (normalized.initiator === 'orchestrator' || safetyFailure)
|
|
728
|
+
&& !(supersededOpenStart && capability.reasonCode === 'LIFECYCLE_EXECUTION_OPEN')) {
|
|
693
729
|
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 });
|
|
694
730
|
}
|
|
695
731
|
}
|
|
@@ -759,7 +795,15 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
759
795
|
}
|
|
760
796
|
const step = eventIdentity.phase === 'started' || eventIdentity.target === null ? currentStep : eventIdentity.target;
|
|
761
797
|
const logStep = eventIdentity.phase === 'started' ? `${eventIdentity.action} [started]` : eventIdentity.action;
|
|
762
|
-
const
|
|
798
|
+
const supersededBody = supersededOpenStart
|
|
799
|
+
? appendActivityEntry(section, {
|
|
800
|
+
time: metadata.timestamp,
|
|
801
|
+
step: `${supersededOpenStart.action} [aborted]`,
|
|
802
|
+
agent: normalized.agent,
|
|
803
|
+
note: `superseded by ${eventIdentity.action}`
|
|
804
|
+
})
|
|
805
|
+
: section.body;
|
|
806
|
+
const body = appendActivityEntry(supersededOpenStart ? { ...section, body: supersededBody } : section, { time: metadata.timestamp, step: logStep, agent: normalized.agent, note: eventIdentity.note });
|
|
763
807
|
const frontmatterSet = { current_step: step, assigned_to: normalized.agent };
|
|
764
808
|
if (currentFact)
|
|
765
809
|
frontmatterSet.completion_facts = JSON.stringify(replaceCompletionFact(parseCompletionFacts(frontmatter.completion_facts), currentFact));
|
|
@@ -9,7 +9,7 @@ import { inspectPlatformIssue, syncPlatformIssue } from '../platform/issues.js';
|
|
|
9
9
|
import { resourceIdentityEquals } from '../platform/resource-identity.js';
|
|
10
10
|
import { parseTaskFrontmatter } from './frontmatter.js';
|
|
11
11
|
import { recoverPlatformOperations } from './platform-operation-recovery.js';
|
|
12
|
-
import { applyTaskLifecycle, inspectTaskLifecycleProgress, previewTaskCompletion } from './lifecycle.js';
|
|
12
|
+
import { applyTaskLifecycle, applyTaskLifecycleWithinTaskLock, inspectTaskLifecycleProgress, previewTaskCompletion } from './lifecycle.js';
|
|
13
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';
|
|
@@ -684,7 +684,7 @@ function preparedResult(taskId, receipt, steps, changed) {
|
|
|
684
684
|
}
|
|
685
685
|
async function prepareUnderLock(request, taskId, options) {
|
|
686
686
|
const repoRoot = path.resolve(options.repoRoot);
|
|
687
|
-
const lifecycle = options.lifecycle ??
|
|
687
|
+
const lifecycle = options.lifecycle ?? ((lifecycleRequest, lifecycleOptions) => (applyTaskLifecycleWithinTaskLock(lifecycleRequest, lifecycleOptions ?? { repoRoot }, taskId)));
|
|
688
688
|
const backfill = options.backfill ?? backfillCompletionComments;
|
|
689
689
|
const commentSync = options.commentSync ?? syncPlatformComment;
|
|
690
690
|
const issueSync = options.issueSync ?? syncPlatformIssue;
|
|
@@ -746,7 +746,9 @@ async function prepareUnderLock(request, taskId, options) {
|
|
|
746
746
|
const issueOperations = synced.operations;
|
|
747
747
|
const badOperation = issueOperations?.find((operation) => ['requirements', 'issue-type', 'fields'].includes(operation.name)
|
|
748
748
|
&& operation.status !== 'applied' && operation.status !== 'no-op'
|
|
749
|
-
&& !(operation.name === 'requirements' && operation.status === 'skipped' && operation.reasonCode === 'NO_REQUIREMENTS_ANCHOR')
|
|
749
|
+
&& !(operation.name === 'requirements' && operation.status === 'skipped' && operation.reasonCode === 'NO_REQUIREMENTS_ANCHOR')
|
|
750
|
+
&& !(!synced.capabilities.push && ['issue-type', 'fields'].includes(operation.name)
|
|
751
|
+
&& operation.status === 'skipped' && operation.reasonCode === 'PUSH_REQUIRED'));
|
|
750
752
|
if ((synced.status !== 'applied' && synced.status !== 'no-op') || synced.error || badOperation) {
|
|
751
753
|
const error = synced.error ?? {
|
|
752
754
|
code: badOperation?.reasonCode || 'FINALIZATION_ISSUE_METADATA_UNCONFIRMED',
|
|
@@ -755,6 +757,15 @@ async function prepareUnderLock(request, taskId, options) {
|
|
|
755
757
|
};
|
|
756
758
|
return failed(taskId, errorOf(error, 'FINALIZATION_ISSUE_METADATA_FAILED', true));
|
|
757
759
|
}
|
|
760
|
+
if (!synced.capabilities.push) {
|
|
761
|
+
const missingCapabilitySkip = ['issue-type', 'fields'].find((name) => !issueOperations?.some((operation) => operation.name === name && operation.status === 'skipped' && operation.reasonCode === 'PUSH_REQUIRED'));
|
|
762
|
+
if (missingCapabilitySkip)
|
|
763
|
+
return failed(taskId, {
|
|
764
|
+
code: 'FINALIZATION_ISSUE_METADATA_UNCONFIRMED',
|
|
765
|
+
message: `Issue metadata operation '${missingCapabilitySkip}' did not report the required PUSH_REQUIRED capability skip`,
|
|
766
|
+
retryable: true
|
|
767
|
+
});
|
|
768
|
+
}
|
|
758
769
|
let inspected;
|
|
759
770
|
try {
|
|
760
771
|
inspected = await issueInspect(taskId, { cwd: repoRoot });
|
|
@@ -770,7 +781,8 @@ async function prepareUnderLock(request, taskId, options) {
|
|
|
770
781
|
retryable: true
|
|
771
782
|
});
|
|
772
783
|
}
|
|
773
|
-
|
|
784
|
+
const rereadNames = synced.capabilities.push ? ['requirements', 'issue-type', 'fields'] : ['requirements'];
|
|
785
|
+
for (const name of rereadNames) {
|
|
774
786
|
const operation = issueOperations?.find((candidate) => candidate.name === name);
|
|
775
787
|
if (!operation)
|
|
776
788
|
return failed(taskId, {
|
|
@@ -1024,8 +1036,11 @@ async function commitPreparedTaskFinalization(request, options) {
|
|
|
1024
1036
|
retryable: false
|
|
1025
1037
|
});
|
|
1026
1038
|
receipt = updateReceipt(repoRoot, receipt, { verification: 'pending' });
|
|
1027
|
-
const
|
|
1028
|
-
const
|
|
1039
|
+
const lifecycleRequest = { taskRef: resolved.taskId, intent: 'complete', agent: request.agent };
|
|
1040
|
+
const lifecycleOptions = { repoRoot, ...(options.metadataProvider ? { metadataProvider: options.metadataProvider } : {}) };
|
|
1041
|
+
const result = options.lifecycle
|
|
1042
|
+
? options.lifecycle(lifecycleRequest, lifecycleOptions)
|
|
1043
|
+
: applyTaskLifecycleWithinTaskLock(lifecycleRequest, lifecycleOptions, resolved.taskId);
|
|
1029
1044
|
const step = lifecycleStep(result);
|
|
1030
1045
|
if (result.status !== 'applied' && result.status !== 'no-op') {
|
|
1031
1046
|
const updated = updateReceipt(repoRoot, receipt, { lifecycle: 'pending', lastError: step.error });
|
|
@@ -1118,6 +1133,20 @@ async function applyTaskFinalization(request, options) {
|
|
|
1118
1133
|
const prepared = await prepareTaskFinalization(request, options);
|
|
1119
1134
|
if (prepared.status !== 'prepared')
|
|
1120
1135
|
return prepared;
|
|
1136
|
+
if (options.beforeLifecycle) {
|
|
1137
|
+
try {
|
|
1138
|
+
await options.beforeLifecycle(prepared.taskId);
|
|
1139
|
+
}
|
|
1140
|
+
catch (error) {
|
|
1141
|
+
const detail = errorOf(error, 'TASK_FINALIZATION_PRE_LIFECYCLE_FAILED', true);
|
|
1142
|
+
return failed(prepared.taskId, detail, {
|
|
1143
|
+
changed: prepared.changed,
|
|
1144
|
+
completedSteps: prepared.completedSteps,
|
|
1145
|
+
pendingSteps: prepared.pendingSteps,
|
|
1146
|
+
warnings: prepared.warnings
|
|
1147
|
+
});
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1121
1150
|
return commitPreparedTaskFinalization(request, options);
|
|
1122
1151
|
}
|
|
1123
1152
|
export { applyFinalizationReceiptMutation, commitPreparedTaskFinalization, applyTaskFinalization, prepareTaskFinalization, issueCapability as createFinalizationCapability, readTaskFinalizationReceipt, terminalResult };
|