immune-brain 4.6.0 → 5.1.0
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/README.md +374 -46
- package/README.zh-CN.md +384 -47
- package/package.json +2 -1
- package/plugins/immune-brain/.claude-plugin/plugin.json +1 -1
- package/plugins/immune-brain/.pi-extension/imm-canary-enroll.ts +42 -44
- package/plugins/immune-brain/.pi-extension/imm-canary-work.ts +143 -474
- package/plugins/immune-brain/.pi-extension/imm-unattended-batch.ts +78 -87
- package/plugins/immune-brain/.pi-extension/pi-canary-assurance-progression.ts +7 -2
- package/plugins/immune-brain/.pi-extension/tsconfig.json +2 -2
- package/plugins/immune-brain/agents/immune-brain-advisory-reviewer.md +23 -0
- package/plugins/immune-brain/agents/immune-brain-qa.md +42 -0
- package/plugins/immune-brain/agents/immune-brain-reviewer.md +90 -1
- package/plugins/immune-brain/agents/immune-brain-ui-review.md +21 -0
- package/plugins/immune-brain/dist/BASELINE.md +4 -4
- package/plugins/immune-brain/dist/claude/mcp-server.mjs +8601 -6975
- package/plugins/immune-brain/dist/docs/reference/immune-brain-config.md +27 -0
- package/plugins/immune-brain/dist/docs/reference/planning-quality-gate.md +3 -3
- package/plugins/immune-brain/dist/docs/reference/subagent-dispatch-protocol.md +30 -1
- package/plugins/immune-brain/dist/imm-brainstorm.md +1 -1
- package/plugins/immune-brain/dist/imm-doc-prune.md +2 -2
- package/plugins/immune-brain/dist/{imm-agent-doc-maintain.md → imm-doc-slim.md} +9 -9
- package/plugins/immune-brain/dist/imm-planner.md +23 -8
- package/plugins/immune-brain/dist/imm-pr-fix.md +1 -1
- package/plugins/immune-brain/dist/{imm-review-retro.md → imm-retro.md} +26 -13
- package/plugins/immune-brain/dist/{imm-loop.md → imm-run.md} +231 -10
- package/plugins/immune-brain/dist/registry.yaml +9 -9
- package/plugins/immune-brain/dist/role-prompts/code-review.md +7 -0
- package/plugins/immune-brain/dist/role-prompts/executor.md +35 -2
- package/plugins/immune-brain/dist/role-prompts/lane-steward.md +44 -0
- package/plugins/immune-brain/runtime/assurance/coordinator.ts +83 -67
- package/plugins/immune-brain/runtime/assurance/host_port.ts +20 -0
- package/plugins/immune-brain/runtime/assurance/qa.ts +2 -2
- package/plugins/immune-brain/runtime/assurance/verdict_authority.ts +347 -0
- package/plugins/immune-brain/runtime/claude/interaction.ts +6 -0
- package/plugins/immune-brain/runtime/claude/kernel_ports.ts +78 -373
- package/plugins/immune-brain/runtime/claude/mcp_server.ts +26 -2
- package/plugins/immune-brain/runtime/claude/review_host.ts +48 -16
- package/plugins/immune-brain/runtime/claude/role_agents.ts +84 -0
- package/plugins/immune-brain/runtime/commands/kernel.ts +3 -6
- package/plugins/immune-brain/runtime/kernel/application.ts +6 -3
- package/plugins/immune-brain/runtime/kernel/assurance_projection.ts +15 -8
- package/plugins/immune-brain/runtime/kernel/canary_application.ts +5 -3
- package/plugins/immune-brain/runtime/kernel/enrollment.ts +149 -5
- package/plugins/immune-brain/runtime/kernel/index.ts +3 -1
- package/plugins/immune-brain/runtime/kernel/storage.ts +170 -11
- package/plugins/immune-brain/runtime/kernel/storage_paths.ts +13 -0
- package/plugins/immune-brain/runtime/local_initiative.ts +94 -0
- package/plugins/immune-brain/runtime/loop_contract.ts +77 -24
- package/plugins/immune-brain/runtime/plugin_version.ts +1 -1
- package/plugins/immune-brain/runtime/prompts/code-review.md +7 -0
- package/plugins/immune-brain/runtime/prompts/executor.md +35 -2
- package/plugins/immune-brain/runtime/prompts/lane-steward.md +44 -0
- package/plugins/immune-brain/runtime/role_prompt_bridge.ts +42 -2
- package/plugins/immune-brain/runtime/unattended/batch_git.ts +57 -27
- package/plugins/immune-brain/runtime/unattended/batch_integration.ts +387 -0
- package/plugins/immune-brain/runtime/unattended/batch_kernel_port.ts +106 -0
- package/plugins/immune-brain/runtime/unattended/batch_lanes.ts +909 -0
- package/plugins/immune-brain/runtime/unattended/batch_plan.ts +36 -11
- package/plugins/immune-brain/runtime/unattended/batch_preflight.ts +53 -36
- package/plugins/immune-brain/runtime/unattended/batch_reconfirmation.ts +34 -29
- package/plugins/immune-brain/runtime/unattended/batch_runner.ts +46 -91
- package/plugins/immune-brain/runtime/unattended/batch_schedule.ts +162 -0
- package/plugins/immune-brain/runtime/unattended/batch_state.ts +356 -8
- package/plugins/immune-brain/runtime/unattended/types.ts +21 -2
- package/plugins/immune-brain/runtime/workspace_scope.ts +34 -0
- package/plugins/immune-brain/skills/BASELINE.md +4 -4
- package/plugins/immune-brain/skills/imm-doc-prune/SKILL.md +1 -1
- package/plugins/immune-brain/skills/{imm-agent-doc-maintain → imm-doc-slim}/SKILL.md +9 -9
- package/plugins/immune-brain/skills/imm-planner/SKILL.md +4 -2
- package/plugins/immune-brain/skills/imm-pr-fix/SKILL.md +1 -1
- package/plugins/immune-brain/skills/{imm-review-retro → imm-retro}/SKILL.md +7 -7
- package/plugins/immune-brain/skills/{imm-review-retro → imm-retro}/scripts/review_retro.ts +46 -21
- package/plugins/immune-brain/skills/{imm-loop → imm-run}/SKILL.md +11 -7
- package/plugins/immune-brain/skills/registry.yaml +9 -9
- package/plugins/immune-brain/.pi-extension/runtime-stub.ts +0 -581
|
@@ -1,30 +1,23 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { stagePlanningArtifactTransition, type VerdictAuthority } from "../assurance/verdict_authority";
|
|
2
|
+
export { ensureReviewRevision as ensureClaudeReviewRevision } from "../assurance/verdict_authority";
|
|
2
3
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
4
|
import { execFileSync } from "node:child_process";
|
|
4
5
|
import { join } from "node:path";
|
|
5
6
|
import {
|
|
6
7
|
AssuranceCoordinator,
|
|
7
|
-
reviewAdvisoryRecords,
|
|
8
|
-
reviewReworkFindings,
|
|
9
8
|
type AssuranceCoordinatorPorts,
|
|
10
9
|
type AssuranceSubmitReviewResult,
|
|
11
|
-
type AssuranceVerdict,
|
|
12
10
|
type HostContext,
|
|
13
|
-
type SnapshotDescriptor,
|
|
14
11
|
} from "../assurance/coordinator";
|
|
15
12
|
import {
|
|
16
|
-
type VerificationDescriptor,
|
|
17
13
|
} from "../assurance/verification";
|
|
18
14
|
import {
|
|
19
|
-
captureReviewManifest,
|
|
20
15
|
writeNativeReviewEvidence,
|
|
21
|
-
type ReviewRevision,
|
|
22
16
|
} from "../assurance/review_evidence";
|
|
23
|
-
import { parseVerificationDescriptor } from "../verification_descriptor";
|
|
24
17
|
import { projectAssurance, type AssuranceProjection, type AssuranceProjectionResult } from "../kernel/assurance_projection";
|
|
25
|
-
import { type
|
|
18
|
+
import { type TaskFinding, type TaskRecord } from "../kernel/types";
|
|
26
19
|
import { findingsDigestV2 } from "../kernel/reducer";
|
|
27
|
-
import {
|
|
20
|
+
import { readTaskRecordRaw, recoverKernelStoreFollowUps } from "../kernel/storage";
|
|
28
21
|
import { canonicalIntentHash, parseTaskIntentV1, readTaskIntent } from "../kernel/intent";
|
|
29
22
|
import { capabilityActionFor, createCanaryApplication } from "../kernel/canary_application";
|
|
30
23
|
import {
|
|
@@ -37,12 +30,12 @@ import {
|
|
|
37
30
|
createEnrollmentAuthorityRegistry,
|
|
38
31
|
type EnrollmentCapabilityBinding,
|
|
39
32
|
} from "../kernel/enrollment_authority";
|
|
40
|
-
import {
|
|
33
|
+
import { enrollTask } from "../kernel/enrollment";
|
|
41
34
|
import { reconcileKernelAuthority, repairKernelAuthority } from "../kernel/storage";
|
|
42
35
|
import { inspectEnrollmentGitBase, enrollmentGitBaseNotice, initializeEnrollmentGitBase } from "../assurance/enrollment_git_base";
|
|
43
36
|
import { preparePiCanary, revalidatePiCanary } from "../kernel/pi_canary_prepare";
|
|
44
37
|
import { runDeterministicQa } from "../assurance/qa";
|
|
45
|
-
import {
|
|
38
|
+
import { taskDeliveryIdentity } from "../workspace_scope";
|
|
46
39
|
import { batchReason } from "../unattended/batch_reasons";
|
|
47
40
|
import { deriveAuthorizationOperation } from "../authorization_operation";
|
|
48
41
|
import { LITERAL_USER_ACTOR_ID, canonicalActorId } from "../kernel/actor_identity";
|
|
@@ -56,21 +49,17 @@ import { readTaskTombstone } from "../kernel/backend_claim";
|
|
|
56
49
|
import {
|
|
57
50
|
authorizeBatch,
|
|
58
51
|
projectBatchPreflight,
|
|
59
|
-
readActiveClaimTaskId,
|
|
60
|
-
isOwnBatchClaim,
|
|
61
52
|
findResumableBatchSlugForTask,
|
|
62
53
|
} from "../unattended/batch_preflight";
|
|
63
54
|
import {
|
|
64
55
|
startBatch,
|
|
65
|
-
batchQaFailureFacts,
|
|
66
56
|
type BatchRunnerKernelPort,
|
|
67
57
|
type BatchRunReport,
|
|
68
58
|
} from "../unattended/batch_runner";
|
|
59
|
+
import { createBatchKernelPort } from "../unattended/batch_kernel_port";
|
|
60
|
+
import type { LaneOffer } from "../unattended/batch_lanes";
|
|
69
61
|
import {
|
|
70
62
|
createBatchAuthorityRegistry,
|
|
71
|
-
deriveChildEnrollment,
|
|
72
|
-
type BatchAuthorityRegistry,
|
|
73
|
-
type BatchAuthorizationBinding,
|
|
74
63
|
} from "../kernel/batch_authority";
|
|
75
64
|
import {
|
|
76
65
|
type BatchRunnerGitPort,
|
|
@@ -78,7 +67,7 @@ import {
|
|
|
78
67
|
import type {
|
|
79
68
|
InitiativeObservationReader,
|
|
80
69
|
} from "../unattended/types";
|
|
81
|
-
import {
|
|
70
|
+
import { observeInitiative } from "../unattended/batch_plan";
|
|
82
71
|
import {
|
|
83
72
|
confirmationRef,
|
|
84
73
|
enrollmentNonce,
|
|
@@ -96,11 +85,7 @@ export function diffSnapshotOf(root: string, record: TaskRecord): {
|
|
|
96
85
|
diff_hash: string;
|
|
97
86
|
changed_paths: readonly string[];
|
|
98
87
|
} {
|
|
99
|
-
|
|
100
|
-
if (!record.git_base_head) throw new Error("TaskRecord v4 is missing git_base_head");
|
|
101
|
-
return taskRevisionIdentity(root, record.intent_snapshot.scope_hint, record.git_base_head, record.task_id);
|
|
102
|
-
}
|
|
103
|
-
return taskDiffIdentity(root, record.intent_snapshot.scope_hint, record.task_id);
|
|
88
|
+
return taskDeliveryIdentity(root, record);
|
|
104
89
|
}
|
|
105
90
|
|
|
106
91
|
export function diffHashOf(root: string, record: TaskRecord): string {
|
|
@@ -144,6 +129,21 @@ function verdictFingerprint(raw: Record<string, unknown>): string {
|
|
|
144
129
|
});
|
|
145
130
|
}
|
|
146
131
|
|
|
132
|
+
const RELEASED_REVIEW_RECOVERY =
|
|
133
|
+
"Call advance_assurance to obtain a new Review reservation, then dispatch one fresh reviewer with the returned envelope unchanged";
|
|
134
|
+
const RETAINED_REVIEW_RECOVERY =
|
|
135
|
+
"Wait for the dispatched reviewer to finish, then call submit_review again with its verdict; do not dispatch or continue another reviewer";
|
|
136
|
+
const MISMATCH_REVIEW_RECOVERY =
|
|
137
|
+
"Resubmit the reviewer's verdict exactly as the reviewer returned it";
|
|
138
|
+
|
|
139
|
+
function withReviewRecovery(
|
|
140
|
+
result: AssuranceSubmitReviewResult,
|
|
141
|
+
recovery_action: string,
|
|
142
|
+
): AssuranceSubmitReviewResult {
|
|
143
|
+
if (result.state !== "blocked" || result.code === "verdict_invalid") return result;
|
|
144
|
+
return { ...result, recovery_action };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
147
|
export async function submitClaudeReview(
|
|
148
148
|
host: ClaudeReviewHost,
|
|
149
149
|
coordinator: AssuranceCoordinator,
|
|
@@ -154,19 +154,19 @@ export async function submitClaudeReview(
|
|
|
154
154
|
if (verdictInput === undefined) throw new Error("verdict is required");
|
|
155
155
|
const observed = host.inspectReviewForTask(taskId);
|
|
156
156
|
if (!observed.ok) {
|
|
157
|
-
if (observed.release) return coordinator.abandonReview(taskId, observed.reason);
|
|
158
|
-
return { state: "blocked", reason: observed.reason };
|
|
157
|
+
if (observed.release) return withReviewRecovery(coordinator.abandonReview(taskId, observed.reason), RELEASED_REVIEW_RECOVERY);
|
|
158
|
+
return { state: "blocked", reason: observed.reason, recovery_action: RETAINED_REVIEW_RECOVERY };
|
|
159
159
|
}
|
|
160
160
|
const parentValid = coordinator.isReviewVerdictValid(taskId, verdictInput);
|
|
161
161
|
if (!parentValid) return coordinator.submitReview(taskId, ctx, verdictInput);
|
|
162
162
|
const receiptValid = coordinator.isReviewVerdictValid(taskId, observed.receipt.result);
|
|
163
163
|
if (!receiptValid) {
|
|
164
|
-
return coordinator.abandonReview(taskId, "reviewer receipt is not a valid verdict");
|
|
164
|
+
return withReviewRecovery(coordinator.abandonReview(taskId, "reviewer receipt is not a valid verdict"), RELEASED_REVIEW_RECOVERY);
|
|
165
165
|
}
|
|
166
166
|
const parentJson = extractVerdictJson(verdictInput)!;
|
|
167
167
|
const receiptJson = extractVerdictJson(observed.receipt.result)!;
|
|
168
168
|
if (verdictFingerprint(parentJson) !== verdictFingerprint(receiptJson)) {
|
|
169
|
-
return { state: "blocked", reason: "parent verdict does not match reviewer receipt" };
|
|
169
|
+
return { state: "blocked", reason: "parent verdict does not match reviewer receipt", recovery_action: MISMATCH_REVIEW_RECOVERY };
|
|
170
170
|
}
|
|
171
171
|
return coordinator.submitReview(taskId, ctx, verdictInput);
|
|
172
172
|
}
|
|
@@ -200,150 +200,6 @@ function assertProjectionBinding(before: AssuranceProjectionResult, after: Assur
|
|
|
200
200
|
}
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
-
function qaOutcomes(record: { attestations: Array<{ kind: string; acceptance_results: Array<{ acceptance_id: string; status: "passed" | "failed" | "blocked"; summary: string }> }> }) {
|
|
204
|
-
return Object.fromEntries(
|
|
205
|
-
record.attestations.filter((item) => item.kind === "qa").flatMap((item) => item.acceptance_results)
|
|
206
|
-
.map((result) => [result.acceptance_id, { status: result.status, summary: result.summary }]),
|
|
207
|
-
);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Publish the task-scoped synthetic revision for a v4 record and return the
|
|
212
|
-
* exact identity the Review snapshot binds.
|
|
213
|
-
*
|
|
214
|
-
* `submitReview` re-derives this identity and compares all four fields —
|
|
215
|
-
* `manifest_digest` included — against the reservation. Returning the bare
|
|
216
|
-
* commit identity therefore compared a real digest against `undefined` and
|
|
217
|
-
* failed every v4 submission with "Review revision changed before submission",
|
|
218
|
-
* so the manifest is recomputed here rather than only the commit. The outcomes
|
|
219
|
-
* come from the same `qaOutcomes` the Review snapshot is built from, which
|
|
220
|
-
* makes the two digests equal by construction instead of by coincidence.
|
|
221
|
-
*
|
|
222
|
-
* v3 records keep the legacy full-source bundle and return null.
|
|
223
|
-
*/
|
|
224
|
-
export async function ensureClaudeReviewRevision(
|
|
225
|
-
root: string,
|
|
226
|
-
taskId: string,
|
|
227
|
-
projection: AssuranceProjectionResult,
|
|
228
|
-
): Promise<ReviewRevision | null> {
|
|
229
|
-
const current = await readTaskRecord(root, taskId);
|
|
230
|
-
const record = current.record;
|
|
231
|
-
if (!record) throw new Error(`task ${taskId} has no TaskRecord`);
|
|
232
|
-
if (current.revision !== projection.projection.record_revision)
|
|
233
|
-
throw new Error("TaskRecord changed before Review revision preparation");
|
|
234
|
-
if (record.contract !== "assurance_kernel/task_record/v4") return null;
|
|
235
|
-
if (!record.git_base_head)
|
|
236
|
-
throw new Error("Review revision requires a TaskRecord v4 git_base_head");
|
|
237
|
-
const manifest = captureReviewManifest(root, {
|
|
238
|
-
taskId,
|
|
239
|
-
baseHead: record.git_base_head,
|
|
240
|
-
scopeHint: record.intent_snapshot.scope_hint,
|
|
241
|
-
expectedDiffHash: projection.projection.diff_hash,
|
|
242
|
-
intentRevision: projection.projection.intent_revision,
|
|
243
|
-
intentContentHash: projection.projection.intent_content_hash,
|
|
244
|
-
recordRevision: projection.projection.record_revision,
|
|
245
|
-
workspaceRevision: projection.projection.workspace_revision,
|
|
246
|
-
lifecycle: projection.projection.lifecycle,
|
|
247
|
-
artifactState: projection.projection.artifact_state,
|
|
248
|
-
risk: record.intent_snapshot.risk,
|
|
249
|
-
outcomes: qaOutcomes(record),
|
|
250
|
-
});
|
|
251
|
-
return {
|
|
252
|
-
contract: "assurance_kernel/review_revision/v1",
|
|
253
|
-
base_head: manifest.base_head,
|
|
254
|
-
review_tree: manifest.review_tree,
|
|
255
|
-
review_commit: manifest.review_commit,
|
|
256
|
-
review_ref: manifest.review_ref,
|
|
257
|
-
diff_hash: manifest.diff_hash,
|
|
258
|
-
manifest_digest: manifest.manifest_digest,
|
|
259
|
-
};
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
async function buildAssuranceSnapshot(
|
|
263
|
-
root: string,
|
|
264
|
-
taskId: string,
|
|
265
|
-
role: "qa" | "review",
|
|
266
|
-
projection: AssuranceProjectionResult,
|
|
267
|
-
) {
|
|
268
|
-
const read = await readTaskRecord(root, taskId);
|
|
269
|
-
const record = read.record;
|
|
270
|
-
if (!record || read.revision !== projection.projection.record_revision) throw new Error("TaskRecord changed before assurance snapshot capture");
|
|
271
|
-
const intent = record.intent_snapshot;
|
|
272
|
-
const descriptors = new Map<string, VerificationDescriptor>();
|
|
273
|
-
for (const item of role === "qa" ? intent.acceptance : []) {
|
|
274
|
-
const descriptor = parseVerificationDescriptor(item.verification);
|
|
275
|
-
descriptors.set(item.id, descriptor);
|
|
276
|
-
}
|
|
277
|
-
const reviewBundle = null;
|
|
278
|
-
const reviewManifest = role === "review"
|
|
279
|
-
? captureReviewManifest(root, {
|
|
280
|
-
taskId,
|
|
281
|
-
baseHead: record.git_base_head,
|
|
282
|
-
scopeHint: intent.scope_hint,
|
|
283
|
-
expectedDiffHash: projection.projection.diff_hash,
|
|
284
|
-
intentRevision: projection.projection.intent_revision,
|
|
285
|
-
intentContentHash: projection.projection.intent_content_hash,
|
|
286
|
-
recordRevision: projection.projection.record_revision,
|
|
287
|
-
workspaceRevision: projection.projection.workspace_revision,
|
|
288
|
-
lifecycle: projection.projection.lifecycle,
|
|
289
|
-
artifactState: projection.projection.artifact_state,
|
|
290
|
-
risk: intent.risk,
|
|
291
|
-
outcomes: qaOutcomes(record),
|
|
292
|
-
})
|
|
293
|
-
: null;
|
|
294
|
-
const dirtyFiles = reviewManifest ? Object.keys(reviewManifest.changed_paths) : [];
|
|
295
|
-
const snapshot: SnapshotDescriptor = {
|
|
296
|
-
contract: "assurance_kernel/assurance_snapshot/v2",
|
|
297
|
-
task_id: taskId,
|
|
298
|
-
run_id: projection.projection.run_id,
|
|
299
|
-
role,
|
|
300
|
-
record_revision: projection.projection.record_revision,
|
|
301
|
-
workspace_revision: projection.projection.workspace_revision,
|
|
302
|
-
intent_revision: projection.projection.intent_revision,
|
|
303
|
-
intent_content_hash: projection.projection.intent_content_hash,
|
|
304
|
-
diff_hash: projection.projection.diff_hash,
|
|
305
|
-
lifecycle: projection.projection.lifecycle,
|
|
306
|
-
artifact_state: projection.projection.artifact_state,
|
|
307
|
-
risk: intent.risk,
|
|
308
|
-
fresh_acceptance_ids: projection.projection.fresh_acceptance_ids,
|
|
309
|
-
missing_acceptance_ids: projection.projection.missing_acceptance_ids,
|
|
310
|
-
stale_attestation_ids: projection.projection.stale_attestation_ids,
|
|
311
|
-
acceptance: intent.acceptance,
|
|
312
|
-
dirty_files: dirtyFiles,
|
|
313
|
-
review_bundle_digest: reviewManifest?.manifest_digest ?? null,
|
|
314
|
-
root,
|
|
315
|
-
...(reviewManifest
|
|
316
|
-
? {
|
|
317
|
-
review_revision: {
|
|
318
|
-
contract: "assurance_kernel/review_revision_identity/v1",
|
|
319
|
-
base_head: reviewManifest.base_head,
|
|
320
|
-
review_commit: reviewManifest.review_commit,
|
|
321
|
-
review_tree: reviewManifest.review_tree,
|
|
322
|
-
manifest_digest: reviewManifest.manifest_digest,
|
|
323
|
-
},
|
|
324
|
-
}
|
|
325
|
-
: {}),
|
|
326
|
-
};
|
|
327
|
-
return { snapshot, descriptors, reviewBundle, reviewManifest };
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
function stagePlanningArtifactTransition(root: string, record: { intent_ref: { path: string }; intent_snapshot: { scope_hint: string[] } }): void {
|
|
331
|
-
const intentActive = record.intent_ref.path.replace("docs/plans/archive/", "docs/plans/");
|
|
332
|
-
const intentArchive = intentActive.replace("docs/plans/", "docs/plans/archive/");
|
|
333
|
-
const specActive = record.intent_snapshot.scope_hint.find((path) =>
|
|
334
|
-
/^docs\/specs\/(?!archive\/)[^/]+\.spec\.md$/.test(path)
|
|
335
|
-
&& record.intent_snapshot.scope_hint.includes(path.replace("docs/specs/", "docs/specs/archive/")),
|
|
336
|
-
);
|
|
337
|
-
const candidates = [
|
|
338
|
-
intentActive,
|
|
339
|
-
intentArchive,
|
|
340
|
-
...(specActive ? [specActive, specActive.replace("docs/specs/", "docs/specs/archive/")] : []),
|
|
341
|
-
];
|
|
342
|
-
const paths = candidates.filter((path) => existsSync(join(root, path)) || execFileSync("git", ["ls-files", "--cached", "--", path], { cwd: root, encoding: "utf8" }).trim().length > 0);
|
|
343
|
-
if (paths.length === 0) return;
|
|
344
|
-
execFileSync("git", ["add", "--", ...paths], { cwd: root, stdio: ["ignore", "pipe", "pipe"] });
|
|
345
|
-
}
|
|
346
|
-
|
|
347
203
|
async function mintCapability(
|
|
348
204
|
registry: MutationAuthorityRegistry,
|
|
349
205
|
input: {
|
|
@@ -413,6 +269,8 @@ export interface ClaudeRuntimeOptions {
|
|
|
413
269
|
* actually wires was never constructed once.
|
|
414
270
|
*/
|
|
415
271
|
ports?: Partial<AssuranceCoordinatorPorts>;
|
|
272
|
+
/** Optional authority test seam, separate from host ports. */
|
|
273
|
+
authorityOverrides?: Partial<VerdictAuthority>;
|
|
416
274
|
interactive?: boolean;
|
|
417
275
|
permissionMode?: PermissionMode;
|
|
418
276
|
requestConfirmation?: NativeConfirmationPort;
|
|
@@ -469,7 +327,8 @@ export class ClaudeRuntime {
|
|
|
469
327
|
...this.createKernelPorts(),
|
|
470
328
|
...options.ports,
|
|
471
329
|
host: this.host,
|
|
472
|
-
|
|
330
|
+
confirmationReference: ({ actorId }) => `claude:${actorId}`,
|
|
331
|
+
}, options.authorityOverrides);
|
|
473
332
|
}
|
|
474
333
|
|
|
475
334
|
observe(event: ClaudeHookEvent): void {
|
|
@@ -502,14 +361,12 @@ export class ClaudeRuntime {
|
|
|
502
361
|
private createKernelPorts(): AssuranceCoordinatorPorts {
|
|
503
362
|
return {
|
|
504
363
|
host: this.host,
|
|
364
|
+
confirmationReference: ({ actorId }) => `claude:${actorId}`,
|
|
505
365
|
projectTask: (root, taskId) => projectAssurance(root, taskId, diffSnapshotOf),
|
|
506
|
-
readTaskRecord: async (root, taskId) =>
|
|
366
|
+
readTaskRecord: async (root, taskId) => readTaskRecordRaw(root, taskId),
|
|
507
367
|
readTaskIntent: async (root, taskId) => readTaskIntentForRecord(root, taskId),
|
|
508
|
-
buildAssurance: (root, taskId, role, projection) => buildAssuranceSnapshot(root, taskId, role, projection),
|
|
509
|
-
ensureReviewRevision: (root, taskId, projection) => ensureClaudeReviewRevision(root, taskId, projection),
|
|
510
368
|
runQa: (snapshot, descriptors, options) => runDeterministicQa(snapshot, descriptors, options),
|
|
511
369
|
writeReviewEvidence: (input) => writeNativeReviewEvidence(input.evidence),
|
|
512
|
-
applyVerdict: (ctx, input) => this.applyVerdict(ctx, input),
|
|
513
370
|
applyOrdinaryOperation: (ctx, input) => this.executeOrdinary(ctx, input),
|
|
514
371
|
};
|
|
515
372
|
}
|
|
@@ -588,21 +445,7 @@ export class ClaudeRuntime {
|
|
|
588
445
|
confirmation_ref: gate.confirmation_ref,
|
|
589
446
|
nonce,
|
|
590
447
|
};
|
|
591
|
-
|
|
592
|
-
const input = {
|
|
593
|
-
task_id: taskId,
|
|
594
|
-
intent_path: binding.intent_path,
|
|
595
|
-
intent_revision: binding.intent_revision,
|
|
596
|
-
preparation_digest: binding.preparation_digest,
|
|
597
|
-
capability,
|
|
598
|
-
capability_binding: binding,
|
|
599
|
-
now,
|
|
600
|
-
};
|
|
601
|
-
const rehearsal = runEnrollmentRehearsal(this.cwd, input, capability, this.enrollmentRegistry);
|
|
602
|
-
if (!rehearsal.rehearsed || rehearsal.evidence.outcome !== "ready") {
|
|
603
|
-
throw new Error(`Kernel enrollment rehearsal failed: ${rehearsal.evidence.blockers.join("; ")}`);
|
|
604
|
-
}
|
|
605
|
-
return enrollCanaryTask(this.cwd, input, this.enrollmentRegistry);
|
|
448
|
+
return await enrollTask(this.cwd, this.enrollmentRegistry, { binding, now });
|
|
606
449
|
} catch (error) {
|
|
607
450
|
if (gitBaseNote) throw new Error(`${error instanceof Error ? error.message : String(error)}${gitBaseNote}`);
|
|
608
451
|
throw error;
|
|
@@ -766,7 +609,7 @@ export class ClaudeRuntime {
|
|
|
766
609
|
const derived = deriveAuthorizationOperation({ readiness: projection.projection.authorization });
|
|
767
610
|
if ("blocked" in derived) throw new Error(derived.blocked);
|
|
768
611
|
if (derived.operation === "resolve-user-decision") {
|
|
769
|
-
const record = await
|
|
612
|
+
const record = await readTaskRecordRaw(this.cwd, taskId);
|
|
770
613
|
const open = (record.record?.findings ?? []).filter(
|
|
771
614
|
(finding) => finding.kind === "unresolved_user_decision" && finding.status === "open",
|
|
772
615
|
);
|
|
@@ -800,7 +643,7 @@ export class ClaudeRuntime {
|
|
|
800
643
|
if (sidecar && nextIntent) {
|
|
801
644
|
writeFileSync(sidecar, `${JSON.stringify(nextIntent, null, 2)}\n`);
|
|
802
645
|
execFileSync("git", ["add", "--", priorIntent.intent_ref.path], { cwd: this.cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
803
|
-
const preparedRecord = await
|
|
646
|
+
const preparedRecord = await readTaskRecordRaw(this.cwd, taskId);
|
|
804
647
|
if (!preparedRecord.record) {
|
|
805
648
|
throw new NativeAuthorityError("workspace_changed", "TaskRecord changed before the breaking revision digest");
|
|
806
649
|
}
|
|
@@ -823,7 +666,7 @@ export class ClaudeRuntime {
|
|
|
823
666
|
});
|
|
824
667
|
} catch (error) {
|
|
825
668
|
if (stagedSnapshot) {
|
|
826
|
-
const current = await
|
|
669
|
+
const current = await readTaskRecordRaw(this.cwd, taskId);
|
|
827
670
|
if (current.record?.intent_snapshot.revision === priorIntent.intent.revision) {
|
|
828
671
|
restoreStagedIntent();
|
|
829
672
|
}
|
|
@@ -885,7 +728,7 @@ export class ClaudeRuntime {
|
|
|
885
728
|
) stagePlanningArtifactTransition(this.cwd, result.record);
|
|
886
729
|
} catch (error) {
|
|
887
730
|
if (stagedSnapshot) {
|
|
888
|
-
const current = await
|
|
731
|
+
const current = await readTaskRecordRaw(this.cwd, taskId);
|
|
889
732
|
if (current.record?.intent_snapshot.revision === priorIntent.intent.revision) {
|
|
890
733
|
restoreStagedIntent();
|
|
891
734
|
}
|
|
@@ -899,94 +742,6 @@ export class ClaudeRuntime {
|
|
|
899
742
|
return this.withTerminalTracker(taskId, committed);
|
|
900
743
|
}
|
|
901
744
|
|
|
902
|
-
private async applyVerdict(
|
|
903
|
-
ctx: HostContext,
|
|
904
|
-
input: {
|
|
905
|
-
taskId: string;
|
|
906
|
-
snapshot: SnapshotDescriptor;
|
|
907
|
-
verdict: AssuranceVerdict;
|
|
908
|
-
invocation: { /* token */ };
|
|
909
|
-
actorId: string;
|
|
910
|
-
hooks?: { beforeCommit?: () => Promise<void>; onCommit?: () => void; afterCommit?: () => Promise<void> };
|
|
911
|
-
},
|
|
912
|
-
): Promise<void> {
|
|
913
|
-
const { registry, app } = await this.authority();
|
|
914
|
-
const priorIntentToken = (await readTaskIntentForRecord(ctx.cwd, input.taskId)).token;
|
|
915
|
-
const now = new Date().toISOString();
|
|
916
|
-
const commitAndApply = async <T>(apply: () => Promise<T>): Promise<T> => {
|
|
917
|
-
this.coordinator.commitInvocation(input.invocation as never);
|
|
918
|
-
const settlement = apply();
|
|
919
|
-
input.hooks?.onCommit?.();
|
|
920
|
-
const result = await settlement;
|
|
921
|
-
await input.hooks?.afterCommit?.();
|
|
922
|
-
return result;
|
|
923
|
-
};
|
|
924
|
-
if (input.verdict.decision === "rework") {
|
|
925
|
-
const findings = reviewReworkFindings(input.verdict);
|
|
926
|
-
const capability = await mintCapability(registry, {
|
|
927
|
-
authority_kind: input.snapshot.role,
|
|
928
|
-
task_id: input.taskId,
|
|
929
|
-
run_id: input.snapshot.run_id,
|
|
930
|
-
action_kind: "request_rework",
|
|
931
|
-
expected_record_hash: input.snapshot.record_revision,
|
|
932
|
-
intent_revision: input.snapshot.intent_revision,
|
|
933
|
-
intent_content_hash: input.snapshot.intent_content_hash,
|
|
934
|
-
diff_hash: input.snapshot.diff_hash,
|
|
935
|
-
actor_id: input.actorId,
|
|
936
|
-
findings,
|
|
937
|
-
now,
|
|
938
|
-
confirmation_ref: `claude:${input.actorId}`,
|
|
939
|
-
});
|
|
940
|
-
await input.hooks?.beforeCommit?.();
|
|
941
|
-
const result = await commitAndApply(async () => app.execute({
|
|
942
|
-
root: ctx.cwd,
|
|
943
|
-
task_id: input.taskId,
|
|
944
|
-
operation: { op: "request_rework", capability, findings: findings as never[], actor_id: input.actorId },
|
|
945
|
-
prior_intent_token: priorIntentToken,
|
|
946
|
-
diffProvider: diffSnapshotOf,
|
|
947
|
-
now,
|
|
948
|
-
}));
|
|
949
|
-
stagePlanningArtifactTransition(ctx.cwd, result.record);
|
|
950
|
-
return;
|
|
951
|
-
}
|
|
952
|
-
const advisories = input.verdict.decision === "pass" ? reviewAdvisoryRecords(input.verdict) : [];
|
|
953
|
-
const approval: TaskApprovalV2 = {
|
|
954
|
-
id: `approval-${input.snapshot.role}-${randomUUID().slice(0, 8)}`,
|
|
955
|
-
kind: input.snapshot.role === "qa" ? "qa" : "review",
|
|
956
|
-
authority_role: input.snapshot.role === "qa" ? "qa" : "reviewer",
|
|
957
|
-
task_revision: input.snapshot.intent_revision,
|
|
958
|
-
intent_content_hash: input.snapshot.intent_content_hash,
|
|
959
|
-
diff_hash: input.snapshot.diff_hash,
|
|
960
|
-
actor_id: input.actorId,
|
|
961
|
-
summary: input.verdict.approval!.summary,
|
|
962
|
-
...(input.snapshot.role === "review" && input.snapshot.review_revision ? { review_revision: input.snapshot.review_revision } : {}),
|
|
963
|
-
...(input.snapshot.role === "review" && advisories.length > 0 ? { advisory_findings: advisories } : {}),
|
|
964
|
-
};
|
|
965
|
-
const capability = await mintCapability(registry, {
|
|
966
|
-
authority_kind: input.snapshot.role,
|
|
967
|
-
task_id: input.taskId,
|
|
968
|
-
run_id: input.snapshot.run_id,
|
|
969
|
-
action_kind: "record_approval",
|
|
970
|
-
expected_record_hash: input.snapshot.record_revision,
|
|
971
|
-
intent_revision: input.snapshot.intent_revision,
|
|
972
|
-
intent_content_hash: input.snapshot.intent_content_hash,
|
|
973
|
-
diff_hash: input.snapshot.diff_hash,
|
|
974
|
-
actor_id: input.actorId,
|
|
975
|
-
approval,
|
|
976
|
-
now,
|
|
977
|
-
confirmation_ref: `claude:${input.actorId}`,
|
|
978
|
-
});
|
|
979
|
-
await input.hooks?.beforeCommit?.();
|
|
980
|
-
await commitAndApply(async () => app.execute({
|
|
981
|
-
root: ctx.cwd,
|
|
982
|
-
task_id: input.taskId,
|
|
983
|
-
operation: { op: "record_approval", capability, approval, actor_id: input.actorId },
|
|
984
|
-
prior_intent_token: priorIntentToken,
|
|
985
|
-
diffProvider: diffSnapshotOf,
|
|
986
|
-
now,
|
|
987
|
-
}));
|
|
988
|
-
}
|
|
989
|
-
|
|
990
745
|
private async executeOrdinary(ctx: HostContext, input: { taskId: string; operation: { op: string; actor_id: string; next_intent?: unknown; finding_id?: string; attestation_id?: string } }) {
|
|
991
746
|
const { app } = await this.authority();
|
|
992
747
|
const operation = input.operation.op === "revise_intent"
|
|
@@ -1019,7 +774,7 @@ export class ClaudeRuntime {
|
|
|
1019
774
|
return result;
|
|
1020
775
|
} catch (error) {
|
|
1021
776
|
if (priorStaged) {
|
|
1022
|
-
const current = await
|
|
777
|
+
const current = await readTaskRecordRaw(ctx.cwd, input.taskId);
|
|
1023
778
|
if (current.record?.intent_snapshot.revision === priorIntent.intent.revision)
|
|
1024
779
|
restoreStagedIntentShared(ctx.cwd, priorStaged);
|
|
1025
780
|
}
|
|
@@ -1030,7 +785,7 @@ export class ClaudeRuntime {
|
|
|
1030
785
|
async startUnattendedBatch(
|
|
1031
786
|
initiativeSlug: string,
|
|
1032
787
|
meta: ToolMeta,
|
|
1033
|
-
options: { reuseOnly?: boolean } = {},
|
|
788
|
+
options: { reuseOnly?: boolean; max_parallel?: number; lane_offers?: LaneOffer[] } = {},
|
|
1034
789
|
): Promise<ClaudeBatchStartResult> {
|
|
1035
790
|
throwIfCancelled(meta.signal);
|
|
1036
791
|
const reuseOnly = options.reuseOnly === true;
|
|
@@ -1053,7 +808,7 @@ export class ClaudeRuntime {
|
|
|
1053
808
|
root: this.cwd,
|
|
1054
809
|
initiative_slug: initiativeSlug,
|
|
1055
810
|
now,
|
|
1056
|
-
readInitiative: this.readInitiative ??
|
|
811
|
+
readInitiative: this.readInitiative ?? observeInitiative,
|
|
1057
812
|
});
|
|
1058
813
|
if (!preflight.ok) {
|
|
1059
814
|
return {
|
|
@@ -1076,7 +831,7 @@ export class ClaudeRuntime {
|
|
|
1076
831
|
initiative_slug: initiativeSlug,
|
|
1077
832
|
now,
|
|
1078
833
|
projection: preflight.projection,
|
|
1079
|
-
readInitiative: this.readInitiative ??
|
|
834
|
+
readInitiative: this.readInitiative ?? observeInitiative,
|
|
1080
835
|
nonce: enrollmentNonce(),
|
|
1081
836
|
gate: async (facts) => {
|
|
1082
837
|
// A continuation after a foreground child never opens a gate: when the
|
|
@@ -1113,6 +868,15 @@ export class ClaudeRuntime {
|
|
|
1113
868
|
reason: child.reason,
|
|
1114
869
|
})),
|
|
1115
870
|
budget: facts.budget,
|
|
871
|
+
...(options.max_parallel !== undefined
|
|
872
|
+
? {
|
|
873
|
+
lane_mode: {
|
|
874
|
+
max_parallel: options.max_parallel,
|
|
875
|
+
parallel_groups: facts.parallel_groups,
|
|
876
|
+
serialized: facts.scope_conflicts,
|
|
877
|
+
},
|
|
878
|
+
}
|
|
879
|
+
: {}),
|
|
1116
880
|
...(facts.reuse_blockers.length > 0
|
|
1117
881
|
? {
|
|
1118
882
|
re_confirmation_required: facts.reuse_blockers,
|
|
@@ -1179,17 +943,30 @@ export class ClaudeRuntime {
|
|
|
1179
943
|
now = new Date().toISOString();
|
|
1180
944
|
const capability = this.batchRegistry.issue(binding, recoveryChildren as any, now);
|
|
1181
945
|
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
}
|
|
946
|
+
// review-batch-partial-port-fabricates-enrollment: the production child
|
|
947
|
+
// Kernel port is the shared runtime/unattended one; this Host supplies
|
|
948
|
+
// only its own advanceTask progression seam (D5).
|
|
949
|
+
const advanceTask: BatchRunnerKernelPort["advanceTask"] = async (root, taskId) => {
|
|
950
|
+
const result = await this.coordinator.advance(taskId, { cwd: root });
|
|
951
|
+
const facts = { diagnostics: result.diagnostics, environment_failure: result.environment_failure, recovery: result.recovery };
|
|
952
|
+
if (result.state === "completed") return { state: "completed", ...facts };
|
|
953
|
+
if (result.state === "stopped") return { state: "stopped", ...facts };
|
|
954
|
+
if (result.state === "rework") return { state: "rework", operation: result.operation, summary: result.summary, ...facts };
|
|
955
|
+
if (result.state === "review_ready") return { state: "review_ready", operation_id: result.operation_id, ...facts };
|
|
956
|
+
if (result.state === "review_preparation_failed") return { state: result.state, operation: result.operation, operation_id: result.operation_id, reason: result.reason, ...facts };
|
|
957
|
+
if (result.state === "blocked") return { state: "blocked", reason: result.reason, ...facts };
|
|
958
|
+
return { state: "failed", reason: (result as { reason?: string }).reason ?? "advance failed", ...facts };
|
|
1192
959
|
};
|
|
960
|
+
const kernelPort = createBatchKernelPort({
|
|
961
|
+
root: this.cwd,
|
|
962
|
+
enrollmentRegistry: this.enrollmentRegistry,
|
|
963
|
+
registry: this.batchRegistry,
|
|
964
|
+
capability,
|
|
965
|
+
binding,
|
|
966
|
+
advanceTask,
|
|
967
|
+
resume: { isResuming, existingBatch, batchBranch },
|
|
968
|
+
overrides: this.batchKernel,
|
|
969
|
+
});
|
|
1193
970
|
const report = await startBatch({
|
|
1194
971
|
root: this.cwd,
|
|
1195
972
|
batch_id: batchId,
|
|
@@ -1204,6 +981,8 @@ export class ClaudeRuntime {
|
|
|
1204
981
|
now,
|
|
1205
982
|
kernel: kernelPort,
|
|
1206
983
|
git: this.batchGit,
|
|
984
|
+
...(options.max_parallel !== undefined ? { max_parallel: options.max_parallel } : {}),
|
|
985
|
+
...(options.lane_offers !== undefined ? { lane_offers: options.lane_offers } : {}),
|
|
1207
986
|
});
|
|
1208
987
|
|
|
1209
988
|
// review-3 & review-batch-preflight-recovery-is-diagnostic: map rejected batch state to rejected result with same-Host recovery action
|
|
@@ -1215,80 +994,6 @@ export class ClaudeRuntime {
|
|
|
1215
994
|
report,
|
|
1216
995
|
};
|
|
1217
996
|
}
|
|
1218
|
-
|
|
1219
|
-
private createBatchKernelPort(
|
|
1220
|
-
registry: BatchAuthorityRegistry,
|
|
1221
|
-
capability: object,
|
|
1222
|
-
binding: BatchAuthorizationBinding,
|
|
1223
|
-
): BatchRunnerKernelPort {
|
|
1224
|
-
// review-batch-partial-port-fabricates-enrollment: construct real production port first, never fabricate stubs
|
|
1225
|
-
const realPort: BatchRunnerKernelPort = {
|
|
1226
|
-
enrollTask: async ({ root, task_id, batch }) => {
|
|
1227
|
-
const now = new Date().toISOString();
|
|
1228
|
-
const derived = deriveChildEnrollment(root, batch.registry, {
|
|
1229
|
-
capability: batch.capability,
|
|
1230
|
-
binding,
|
|
1231
|
-
task_id,
|
|
1232
|
-
expected_head: batch.binding.expected_head,
|
|
1233
|
-
now,
|
|
1234
|
-
});
|
|
1235
|
-
const enrollmentCapability = this.enrollmentRegistry.issue(derived.binding);
|
|
1236
|
-
const input = {
|
|
1237
|
-
task_id,
|
|
1238
|
-
intent_path: derived.binding.intent_path,
|
|
1239
|
-
intent_revision: derived.binding.intent_revision,
|
|
1240
|
-
preparation_digest: derived.binding.preparation_digest,
|
|
1241
|
-
capability: enrollmentCapability,
|
|
1242
|
-
capability_binding: derived.binding,
|
|
1243
|
-
batch: {
|
|
1244
|
-
registry: batch.registry,
|
|
1245
|
-
capability: batch.capability,
|
|
1246
|
-
binding,
|
|
1247
|
-
expected_head: batch.binding.expected_head,
|
|
1248
|
-
},
|
|
1249
|
-
now,
|
|
1250
|
-
};
|
|
1251
|
-
const rehearsal = runEnrollmentRehearsal(root, input, enrollmentCapability, this.enrollmentRegistry);
|
|
1252
|
-
if (!rehearsal.rehearsed || rehearsal.evidence.outcome !== "ready") {
|
|
1253
|
-
throw new Error(`Kernel enrollment rehearsal failed: ${rehearsal.evidence.blockers.join("; ")}`);
|
|
1254
|
-
}
|
|
1255
|
-
// The enrollment itself is the effect; its record is re-read below.
|
|
1256
|
-
enrollCanaryTask(root, input, this.enrollmentRegistry);
|
|
1257
|
-
const recordRaw = readTaskRecordRaw(root, task_id);
|
|
1258
|
-
return { record_revision: recordRaw.revision };
|
|
1259
|
-
},
|
|
1260
|
-
advanceTask: async (root, taskId) => {
|
|
1261
|
-
const result = await this.coordinator.advance(taskId, { cwd: root });
|
|
1262
|
-
const facts = { diagnostics: result.diagnostics, environment_failure: result.environment_failure, recovery: result.recovery };
|
|
1263
|
-
if (result.state === "completed") return { state: "completed", ...facts };
|
|
1264
|
-
if (result.state === "stopped") return { state: "stopped", ...facts };
|
|
1265
|
-
if (result.state === "rework") return { state: "rework", operation: result.operation, summary: result.summary, ...facts };
|
|
1266
|
-
if (result.state === "review_ready") return { state: "review_ready", operation_id: result.operation_id, ...facts };
|
|
1267
|
-
if (result.state === "review_preparation_failed") return { state: result.state, operation: result.operation, operation_id: result.operation_id, reason: result.reason, ...facts };
|
|
1268
|
-
if (result.state === "blocked") return { state: "blocked", reason: result.reason, ...facts };
|
|
1269
|
-
return { state: "failed", reason: (result as { reason?: string }).reason ?? "advance failed", ...facts };
|
|
1270
|
-
},
|
|
1271
|
-
projectTask: async (root, taskId) => {
|
|
1272
|
-
const fresh = await projectAssurance(root, taskId, diffSnapshotOf);
|
|
1273
|
-
if (!fresh.claim || fresh.projection.lifecycle !== "active" || fresh.error !== null) return fresh;
|
|
1274
|
-
const { record, revision } = readTaskRecordRaw(root, taskId);
|
|
1275
|
-
if (!record || revision !== fresh.projection.record_revision) throw new Error(`Kernel recovery projection changed for ${taskId}`);
|
|
1276
|
-
return { ...fresh, ...batchQaFailureFacts(record) };
|
|
1277
|
-
},
|
|
1278
|
-
ownsTaskClaim: (taskId) => {
|
|
1279
|
-
return registry.isChildConsumed(capability, taskId);
|
|
1280
|
-
},
|
|
1281
|
-
validateBatchAuthorization: (input) => {
|
|
1282
|
-
return input.registry.inspect(input.capability, input.binding as never);
|
|
1283
|
-
},
|
|
1284
|
-
};
|
|
1285
|
-
|
|
1286
|
-
if (!this.batchKernel) return realPort;
|
|
1287
|
-
return {
|
|
1288
|
-
...realPort,
|
|
1289
|
-
...this.batchKernel,
|
|
1290
|
-
};
|
|
1291
|
-
}
|
|
1292
997
|
}
|
|
1293
998
|
|
|
1294
999
|
export interface ToolMeta {
|