immune-brain 4.6.0 → 5.0.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 +154 -37
- package/README.zh-CN.md +160 -40
- package/package.json +1 -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 +142 -474
- package/plugins/immune-brain/.pi-extension/imm-unattended-batch.ts +54 -84
- 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 +7369 -7016
- 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} +33 -7
- 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/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/kernel_ports.ts +62 -369
- package/plugins/immune-brain/runtime/claude/mcp_server.ts +2 -0
- package/plugins/immune-brain/runtime/claude/review_host.ts +48 -16
- package/plugins/immune-brain/runtime/claude/role_agents.ts +83 -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/loop_contract.ts +22 -22
- 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/role_prompt_bridge.ts +27 -0
- package/plugins/immune-brain/runtime/unattended/batch_git.ts +57 -27
- package/plugins/immune-brain/runtime/unattended/batch_kernel_port.ts +106 -0
- package/plugins/immune-brain/runtime/unattended/batch_preflight.ts +12 -14
- package/plugins/immune-brain/runtime/unattended/batch_reconfirmation.ts +34 -29
- package/plugins/immune-brain/runtime/unattended/batch_runner.ts +26 -90
- 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 +7 -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,16 @@ 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";
|
|
69
60
|
import {
|
|
70
61
|
createBatchAuthorityRegistry,
|
|
71
|
-
deriveChildEnrollment,
|
|
72
|
-
type BatchAuthorityRegistry,
|
|
73
|
-
type BatchAuthorizationBinding,
|
|
74
62
|
} from "../kernel/batch_authority";
|
|
75
63
|
import {
|
|
76
64
|
type BatchRunnerGitPort,
|
|
@@ -96,11 +84,7 @@ export function diffSnapshotOf(root: string, record: TaskRecord): {
|
|
|
96
84
|
diff_hash: string;
|
|
97
85
|
changed_paths: readonly string[];
|
|
98
86
|
} {
|
|
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);
|
|
87
|
+
return taskDeliveryIdentity(root, record);
|
|
104
88
|
}
|
|
105
89
|
|
|
106
90
|
export function diffHashOf(root: string, record: TaskRecord): string {
|
|
@@ -144,6 +128,21 @@ function verdictFingerprint(raw: Record<string, unknown>): string {
|
|
|
144
128
|
});
|
|
145
129
|
}
|
|
146
130
|
|
|
131
|
+
const RELEASED_REVIEW_RECOVERY =
|
|
132
|
+
"Call advance_assurance to obtain a new Review reservation, then dispatch one fresh reviewer with the returned envelope unchanged";
|
|
133
|
+
const RETAINED_REVIEW_RECOVERY =
|
|
134
|
+
"Wait for the dispatched reviewer to finish, then call submit_review again with its verdict; do not dispatch or continue another reviewer";
|
|
135
|
+
const MISMATCH_REVIEW_RECOVERY =
|
|
136
|
+
"Resubmit the reviewer's verdict exactly as the reviewer returned it";
|
|
137
|
+
|
|
138
|
+
function withReviewRecovery(
|
|
139
|
+
result: AssuranceSubmitReviewResult,
|
|
140
|
+
recovery_action: string,
|
|
141
|
+
): AssuranceSubmitReviewResult {
|
|
142
|
+
if (result.state !== "blocked" || result.code === "verdict_invalid") return result;
|
|
143
|
+
return { ...result, recovery_action };
|
|
144
|
+
}
|
|
145
|
+
|
|
147
146
|
export async function submitClaudeReview(
|
|
148
147
|
host: ClaudeReviewHost,
|
|
149
148
|
coordinator: AssuranceCoordinator,
|
|
@@ -154,19 +153,19 @@ export async function submitClaudeReview(
|
|
|
154
153
|
if (verdictInput === undefined) throw new Error("verdict is required");
|
|
155
154
|
const observed = host.inspectReviewForTask(taskId);
|
|
156
155
|
if (!observed.ok) {
|
|
157
|
-
if (observed.release) return coordinator.abandonReview(taskId, observed.reason);
|
|
158
|
-
return { state: "blocked", reason: observed.reason };
|
|
156
|
+
if (observed.release) return withReviewRecovery(coordinator.abandonReview(taskId, observed.reason), RELEASED_REVIEW_RECOVERY);
|
|
157
|
+
return { state: "blocked", reason: observed.reason, recovery_action: RETAINED_REVIEW_RECOVERY };
|
|
159
158
|
}
|
|
160
159
|
const parentValid = coordinator.isReviewVerdictValid(taskId, verdictInput);
|
|
161
160
|
if (!parentValid) return coordinator.submitReview(taskId, ctx, verdictInput);
|
|
162
161
|
const receiptValid = coordinator.isReviewVerdictValid(taskId, observed.receipt.result);
|
|
163
162
|
if (!receiptValid) {
|
|
164
|
-
return coordinator.abandonReview(taskId, "reviewer receipt is not a valid verdict");
|
|
163
|
+
return withReviewRecovery(coordinator.abandonReview(taskId, "reviewer receipt is not a valid verdict"), RELEASED_REVIEW_RECOVERY);
|
|
165
164
|
}
|
|
166
165
|
const parentJson = extractVerdictJson(verdictInput)!;
|
|
167
166
|
const receiptJson = extractVerdictJson(observed.receipt.result)!;
|
|
168
167
|
if (verdictFingerprint(parentJson) !== verdictFingerprint(receiptJson)) {
|
|
169
|
-
return { state: "blocked", reason: "parent verdict does not match reviewer receipt" };
|
|
168
|
+
return { state: "blocked", reason: "parent verdict does not match reviewer receipt", recovery_action: MISMATCH_REVIEW_RECOVERY };
|
|
170
169
|
}
|
|
171
170
|
return coordinator.submitReview(taskId, ctx, verdictInput);
|
|
172
171
|
}
|
|
@@ -200,150 +199,6 @@ function assertProjectionBinding(before: AssuranceProjectionResult, after: Assur
|
|
|
200
199
|
}
|
|
201
200
|
}
|
|
202
201
|
|
|
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
202
|
async function mintCapability(
|
|
348
203
|
registry: MutationAuthorityRegistry,
|
|
349
204
|
input: {
|
|
@@ -413,6 +268,8 @@ export interface ClaudeRuntimeOptions {
|
|
|
413
268
|
* actually wires was never constructed once.
|
|
414
269
|
*/
|
|
415
270
|
ports?: Partial<AssuranceCoordinatorPorts>;
|
|
271
|
+
/** Optional authority test seam, separate from host ports. */
|
|
272
|
+
authorityOverrides?: Partial<VerdictAuthority>;
|
|
416
273
|
interactive?: boolean;
|
|
417
274
|
permissionMode?: PermissionMode;
|
|
418
275
|
requestConfirmation?: NativeConfirmationPort;
|
|
@@ -469,7 +326,8 @@ export class ClaudeRuntime {
|
|
|
469
326
|
...this.createKernelPorts(),
|
|
470
327
|
...options.ports,
|
|
471
328
|
host: this.host,
|
|
472
|
-
|
|
329
|
+
confirmationReference: ({ actorId }) => `claude:${actorId}`,
|
|
330
|
+
}, options.authorityOverrides);
|
|
473
331
|
}
|
|
474
332
|
|
|
475
333
|
observe(event: ClaudeHookEvent): void {
|
|
@@ -502,14 +360,12 @@ export class ClaudeRuntime {
|
|
|
502
360
|
private createKernelPorts(): AssuranceCoordinatorPorts {
|
|
503
361
|
return {
|
|
504
362
|
host: this.host,
|
|
363
|
+
confirmationReference: ({ actorId }) => `claude:${actorId}`,
|
|
505
364
|
projectTask: (root, taskId) => projectAssurance(root, taskId, diffSnapshotOf),
|
|
506
|
-
readTaskRecord: async (root, taskId) =>
|
|
365
|
+
readTaskRecord: async (root, taskId) => readTaskRecordRaw(root, taskId),
|
|
507
366
|
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
367
|
runQa: (snapshot, descriptors, options) => runDeterministicQa(snapshot, descriptors, options),
|
|
511
368
|
writeReviewEvidence: (input) => writeNativeReviewEvidence(input.evidence),
|
|
512
|
-
applyVerdict: (ctx, input) => this.applyVerdict(ctx, input),
|
|
513
369
|
applyOrdinaryOperation: (ctx, input) => this.executeOrdinary(ctx, input),
|
|
514
370
|
};
|
|
515
371
|
}
|
|
@@ -588,21 +444,7 @@ export class ClaudeRuntime {
|
|
|
588
444
|
confirmation_ref: gate.confirmation_ref,
|
|
589
445
|
nonce,
|
|
590
446
|
};
|
|
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);
|
|
447
|
+
return await enrollTask(this.cwd, this.enrollmentRegistry, { binding, now });
|
|
606
448
|
} catch (error) {
|
|
607
449
|
if (gitBaseNote) throw new Error(`${error instanceof Error ? error.message : String(error)}${gitBaseNote}`);
|
|
608
450
|
throw error;
|
|
@@ -766,7 +608,7 @@ export class ClaudeRuntime {
|
|
|
766
608
|
const derived = deriveAuthorizationOperation({ readiness: projection.projection.authorization });
|
|
767
609
|
if ("blocked" in derived) throw new Error(derived.blocked);
|
|
768
610
|
if (derived.operation === "resolve-user-decision") {
|
|
769
|
-
const record = await
|
|
611
|
+
const record = await readTaskRecordRaw(this.cwd, taskId);
|
|
770
612
|
const open = (record.record?.findings ?? []).filter(
|
|
771
613
|
(finding) => finding.kind === "unresolved_user_decision" && finding.status === "open",
|
|
772
614
|
);
|
|
@@ -800,7 +642,7 @@ export class ClaudeRuntime {
|
|
|
800
642
|
if (sidecar && nextIntent) {
|
|
801
643
|
writeFileSync(sidecar, `${JSON.stringify(nextIntent, null, 2)}\n`);
|
|
802
644
|
execFileSync("git", ["add", "--", priorIntent.intent_ref.path], { cwd: this.cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
803
|
-
const preparedRecord = await
|
|
645
|
+
const preparedRecord = await readTaskRecordRaw(this.cwd, taskId);
|
|
804
646
|
if (!preparedRecord.record) {
|
|
805
647
|
throw new NativeAuthorityError("workspace_changed", "TaskRecord changed before the breaking revision digest");
|
|
806
648
|
}
|
|
@@ -823,7 +665,7 @@ export class ClaudeRuntime {
|
|
|
823
665
|
});
|
|
824
666
|
} catch (error) {
|
|
825
667
|
if (stagedSnapshot) {
|
|
826
|
-
const current = await
|
|
668
|
+
const current = await readTaskRecordRaw(this.cwd, taskId);
|
|
827
669
|
if (current.record?.intent_snapshot.revision === priorIntent.intent.revision) {
|
|
828
670
|
restoreStagedIntent();
|
|
829
671
|
}
|
|
@@ -885,7 +727,7 @@ export class ClaudeRuntime {
|
|
|
885
727
|
) stagePlanningArtifactTransition(this.cwd, result.record);
|
|
886
728
|
} catch (error) {
|
|
887
729
|
if (stagedSnapshot) {
|
|
888
|
-
const current = await
|
|
730
|
+
const current = await readTaskRecordRaw(this.cwd, taskId);
|
|
889
731
|
if (current.record?.intent_snapshot.revision === priorIntent.intent.revision) {
|
|
890
732
|
restoreStagedIntent();
|
|
891
733
|
}
|
|
@@ -899,94 +741,6 @@ export class ClaudeRuntime {
|
|
|
899
741
|
return this.withTerminalTracker(taskId, committed);
|
|
900
742
|
}
|
|
901
743
|
|
|
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
744
|
private async executeOrdinary(ctx: HostContext, input: { taskId: string; operation: { op: string; actor_id: string; next_intent?: unknown; finding_id?: string; attestation_id?: string } }) {
|
|
991
745
|
const { app } = await this.authority();
|
|
992
746
|
const operation = input.operation.op === "revise_intent"
|
|
@@ -1019,7 +773,7 @@ export class ClaudeRuntime {
|
|
|
1019
773
|
return result;
|
|
1020
774
|
} catch (error) {
|
|
1021
775
|
if (priorStaged) {
|
|
1022
|
-
const current = await
|
|
776
|
+
const current = await readTaskRecordRaw(ctx.cwd, input.taskId);
|
|
1023
777
|
if (current.record?.intent_snapshot.revision === priorIntent.intent.revision)
|
|
1024
778
|
restoreStagedIntentShared(ctx.cwd, priorStaged);
|
|
1025
779
|
}
|
|
@@ -1179,17 +933,30 @@ export class ClaudeRuntime {
|
|
|
1179
933
|
now = new Date().toISOString();
|
|
1180
934
|
const capability = this.batchRegistry.issue(binding, recoveryChildren as any, now);
|
|
1181
935
|
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
}
|
|
936
|
+
// review-batch-partial-port-fabricates-enrollment: the production child
|
|
937
|
+
// Kernel port is the shared runtime/unattended one; this Host supplies
|
|
938
|
+
// only its own advanceTask progression seam (D5).
|
|
939
|
+
const advanceTask: BatchRunnerKernelPort["advanceTask"] = async (root, taskId) => {
|
|
940
|
+
const result = await this.coordinator.advance(taskId, { cwd: root });
|
|
941
|
+
const facts = { diagnostics: result.diagnostics, environment_failure: result.environment_failure, recovery: result.recovery };
|
|
942
|
+
if (result.state === "completed") return { state: "completed", ...facts };
|
|
943
|
+
if (result.state === "stopped") return { state: "stopped", ...facts };
|
|
944
|
+
if (result.state === "rework") return { state: "rework", operation: result.operation, summary: result.summary, ...facts };
|
|
945
|
+
if (result.state === "review_ready") return { state: "review_ready", operation_id: result.operation_id, ...facts };
|
|
946
|
+
if (result.state === "review_preparation_failed") return { state: result.state, operation: result.operation, operation_id: result.operation_id, reason: result.reason, ...facts };
|
|
947
|
+
if (result.state === "blocked") return { state: "blocked", reason: result.reason, ...facts };
|
|
948
|
+
return { state: "failed", reason: (result as { reason?: string }).reason ?? "advance failed", ...facts };
|
|
1192
949
|
};
|
|
950
|
+
const kernelPort = createBatchKernelPort({
|
|
951
|
+
root: this.cwd,
|
|
952
|
+
enrollmentRegistry: this.enrollmentRegistry,
|
|
953
|
+
registry: this.batchRegistry,
|
|
954
|
+
capability,
|
|
955
|
+
binding,
|
|
956
|
+
advanceTask,
|
|
957
|
+
resume: { isResuming, existingBatch, batchBranch },
|
|
958
|
+
overrides: this.batchKernel,
|
|
959
|
+
});
|
|
1193
960
|
const report = await startBatch({
|
|
1194
961
|
root: this.cwd,
|
|
1195
962
|
batch_id: batchId,
|
|
@@ -1215,80 +982,6 @@ export class ClaudeRuntime {
|
|
|
1215
982
|
report,
|
|
1216
983
|
};
|
|
1217
984
|
}
|
|
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
985
|
}
|
|
1293
986
|
|
|
1294
987
|
export interface ToolMeta {
|
|
@@ -74,6 +74,7 @@ export interface McpRuntimeOptions {
|
|
|
74
74
|
cwd?: string;
|
|
75
75
|
env?: Record<string, string | undefined>;
|
|
76
76
|
ports?: AssuranceCoordinatorPorts;
|
|
77
|
+
authorityOverrides?: Partial<import("../assurance/verdict_authority").VerdictAuthority>;
|
|
77
78
|
host?: ClaudeReviewHost;
|
|
78
79
|
interactive?: boolean;
|
|
79
80
|
requestConfirmation?: NativeConfirmationPort;
|
|
@@ -89,6 +90,7 @@ export function createMcpRuntime(options: McpRuntimeOptions = {}) {
|
|
|
89
90
|
env: options.env ?? process.env,
|
|
90
91
|
host,
|
|
91
92
|
ports: options.ports,
|
|
93
|
+
authorityOverrides: options.authorityOverrides,
|
|
92
94
|
interactive: options.interactive,
|
|
93
95
|
requestConfirmation: options.requestConfirmation,
|
|
94
96
|
batchKernel: options.batchKernel,
|