immune-brain 5.1.0 → 5.1.1
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/package.json +1 -1
- package/plugins/immune-brain/.claude-plugin/plugin.json +1 -1
- package/plugins/immune-brain/.pi-extension/imm-canary-work.ts +17 -5
- package/plugins/immune-brain/.pi-extension/imm-unattended-batch.ts +165 -0
- package/plugins/immune-brain/.pi-extension/pi-canary-assurance-progression.ts +66 -3
- package/plugins/immune-brain/agents/immune-brain-reviewer.md +11 -1
- package/plugins/immune-brain/dist/claude/mcp-server.mjs +542 -79
- package/plugins/immune-brain/dist/docs/reference/subagent-dispatch-protocol.md +8 -6
- package/plugins/immune-brain/dist/imm-planner.md +4 -1
- package/plugins/immune-brain/dist/role-prompts/code-review.md +10 -0
- package/plugins/immune-brain/runtime/assurance/coordinator.ts +22 -4
- package/plugins/immune-brain/runtime/assurance/review_mediation.ts +107 -0
- package/plugins/immune-brain/runtime/assurance/verdict_authority.ts +3 -0
- package/plugins/immune-brain/runtime/claude/interaction.ts +10 -0
- package/plugins/immune-brain/runtime/claude/kernel_ports.ts +89 -54
- package/plugins/immune-brain/runtime/claude/mcp_server.ts +76 -25
- package/plugins/immune-brain/runtime/kernel/assurance_projection.ts +4 -1
- package/plugins/immune-brain/runtime/kernel/canary_application.ts +9 -2
- package/plugins/immune-brain/runtime/kernel/completion.ts +24 -1
- package/plugins/immune-brain/runtime/kernel/reducer.ts +15 -2
- package/plugins/immune-brain/runtime/kernel/spec_binding.ts +6 -0
- package/plugins/immune-brain/runtime/kernel/storage.ts +20 -4
- package/plugins/immune-brain/runtime/kernel/storage_paths.ts +33 -3
- package/plugins/immune-brain/runtime/kernel/types.ts +7 -0
- package/plugins/immune-brain/runtime/kernel/validation.ts +13 -2
- package/plugins/immune-brain/runtime/plugin_version.ts +1 -1
- package/plugins/immune-brain/runtime/prompts/code-review.md +10 -0
- package/plugins/immune-brain/runtime/role_prompt_bridge.ts +1 -1
- package/plugins/immune-brain/runtime/unattended/batch_disposition.ts +222 -0
- package/plugins/immune-brain/runtime/unattended/batch_lanes.ts +51 -14
- package/plugins/immune-brain/runtime/unattended/batch_preflight.ts +8 -12
- package/plugins/immune-brain/runtime/unattended/batch_reasons.ts +13 -1
- package/plugins/immune-brain/runtime/unattended/batch_runner.ts +2 -0
- package/plugins/immune-brain/runtime/unattended/batch_state.ts +44 -6
|
@@ -39,7 +39,7 @@ function probeHost(env = process.env, platform = process.platform, hostVersion)
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
// plugins/immune-brain/runtime/plugin_version.ts
|
|
42
|
-
var PLUGIN_VERSION = "5.1.
|
|
42
|
+
var PLUGIN_VERSION = "5.1.1";
|
|
43
43
|
|
|
44
44
|
// plugins/immune-brain/runtime/claude/interaction.ts
|
|
45
45
|
import { createHash, randomUUID } from "node:crypto";
|
|
@@ -48,7 +48,8 @@ var PRIVILEGED_OPERATIONS = [
|
|
|
48
48
|
"request_authorization",
|
|
49
49
|
"approve_breaking_intent_revision",
|
|
50
50
|
"stop",
|
|
51
|
-
"start_unattended_batch"
|
|
51
|
+
"start_unattended_batch",
|
|
52
|
+
"retire_stale_batch"
|
|
52
53
|
];
|
|
53
54
|
var RECOVERY_ACTIONS = {
|
|
54
55
|
interaction_not_opened: "retry through a fresh native gate in the current Host",
|
|
@@ -732,6 +733,7 @@ var KERNEL_DB_RELATIVE = ".imm/state/kernel.sqlite";
|
|
|
732
733
|
var KERNEL_STORE_SCHEMA_VERSION = 1;
|
|
733
734
|
var FILE_STORE_CLAIM_RELATIVE = ".imm/state/active-claim.json";
|
|
734
735
|
var FILE_STORE_WORKSPACE_RELATIVE = ".imm/state/workspace.json";
|
|
736
|
+
var FILE_STORE_TASKS_RELATIVE = ".imm/state/tasks";
|
|
735
737
|
var FILE_STORE_TRANSACTIONS_RELATIVE = ".imm/state/transactions";
|
|
736
738
|
var BATCH_STATE_RELATIVE = ".imm/state/batches";
|
|
737
739
|
function stateDatabasePath() {
|
|
@@ -790,6 +792,25 @@ function batchCommitEvidencePath(batchId, taskId) {
|
|
|
790
792
|
validateTaskId(taskId);
|
|
791
793
|
return `${BATCH_STATE_RELATIVE}/commits/${batchId}-${taskId}.json`;
|
|
792
794
|
}
|
|
795
|
+
function entryStatus(root, relativePath) {
|
|
796
|
+
const candidate = resolve(root, relativePath);
|
|
797
|
+
let stat;
|
|
798
|
+
try {
|
|
799
|
+
stat = lstatSync2(candidate);
|
|
800
|
+
} catch (error) {
|
|
801
|
+
const code = error.code;
|
|
802
|
+
if (code === "ENOENT" || code === "ENOTDIR")
|
|
803
|
+
return "absent";
|
|
804
|
+
return "other";
|
|
805
|
+
}
|
|
806
|
+
if (stat.isSymbolicLink())
|
|
807
|
+
return "symlink";
|
|
808
|
+
if (stat.isFile())
|
|
809
|
+
return "file";
|
|
810
|
+
if (stat.isDirectory())
|
|
811
|
+
return "directory";
|
|
812
|
+
return "other";
|
|
813
|
+
}
|
|
793
814
|
function listEntries(root, relativePath) {
|
|
794
815
|
const candidate = resolve(root, relativePath);
|
|
795
816
|
try {
|
|
@@ -798,6 +819,44 @@ function listEntries(root, relativePath) {
|
|
|
798
819
|
return null;
|
|
799
820
|
}
|
|
800
821
|
}
|
|
822
|
+
function readSmallFile(root, relativePath) {
|
|
823
|
+
const candidate = resolve(root, relativePath);
|
|
824
|
+
try {
|
|
825
|
+
const fd = openSync2(candidate, FS_CONSTANTS.O_RDONLY | (FS_CONSTANTS.O_NOFOLLOW ?? 0));
|
|
826
|
+
try {
|
|
827
|
+
const stat = fstatSync2(fd);
|
|
828
|
+
if (stat.size > 4 * 1024 * 1024)
|
|
829
|
+
throw new Error("file exceeds the inspection read bound");
|
|
830
|
+
return readFileSync(fd, "utf8");
|
|
831
|
+
} finally {
|
|
832
|
+
closeSync2(fd);
|
|
833
|
+
}
|
|
834
|
+
} catch {
|
|
835
|
+
return null;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
function inspectRetiredStateResidue(root) {
|
|
839
|
+
const tasksStatus = entryStatus(root, FILE_STORE_TASKS_RELATIVE);
|
|
840
|
+
if (tasksStatus !== "absent" && tasksStatus !== "directory")
|
|
841
|
+
throw new Error(`${FILE_STORE_TASKS_RELATIVE} is not a regular directory`);
|
|
842
|
+
const entries = tasksStatus === "absent" ? [] : listEntries(root, FILE_STORE_TASKS_RELATIVE);
|
|
843
|
+
if (entries === null)
|
|
844
|
+
throw new Error(`${FILE_STORE_TASKS_RELATIVE} is unreadable`);
|
|
845
|
+
const workspaceStatus = entryStatus(root, FILE_STORE_WORKSPACE_RELATIVE);
|
|
846
|
+
let owner = null;
|
|
847
|
+
if (workspaceStatus !== "absent") {
|
|
848
|
+
if (workspaceStatus !== "file")
|
|
849
|
+
throw new Error(`${FILE_STORE_WORKSPACE_RELATIVE} is not a regular file`);
|
|
850
|
+
const content = readSmallFile(root, FILE_STORE_WORKSPACE_RELATIVE);
|
|
851
|
+
if (content === null)
|
|
852
|
+
throw new Error(`${FILE_STORE_WORKSPACE_RELATIVE} is unreadable`);
|
|
853
|
+
const raw = JSON.parse(content);
|
|
854
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw) || raw.contract !== "assurance_kernel/workspace/v1" || raw.current_working !== null && (typeof raw.current_working !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(raw.current_working)) || raw.current_working === null && Object.keys(raw).some((key) => !["contract", "current_working"].includes(key)))
|
|
855
|
+
throw new Error(`${FILE_STORE_WORKSPACE_RELATIVE} has invalid workspace content`);
|
|
856
|
+
owner = raw.current_working;
|
|
857
|
+
}
|
|
858
|
+
return { task_entries: entries, workspace_owner: owner };
|
|
859
|
+
}
|
|
801
860
|
|
|
802
861
|
// plugins/immune-brain/runtime/kernel/sqlite_store.ts
|
|
803
862
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
@@ -2144,6 +2203,7 @@ function parseApprovalV2(value, index, violations, allowReviewRevision = false)
|
|
|
2144
2203
|
"actor_id",
|
|
2145
2204
|
"summary",
|
|
2146
2205
|
"advisory_findings",
|
|
2206
|
+
"reviewer_verdict_sha256",
|
|
2147
2207
|
...allowReviewRevision ? ["review_revision"] : []
|
|
2148
2208
|
], `record.approvals[${index}]`, violations);
|
|
2149
2209
|
const intentContentHash = stringAt(item.intent_content_hash, `record.approvals[${index}].intent_content_hash`, violations);
|
|
@@ -2164,7 +2224,8 @@ function parseApprovalV2(value, index, violations, allowReviewRevision = false)
|
|
|
2164
2224
|
...allowReviewRevision && item.review_revision !== undefined ? { review_revision: parseReviewRevisionIdentity(item.review_revision, `record.approvals[${index}].review_revision`, violations) } : {},
|
|
2165
2225
|
...item.advisory_findings !== undefined ? {
|
|
2166
2226
|
advisory_findings: arrayAt(item.advisory_findings, `record.approvals[${index}].advisory_findings`, violations).map((entry, advisoryIndex) => parseAdvisoryFinding(entry, `record.approvals[${index}].advisory_findings[${advisoryIndex}]`, violations))
|
|
2167
|
-
} : {}
|
|
2227
|
+
} : {},
|
|
2228
|
+
...item.reviewer_verdict_sha256 !== undefined ? { reviewer_verdict_sha256: stringAt(item.reviewer_verdict_sha256, `record.approvals[${index}].reviewer_verdict_sha256`, violations) } : {}
|
|
2168
2229
|
};
|
|
2169
2230
|
}
|
|
2170
2231
|
function parseAdvisoryFinding(value, path, violations) {
|
|
@@ -2211,6 +2272,7 @@ function parseAttestationV3(value, index, acceptanceIds, violations, allowReview
|
|
|
2211
2272
|
"summary",
|
|
2212
2273
|
"acceptance_results",
|
|
2213
2274
|
"advisory_findings",
|
|
2275
|
+
"reviewer_verdict_sha256",
|
|
2214
2276
|
...allowReviewRevision ? ["review_revision"] : []
|
|
2215
2277
|
], path, violations);
|
|
2216
2278
|
const kind = enumAt(item.kind, APPROVAL_KINDS, `${path}.kind`, violations);
|
|
@@ -2248,6 +2310,11 @@ function parseAttestationV3(value, index, acceptanceIds, violations, allowReview
|
|
|
2248
2310
|
const advisoryFindings = item.advisory_findings === undefined ? undefined : arrayAt(item.advisory_findings, `${path}.advisory_findings`, violations).map((entry, advisoryIndex) => parseAdvisoryFinding(entry, `${path}.advisory_findings[${advisoryIndex}]`, violations));
|
|
2249
2311
|
if (advisoryFindings !== undefined && kind !== "review")
|
|
2250
2312
|
violations.push(`${path}.advisory_findings is only valid on review attestations`);
|
|
2313
|
+
const reviewerVerdictSha256 = item.reviewer_verdict_sha256 === undefined ? undefined : stringAt(item.reviewer_verdict_sha256, `${path}.reviewer_verdict_sha256`, violations);
|
|
2314
|
+
if (reviewerVerdictSha256 !== undefined && kind !== "review")
|
|
2315
|
+
violations.push(`${path}.reviewer_verdict_sha256 is only valid on review attestations`);
|
|
2316
|
+
if (reviewerVerdictSha256 !== undefined && !SHA256_HEX.test(reviewerVerdictSha256))
|
|
2317
|
+
violations.push(`${path}.reviewer_verdict_sha256 must be sha256:<64 hex>`);
|
|
2251
2318
|
return {
|
|
2252
2319
|
id: stringAt(item.id, `${path}.id`, violations),
|
|
2253
2320
|
kind,
|
|
@@ -2259,7 +2326,8 @@ function parseAttestationV3(value, index, acceptanceIds, violations, allowReview
|
|
|
2259
2326
|
summary: stringAt(item.summary, `${path}.summary`, violations),
|
|
2260
2327
|
acceptance_results: acceptanceResults,
|
|
2261
2328
|
...reviewRevision ? { review_revision: reviewRevision } : {},
|
|
2262
|
-
...advisoryFindings !== undefined ? { advisory_findings: advisoryFindings } : {}
|
|
2329
|
+
...advisoryFindings !== undefined ? { advisory_findings: advisoryFindings } : {},
|
|
2330
|
+
...reviewerVerdictSha256 !== undefined ? { reviewer_verdict_sha256: reviewerVerdictSha256 } : {}
|
|
2263
2331
|
};
|
|
2264
2332
|
}
|
|
2265
2333
|
function parseTaskRecordAtVersion(raw, version) {
|
|
@@ -2643,6 +2711,11 @@ function readBoundActiveSpec(root, intent, required = false) {
|
|
|
2643
2711
|
throw error;
|
|
2644
2712
|
}
|
|
2645
2713
|
}
|
|
2714
|
+
function assertValidSpecBinding(intent) {
|
|
2715
|
+
const binding = inspectSpecBinding(intent);
|
|
2716
|
+
if (!binding.ok)
|
|
2717
|
+
throw new KernelInvariantError([binding.message]);
|
|
2718
|
+
}
|
|
2646
2719
|
function inspectSpecBinding(intent) {
|
|
2647
2720
|
const active = intent.scope_hint.filter((path) => ACTIVE_SPEC_RE.test(path));
|
|
2648
2721
|
const archived = intent.scope_hint.filter((path) => ARCHIVED_SPEC_RE.test(path));
|
|
@@ -2696,6 +2769,8 @@ function asTaskDiffSnapshot(value) {
|
|
|
2696
2769
|
return { diff_hash: value.diff_hash, changed_paths: value.changed_paths };
|
|
2697
2770
|
}
|
|
2698
2771
|
function resolveProjectedRisk(intent, changedPaths = []) {
|
|
2772
|
+
if (!inspectSpecBinding(intent).ok)
|
|
2773
|
+
return classifyTaskRisk(changedPaths, intent.risk);
|
|
2699
2774
|
const excluded = ownSidecarPaths(intent);
|
|
2700
2775
|
return classifyTaskRisk(changedPaths.filter((path) => !excluded.has(path)), intent.risk);
|
|
2701
2776
|
}
|
|
@@ -2754,6 +2829,27 @@ function completionDecision(intent, record, currentDiffHash, currentIntentConten
|
|
|
2754
2829
|
};
|
|
2755
2830
|
}
|
|
2756
2831
|
function projectTask(intent, record, currentDiffHash, currentIntentContentHash, changedPaths = []) {
|
|
2832
|
+
if (record.lifecycle === "active" && !inspectSpecBinding(intent).ok) {
|
|
2833
|
+
assertKernelInvariantsV3(intent, record);
|
|
2834
|
+
return {
|
|
2835
|
+
contract: "assurance_kernel/projection/v3",
|
|
2836
|
+
task_id: record.task_id,
|
|
2837
|
+
intent_revision: intent.revision,
|
|
2838
|
+
lifecycle: record.lifecycle,
|
|
2839
|
+
artifact_state: record.artifact_state,
|
|
2840
|
+
blocked: true,
|
|
2841
|
+
next_obligation: "revise_intent",
|
|
2842
|
+
complete: false,
|
|
2843
|
+
fresh_acceptance_ids: [],
|
|
2844
|
+
missing_acceptance_ids: intent.acceptance.map((item) => item.id),
|
|
2845
|
+
stale_attestation_ids: record.attestations.map((item) => item.id),
|
|
2846
|
+
missing_approval_kinds: REQUIRED_ATTESTATIONS[resolveProjectedRisk(intent, changedPaths)],
|
|
2847
|
+
blocking_finding_ids: record.findings.filter((item) => item.kind === "blocking" && item.status !== "resolved").map((item) => item.id),
|
|
2848
|
+
unresolved_user_decision_ids: record.findings.filter((item) => item.kind === "unresolved_user_decision" && item.status === "open").map((item) => item.id),
|
|
2849
|
+
replan_required_ids: record.findings.filter((item) => item.kind === "replan_required" && item.status === "open").map((item) => item.id),
|
|
2850
|
+
independence_violations: []
|
|
2851
|
+
};
|
|
2852
|
+
}
|
|
2757
2853
|
const decision = completionDecision(intent, record, currentDiffHash, currentIntentContentHash, changedPaths);
|
|
2758
2854
|
const blocked = decision.blocking_finding_ids.length > 0 || decision.unresolved_user_decision_ids.length > 0 || decision.replan_required_ids.length > 0 || decision.independence_violations.length > 0;
|
|
2759
2855
|
let nextObligation = "none";
|
|
@@ -3044,27 +3140,37 @@ function reduceTask(recordRaw, actionRaw, authorityAudit = null, changedPaths) {
|
|
|
3044
3140
|
const reviewRevision = approval.review_revision;
|
|
3045
3141
|
if (reviewRevision && approval.kind !== "review")
|
|
3046
3142
|
throw new KernelInvariantError(["review_revision is only valid on review approvals"]);
|
|
3143
|
+
if (approval.reviewer_verdict_sha256 && approval.kind !== "review")
|
|
3144
|
+
throw new KernelInvariantError(["reviewer_verdict_sha256 is only valid on review approvals"]);
|
|
3047
3145
|
if (approval.kind === "review") {
|
|
3048
3146
|
if (!reviewRevision)
|
|
3049
3147
|
throw new KernelInvariantError(["v4 review approval requires review_revision"]);
|
|
3050
3148
|
if (reviewRevision.base_head !== record.git_base_head)
|
|
3051
3149
|
throw new KernelInvariantError(["review_revision.base_head must equal the Enrollment git_base_head"]);
|
|
3052
3150
|
}
|
|
3053
|
-
const {
|
|
3151
|
+
const {
|
|
3152
|
+
review_revision: storedReviewRevision,
|
|
3153
|
+
advisory_findings: storedAdvisoryFindings,
|
|
3154
|
+
reviewer_verdict_sha256: storedReviewerDigest,
|
|
3155
|
+
...approvalWithoutExtras
|
|
3156
|
+
} = approval;
|
|
3054
3157
|
record.attestations.push({
|
|
3055
|
-
...
|
|
3158
|
+
...approvalWithoutExtras,
|
|
3056
3159
|
acceptance_results: approval.kind === "qa" ? record.intent_snapshot.acceptance.map((item) => ({
|
|
3057
3160
|
acceptance_id: item.id,
|
|
3058
3161
|
status: "passed",
|
|
3059
3162
|
summary: `host-attested QA: ${approval.summary}`
|
|
3060
3163
|
})) : [],
|
|
3061
|
-
...storedReviewRevision ? { review_revision: storedReviewRevision } : {}
|
|
3164
|
+
...storedReviewRevision ? { review_revision: storedReviewRevision } : {},
|
|
3165
|
+
...storedAdvisoryFindings ? { advisory_findings: storedAdvisoryFindings } : {},
|
|
3166
|
+
...storedReviewerDigest ? { reviewer_verdict_sha256: storedReviewerDigest } : {}
|
|
3062
3167
|
});
|
|
3063
3168
|
appendHistory(record, action, from, approval.id, authorityAudit);
|
|
3064
3169
|
break;
|
|
3065
3170
|
}
|
|
3066
3171
|
case "revise_intent":
|
|
3067
3172
|
case "approve_breaking_intent_revision": {
|
|
3173
|
+
assertValidSpecBinding(action.next_intent);
|
|
3068
3174
|
if (record.lifecycle !== "active")
|
|
3069
3175
|
throw new KernelInvariantError([
|
|
3070
3176
|
`cannot revise intent while lifecycle is ${record.lifecycle}`
|
|
@@ -3544,10 +3650,16 @@ function isRetiredFileProvablySuperseded(path, db, taskId) {
|
|
|
3544
3650
|
}
|
|
3545
3651
|
function assertNoRetiredFileStore(root, db, taskId) {
|
|
3546
3652
|
const canonical = canonicalRoot2(root);
|
|
3653
|
+
let residue;
|
|
3654
|
+
try {
|
|
3655
|
+
residue = inspectRetiredStateResidue(canonical);
|
|
3656
|
+
} catch (error) {
|
|
3657
|
+
throw new KernelStoreSecurityError(`retired file-store residue is invalid: ${error instanceof Error ? error.message : String(error)}`);
|
|
3658
|
+
}
|
|
3547
3659
|
const retired = [
|
|
3548
3660
|
[".imm/tasks", "pre-cutover task store"],
|
|
3549
3661
|
[".imm/workspace.json", "pre-cutover workspace owner"],
|
|
3550
|
-
[".imm/state/tasks", "task records"]
|
|
3662
|
+
...residue.task_entries.length > 0 ? [[".imm/state/tasks", "task records"]] : []
|
|
3551
3663
|
];
|
|
3552
3664
|
for (const [path, label] of retired) {
|
|
3553
3665
|
if (existsSync2(resolve5(canonical, path)))
|
|
@@ -3559,6 +3671,8 @@ function assertNoRetiredFileStore(root, db, taskId) {
|
|
|
3559
3671
|
];
|
|
3560
3672
|
for (const [path, label] of derived) {
|
|
3561
3673
|
const full = resolve5(canonical, path);
|
|
3674
|
+
if (path === FILE_STORE_WORKSPACE_RELATIVE && residue.workspace_owner === null)
|
|
3675
|
+
continue;
|
|
3562
3676
|
if (!existsSync2(full))
|
|
3563
3677
|
continue;
|
|
3564
3678
|
if (db === undefined || typeof taskId !== "string" || taskId.length === 0)
|
|
@@ -3575,10 +3689,16 @@ function assertNoRetiredFileStore(root, db, taskId) {
|
|
|
3575
3689
|
}
|
|
3576
3690
|
function retiredFileStoreConflict(root, db, taskId) {
|
|
3577
3691
|
const canonical = canonicalRoot2(root);
|
|
3692
|
+
let residue;
|
|
3693
|
+
try {
|
|
3694
|
+
residue = inspectRetiredStateResidue(canonical);
|
|
3695
|
+
} catch (error) {
|
|
3696
|
+
return `retired file-store residue is invalid: ${error instanceof Error ? error.message : String(error)}`;
|
|
3697
|
+
}
|
|
3578
3698
|
const authority = [
|
|
3579
3699
|
[".imm/tasks", "pre-cutover task store"],
|
|
3580
3700
|
[".imm/workspace.json", "pre-cutover workspace owner"],
|
|
3581
|
-
[".imm/state/tasks", "task records"]
|
|
3701
|
+
...residue.task_entries.length > 0 ? [[".imm/state/tasks", "task records"]] : []
|
|
3582
3702
|
];
|
|
3583
3703
|
for (const [path, label] of authority)
|
|
3584
3704
|
if (existsSync2(resolve5(canonical, path)))
|
|
@@ -3590,7 +3710,7 @@ function retiredFileStoreConflict(root, db, taskId) {
|
|
|
3590
3710
|
[FILE_STORE_WORKSPACE_RELATIVE, "workspace owner"]
|
|
3591
3711
|
];
|
|
3592
3712
|
for (const [path, label] of derived)
|
|
3593
|
-
if (existsSync2(resolve5(canonical, path)))
|
|
3713
|
+
if ((path !== FILE_STORE_WORKSPACE_RELATIVE || residue.workspace_owner !== null) && existsSync2(resolve5(canonical, path)))
|
|
3594
3714
|
return `retired file-store authority is present (${label}: ${path}); import it with the supported migration before mutating this worktree`;
|
|
3595
3715
|
}
|
|
3596
3716
|
const transactions = resolve5(canonical, FILE_STORE_TRANSACTIONS_RELATIVE);
|
|
@@ -5222,7 +5342,7 @@ function projectFromRecord(record, recordRevision, workspaceRevision, snapshot)
|
|
|
5222
5342
|
independence_violations: decision.independence_violations,
|
|
5223
5343
|
open_user_decision_count: openUserDecisionCount,
|
|
5224
5344
|
completion_ready: decision.complete,
|
|
5225
|
-
authorization: deriveAssuranceAuthorization({
|
|
5345
|
+
authorization: !inspectSpecBinding(intent).ok ? { state: "none", blocked: "invalid Spec binding requires a valid breaking intent revision" } : deriveAssuranceAuthorization({
|
|
5226
5346
|
next_obligation: decision.next_obligation,
|
|
5227
5347
|
open_user_decision_count: openUserDecisionCount
|
|
5228
5348
|
})
|
|
@@ -5555,6 +5675,7 @@ function createCanaryApplication(registry) {
|
|
|
5555
5675
|
const current = readTaskRecordRaw(input.root, input.task_id);
|
|
5556
5676
|
if (!current.record)
|
|
5557
5677
|
throw new KernelInvariantError([`task ${input.task_id} has no TaskRecord v3`]);
|
|
5678
|
+
assertValidSpecBinding(current.record.intent_snapshot);
|
|
5558
5679
|
const workspace = readWorkspaceStateRaw(input.root);
|
|
5559
5680
|
if (current.record.artifact_state === "frozen")
|
|
5560
5681
|
return { revision: current.revision, record: current.record, workspace };
|
|
@@ -5636,7 +5757,11 @@ function createCanaryApplication(registry) {
|
|
|
5636
5757
|
const diffHash = asTaskDiffSnapshot((input.diffProvider ?? taskDeliveryIdentity)(input.root, snapshot.record)).diff_hash;
|
|
5637
5758
|
if (operation.op === "stop" && !("capability" in operation))
|
|
5638
5759
|
throw new KernelInvariantError(["stop requires user authority capability"]);
|
|
5639
|
-
|
|
5760
|
+
if (operation.op === "approve_breaking_intent_revision")
|
|
5761
|
+
assertValidSpecBinding(operation.next_intent);
|
|
5762
|
+
else
|
|
5763
|
+
assertValidSpecBinding(snapshot.intent_snapshot);
|
|
5764
|
+
const hasBoundSpec = operation.op === "approve_breaking_intent_revision" ? boundSpecPath(operation.next_intent) !== undefined : boundSpecPath(snapshot.intent_snapshot) !== undefined;
|
|
5640
5765
|
if (operation.op === "complete" && hasBoundSpec && snapshot.record.artifact_state !== "frozen")
|
|
5641
5766
|
throw new KernelInvariantError(["complete requires frozen planning artifacts"]);
|
|
5642
5767
|
const artifactTransition = snapshot.record.artifact_state === "frozen" && (operation.op === "request_rework" || operation.op === "authorize_rework" || operation.op === "approve_breaking_intent_revision") ? transitionFor(input.root, snapshot.record, "restore") : operation.op === "stop" && snapshot.record.artifact_state !== "frozen" ? transitionFor(input.root, snapshot.record, "freeze", true) : undefined;
|
|
@@ -7169,8 +7294,9 @@ function parseVerdictEvidence(value, index) {
|
|
|
7169
7294
|
function parseAssuranceVerdict(input, snapshot) {
|
|
7170
7295
|
let raw;
|
|
7171
7296
|
if (typeof input === "string") {
|
|
7172
|
-
const
|
|
7173
|
-
|
|
7297
|
+
const first = input.indexOf("{");
|
|
7298
|
+
const last = input.lastIndexOf("}");
|
|
7299
|
+
const cleaned = first >= 0 && last >= first ? input.slice(first, last + 1) : "";
|
|
7174
7300
|
if (!cleaned)
|
|
7175
7301
|
throw new Error("reviewer returned no strict JSON verdict");
|
|
7176
7302
|
try {
|
|
@@ -7798,10 +7924,10 @@ class AssuranceCoordinator {
|
|
|
7798
7924
|
signal?.removeEventListener("abort", relayExternalAbort);
|
|
7799
7925
|
}
|
|
7800
7926
|
}
|
|
7801
|
-
async submitReview(taskId, ctx, verdictInput) {
|
|
7802
|
-
return this.withRecovery(taskId, ctx, await this.submitReviewOnce(taskId, ctx, verdictInput));
|
|
7927
|
+
async submitReview(taskId, ctx, verdictInput, options) {
|
|
7928
|
+
return this.withRecovery(taskId, ctx, await this.submitReviewOnce(taskId, ctx, verdictInput, options));
|
|
7803
7929
|
}
|
|
7804
|
-
async submitReviewOnce(taskId, ctx, verdictInput) {
|
|
7930
|
+
async submitReviewOnce(taskId, ctx, verdictInput, options) {
|
|
7805
7931
|
const unknown = this.unknownOperations.get(taskId);
|
|
7806
7932
|
if (unknown)
|
|
7807
7933
|
return { state: "settlement_unknown", operation: unknown.operation, operation_id: unknown.operationId, reason: unknown.reason };
|
|
@@ -7842,6 +7968,9 @@ class AssuranceCoordinator {
|
|
|
7842
7968
|
reservation.verdictCorrectionRequired = true;
|
|
7843
7969
|
return { state: "blocked", code: "verdict_invalid", reason: boundedAssuranceError(error) };
|
|
7844
7970
|
}
|
|
7971
|
+
const receiptError = options?.validateReceipt?.();
|
|
7972
|
+
if (receiptError)
|
|
7973
|
+
return { state: "blocked", reason: receiptError, ...options?.receiptRecoveryAction ? { recovery_action: options.receiptRecoveryAction } : {} };
|
|
7845
7974
|
const invocation = this.openInvocation(taskId);
|
|
7846
7975
|
this.releaseReviewReservation(taskId, reservation);
|
|
7847
7976
|
try {
|
|
@@ -7850,7 +7979,8 @@ class AssuranceCoordinator {
|
|
|
7850
7979
|
snapshot: reservation.snapshot,
|
|
7851
7980
|
verdict,
|
|
7852
7981
|
invocation,
|
|
7853
|
-
actorId: "parent-mediated-review"
|
|
7982
|
+
actorId: "parent-mediated-review",
|
|
7983
|
+
...options?.reviewer_verdict_sha256 ? { reviewer_verdict_sha256: options.reviewer_verdict_sha256 } : {}
|
|
7854
7984
|
});
|
|
7855
7985
|
} catch (error) {
|
|
7856
7986
|
const reason = boundedAssuranceError(error);
|
|
@@ -8143,7 +8273,8 @@ function createVerdictAuthority(options, registry = createMutationAuthorityRegis
|
|
|
8143
8273
|
actor_id: actorId,
|
|
8144
8274
|
summary: verdict.approval.summary,
|
|
8145
8275
|
...snapshot.role === "review" && snapshot.review_revision ? { review_revision: snapshot.review_revision } : {},
|
|
8146
|
-
...snapshot.role === "review" && advisories.length ? { advisory_findings: advisories } : {}
|
|
8276
|
+
...snapshot.role === "review" && advisories.length ? { advisory_findings: advisories } : {},
|
|
8277
|
+
...snapshot.role === "review" && input.reviewer_verdict_sha256 ? { reviewer_verdict_sha256: input.reviewer_verdict_sha256 } : {}
|
|
8147
8278
|
} : undefined;
|
|
8148
8279
|
const op = verdict.decision === "rework" ? "request_rework" : "record_approval";
|
|
8149
8280
|
const payload = approval ? { approval } : { findings };
|
|
@@ -9489,6 +9620,16 @@ var BATCH_REASONS = Object.freeze({
|
|
|
9489
9620
|
state: "rejected",
|
|
9490
9621
|
reason: (detail) => detail || "batch run rejected",
|
|
9491
9622
|
recovery_action: "delete or rename the conflicting branch, or commit working changes and retry in the current Host"
|
|
9623
|
+
},
|
|
9624
|
+
stale_batch_absent: {
|
|
9625
|
+
state: "rejected",
|
|
9626
|
+
reason: (detail) => `no retirable batch record to dispose: ${detail}`,
|
|
9627
|
+
recovery_action: "start a batch for this Initiative, or inspect the batch records under .imm/state/batches in the current Host"
|
|
9628
|
+
},
|
|
9629
|
+
stale_batch_in_flight: {
|
|
9630
|
+
state: "blocked",
|
|
9631
|
+
reason: (detail) => `a batch child is still mid-flight, so the record cannot be disposed: ${detail}`,
|
|
9632
|
+
recovery_action: "settle or stop the in-flight child through its own task, then dispose the batch in the current Host"
|
|
9492
9633
|
}
|
|
9493
9634
|
});
|
|
9494
9635
|
function batchReason(key, detail = "") {
|
|
@@ -10736,7 +10877,8 @@ var BATCH_RUN_STATES = new Set([
|
|
|
10736
10877
|
"completed",
|
|
10737
10878
|
"budget_stopped",
|
|
10738
10879
|
"failed",
|
|
10739
|
-
"rejected"
|
|
10880
|
+
"rejected",
|
|
10881
|
+
"superseded"
|
|
10740
10882
|
]);
|
|
10741
10883
|
var ISO_TIMESTAMP_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
|
|
10742
10884
|
function isCanonicalTimestamp(value) {
|
|
@@ -10817,7 +10959,7 @@ function validateRecordShape(value, batchId) {
|
|
|
10817
10959
|
if (child.state === "committed" && child.commit !== null && !record.commits.includes(child.commit))
|
|
10818
10960
|
throw new Error(`batch run state ${batchId} child ${child.task_id} commit is missing from commits`);
|
|
10819
10961
|
}
|
|
10820
|
-
if ((record.batch_state === "budget_stopped" || record.batch_state === "failed" || record.batch_state === "rejected") && record.children.some((child) => child.state === "enrolled" || child.state === "settled"))
|
|
10962
|
+
if ((record.batch_state === "budget_stopped" || record.batch_state === "failed" || record.batch_state === "rejected" || record.batch_state === "superseded") && record.children.some((child) => child.state === "enrolled" || child.state === "settled"))
|
|
10821
10963
|
throw new Error(`batch run state ${batchId} is ${String(record.batch_state)} but a child is still mid-flight`);
|
|
10822
10964
|
if (record.batch_state === "completed" && record.children.some((child) => child.state !== "committed"))
|
|
10823
10965
|
throw new Error(`batch run state ${batchId} is completed but a child is not committed`);
|
|
@@ -10911,6 +11053,21 @@ function replaceBatchRunState(root, expected, next, validate) {
|
|
|
10911
11053
|
return stored;
|
|
10912
11054
|
});
|
|
10913
11055
|
}
|
|
11056
|
+
function replaceAnyBatchRunState(root, expected, next, validate) {
|
|
11057
|
+
const path = statePath(next.batch_id);
|
|
11058
|
+
if (isLaneBatchRecord(next))
|
|
11059
|
+
validateLaneRecordShape(next, next.batch_id);
|
|
11060
|
+
else
|
|
11061
|
+
validateRecordShape(next, next.batch_id);
|
|
11062
|
+
return withKernelStoreLock(root, () => {
|
|
11063
|
+
if (!readSecureProjectBytes(root, path).equals(expected))
|
|
11064
|
+
throw new Error("batch state CAS mismatch");
|
|
11065
|
+
validate();
|
|
11066
|
+
const stored = withoutRetiredClock({ ...next, updated_at: new Date().toISOString() });
|
|
11067
|
+
writeFileAtomically(root, path, canonicalBytes(stored));
|
|
11068
|
+
return stored;
|
|
11069
|
+
});
|
|
11070
|
+
}
|
|
10914
11071
|
function writeBatchRunState(root, record) {
|
|
10915
11072
|
const path = statePath(record.batch_id);
|
|
10916
11073
|
validateRecordShape(record, record.batch_id);
|
|
@@ -10991,7 +11148,7 @@ function validateLaneRecordShape(value, batchId) {
|
|
|
10991
11148
|
throw new Error(`batch run state ${batchId} child ${child.task_id} has an invalid qa_failures`);
|
|
10992
11149
|
const lane = child.lane;
|
|
10993
11150
|
if (lane !== null) {
|
|
10994
|
-
if (typeof lane !== "object" || typeof lane.path !== "string" || !lane.path || typeof lane.branch !== "string" || !lane.branch || typeof lane.base_head !== "string" || !lane.base_head || lane.lane_commit !== null && typeof lane.lane_commit !== "string")
|
|
11151
|
+
if (typeof lane !== "object" || typeof lane.path !== "string" || !lane.path || typeof lane.branch !== "string" || !lane.branch || typeof lane.base_head !== "string" || !lane.base_head || lane.lane_commit !== null && typeof lane.lane_commit !== "string" || lane.run_id !== undefined && lane.run_id !== null && (typeof lane.run_id !== "string" || !lane.run_id))
|
|
10995
11152
|
throw new Error(`batch run state ${batchId} child ${child.task_id} has an invalid lane`);
|
|
10996
11153
|
}
|
|
10997
11154
|
if (LANE_BOUND_STATES.has(child.state) && lane === null)
|
|
@@ -11018,7 +11175,7 @@ function validateLaneRecordShape(value, batchId) {
|
|
|
11018
11175
|
throw new Error(`batch run state ${batchId} child ${child.task_id} commit is missing from commits`);
|
|
11019
11176
|
}
|
|
11020
11177
|
const inFlight = (child) => child.state === "lane_admitted" || child.state === "enrolled" || child.state === "settled" || child.state === "lane_committed";
|
|
11021
|
-
if ((record.batch_state === "budget_stopped" || record.batch_state === "failed" || record.batch_state === "rejected") && record.children.some(inFlight))
|
|
11178
|
+
if ((record.batch_state === "budget_stopped" || record.batch_state === "failed" || record.batch_state === "rejected" || record.batch_state === "superseded") && record.children.some(inFlight))
|
|
11022
11179
|
throw new Error(`batch run state ${batchId} is ${String(record.batch_state)} but a child is still mid-flight`);
|
|
11023
11180
|
if (record.batch_state === "completed" && record.children.some((child) => child.state !== "integrated" && child.state !== "released"))
|
|
11024
11181
|
throw new Error(`batch run state ${batchId} is completed but a child is not integrated`);
|
|
@@ -11111,7 +11268,7 @@ function canonicalReportBytes(report) {
|
|
|
11111
11268
|
`;
|
|
11112
11269
|
}
|
|
11113
11270
|
function isTerminalBatchState(state) {
|
|
11114
|
-
return state === "completed" || state === "budget_stopped" || state === "failed" || state === "rejected";
|
|
11271
|
+
return state === "completed" || state === "budget_stopped" || state === "failed" || state === "rejected" || state === "superseded";
|
|
11115
11272
|
}
|
|
11116
11273
|
|
|
11117
11274
|
// plugins/immune-brain/runtime/unattended/batch_reconfirmation.ts
|
|
@@ -11784,15 +11941,7 @@ function findExistingActiveBatch(root, initiativeSlug) {
|
|
|
11784
11941
|
continue;
|
|
11785
11942
|
if (candidate.initiative_slug !== initiativeSlug)
|
|
11786
11943
|
continue;
|
|
11787
|
-
const validStates = new Set(
|
|
11788
|
-
"prepared",
|
|
11789
|
-
"running",
|
|
11790
|
-
"needs_human",
|
|
11791
|
-
"completed",
|
|
11792
|
-
"budget_stopped",
|
|
11793
|
-
"failed",
|
|
11794
|
-
"rejected"
|
|
11795
|
-
]);
|
|
11944
|
+
const validStates = new Set(BATCH_RUN_STATES);
|
|
11796
11945
|
if (typeof candidate.batch_id !== "string" || typeof candidate.base_head !== "string" || !Array.isArray(candidate.children) || !validStates.has(candidate.batch_state)) {
|
|
11797
11946
|
return { corrupt: true, path: file };
|
|
11798
11947
|
}
|
|
@@ -12149,7 +12298,7 @@ async function projectBatchPreflight(options) {
|
|
|
12149
12298
|
try {
|
|
12150
12299
|
reconfirmation = await captureBatchReconfirmation(root, activeRecord, planSurface.surface.recovery_children);
|
|
12151
12300
|
} catch {
|
|
12152
|
-
return reject("plan_projection_failed", "batch plan reconfirmation is not eligible");
|
|
12301
|
+
return reject("plan_projection_failed", activeRecord.commits.length ? `batch plan changed after ${activeRecord.commits.length} recorded child commit(s). The old authorization cannot execute the revised plan. Preserve the batch record, report, commit evidence and audit; inspect each remaining child's Kernel run before an explicit imm-run handoff. Do not re-enroll settled children or manufacture batch trailers. To close the parked record explicitly, dispose it with the retire_stale_batch Tool for this Initiative; a child that is still mid-flight must settle first. This rejection neither terminates the old batch nor authorizes the revised plan.` : "batch plan reconfirmation is not eligible");
|
|
12153
12302
|
}
|
|
12154
12303
|
}
|
|
12155
12304
|
const projection = {
|
|
@@ -13257,14 +13406,23 @@ function serialGitOf(input) {
|
|
|
13257
13406
|
return input.git ?? defaultSerialGit();
|
|
13258
13407
|
}
|
|
13259
13408
|
function releaseHandoffs(input, record, lanes) {
|
|
13409
|
+
const lineage = classifyBatchLineage({
|
|
13410
|
+
root: input.root,
|
|
13411
|
+
branch: record.branch ?? "",
|
|
13412
|
+
expectedHead: expectedBatchHead(record),
|
|
13413
|
+
childCommits: record.commits,
|
|
13414
|
+
batchId: record.batch_id
|
|
13415
|
+
});
|
|
13416
|
+
if (lineage.kind === "broken")
|
|
13417
|
+
return [];
|
|
13260
13418
|
const handoffs = [];
|
|
13261
13419
|
for (const child of record.children) {
|
|
13262
13420
|
if (child.state !== "integrated" || !child.lane)
|
|
13263
13421
|
continue;
|
|
13264
13422
|
const facts = lanes.inspectLane(input.root, child.lane.path);
|
|
13265
|
-
if (!facts.exists || !facts.same_repository || !facts.clean || facts.branch !== child.lane.branch)
|
|
13423
|
+
if (!facts.exists || !facts.same_repository || !facts.is_worktree_root || !facts.clean || facts.branch !== child.lane.branch || facts.active_claim_task_id !== null)
|
|
13266
13424
|
continue;
|
|
13267
|
-
if (!lanes.auditReachable({ root: input.root, head:
|
|
13425
|
+
if (!lanes.auditReachable({ root: input.root, head: lineage.head, task_id: child.task_id }))
|
|
13268
13426
|
continue;
|
|
13269
13427
|
handoffs.push({ role: "lane-steward", action: "release", task_id: child.task_id, lane_branch: child.lane.branch });
|
|
13270
13428
|
}
|
|
@@ -13315,6 +13473,7 @@ var TERMINAL_NEXT_ACTIONS = {
|
|
|
13315
13473
|
budget_stopped: "The child budget stopped new enrollments; re-confirm to continue under a new authorization.",
|
|
13316
13474
|
failed: "A lineage failure stopped the batch; inspect the failing child and the branch state.",
|
|
13317
13475
|
rejected: "The batch was rejected before any enrollment; correct the stated reason and re-confirm.",
|
|
13476
|
+
superseded: "The plan moved past this record and an explicit disposition retired it; it grants no handoff and no child delivery.",
|
|
13318
13477
|
needs_human: LANE_NEEDS_HUMAN_NEXT_ACTION,
|
|
13319
13478
|
running: "The batch is still running; no terminal report is due yet.",
|
|
13320
13479
|
prepared: "The batch is prepared but not started."
|
|
@@ -13335,8 +13494,9 @@ function laneReport(record, reason, nextAction, extra = {}) {
|
|
|
13335
13494
|
created_at: record.updated_at
|
|
13336
13495
|
};
|
|
13337
13496
|
}
|
|
13338
|
-
function laneRejectionReport(record, reason, nextAction) {
|
|
13339
|
-
|
|
13497
|
+
function laneRejectionReport(input, record, reason, nextAction) {
|
|
13498
|
+
const handoffs = releaseHandoffs(input, record, laneGitOf(input));
|
|
13499
|
+
return laneReport({ ...record, batch_state: "rejected" }, reason, handoffs.length ? `${nextAction}. Check each release handoff with the Lane Steward.` : nextAction, { handoffs });
|
|
13340
13500
|
}
|
|
13341
13501
|
function finalizeLane(root, record, reason, nextAction, extra = {}) {
|
|
13342
13502
|
const report = laneReport(record, reason, nextAction, extra);
|
|
@@ -13449,8 +13609,34 @@ async function runLaneBatch(input, persisted) {
|
|
|
13449
13609
|
return finalizeLane(input.root, record, `terminal state already reached: ${record.batch_state}`, "", {
|
|
13450
13610
|
handoffs: record.batch_state === "completed" ? releaseHandoffs(input, record, lanes) : []
|
|
13451
13611
|
});
|
|
13452
|
-
if (record.batch_state === "needs_human")
|
|
13453
|
-
|
|
13612
|
+
if (record.batch_state === "needs_human") {
|
|
13613
|
+
const invalid = validateNewAuthorization(input);
|
|
13614
|
+
if (invalid || Date.parse(input.confirmation_time) <= Date.parse(record.confirmation_time))
|
|
13615
|
+
return laneReport(record, invalid ?? "the parked batch requires a fresh literal-user confirmation", LANE_NEEDS_HUMAN_NEXT_ACTION);
|
|
13616
|
+
const resumed = [];
|
|
13617
|
+
for (const child of record.children) {
|
|
13618
|
+
if (child.state !== "needs_human") {
|
|
13619
|
+
resumed.push(child);
|
|
13620
|
+
continue;
|
|
13621
|
+
}
|
|
13622
|
+
if (!child.lane?.run_id)
|
|
13623
|
+
return laneReport(record, "parked Lane has no recorded run identity; inspect it without re-enrolling", LANE_NEEDS_HUMAN_NEXT_ACTION);
|
|
13624
|
+
const facts = lanes.inspectLane(input.root, child.lane.path);
|
|
13625
|
+
const fresh = await input.kernel.projectTask(child.lane.path, child.task_id);
|
|
13626
|
+
if (!facts.exists || !facts.same_repository || !facts.is_worktree_root || facts.branch !== child.lane.branch || fresh.error !== null || fresh.projection.run_id !== child.lane.run_id || fresh.projection.open_user_decision_count > 0 || fresh.projection.replan_required_ids.length > 0)
|
|
13627
|
+
return laneReport(record, "parked Lane identity or decision is unresolved", LANE_NEEDS_HUMAN_NEXT_ACTION);
|
|
13628
|
+
const state = fresh.projection.lifecycle === "done" && fresh.projection.completion_ready ? child.lane.lane_commit ? "lane_committed" : "settled" : fresh.projection.lifecycle === "active" && fresh.claim?.task_id === child.task_id && (facts.active_claim_task_id === null || facts.active_claim_task_id === child.task_id) ? "enrolled" : null;
|
|
13629
|
+
if (!state)
|
|
13630
|
+
return laneReport(record, "parked child has no recoverable owned run", LANE_NEEDS_HUMAN_NEXT_ACTION);
|
|
13631
|
+
resumed.push({ ...child, state, reason: null, qa_failures: 0 });
|
|
13632
|
+
}
|
|
13633
|
+
record = {
|
|
13634
|
+
...record,
|
|
13635
|
+
batch_state: "running",
|
|
13636
|
+
confirmation_time: input.confirmation_time,
|
|
13637
|
+
children: resumed.map((child) => child.state === "skipped_blocked" ? { ...child, state: "pending", reason: null } : child)
|
|
13638
|
+
};
|
|
13639
|
+
}
|
|
13454
13640
|
} else {
|
|
13455
13641
|
const invalid = validateNewAuthorization(input);
|
|
13456
13642
|
if (invalid)
|
|
@@ -13477,6 +13663,7 @@ async function runLaneBatch(input, persisted) {
|
|
|
13477
13663
|
const persist = () => {
|
|
13478
13664
|
record = writeBatchLaneRunState(input.root, record);
|
|
13479
13665
|
};
|
|
13666
|
+
const resumedPark = persisted?.batch_state === "needs_human" && record.batch_state === "running";
|
|
13480
13667
|
if (record.batch_state === "prepared") {
|
|
13481
13668
|
record.batch_state = "running";
|
|
13482
13669
|
persist();
|
|
@@ -13526,6 +13713,8 @@ async function runLaneBatch(input, persisted) {
|
|
|
13526
13713
|
} catch (error) {
|
|
13527
13714
|
return failLineage(input.root, record, error instanceof Error ? error.message : String(error));
|
|
13528
13715
|
}
|
|
13716
|
+
if (resumedPark)
|
|
13717
|
+
persist();
|
|
13529
13718
|
const refusals = [];
|
|
13530
13719
|
let parkedMessage = null;
|
|
13531
13720
|
const reviewOpen = [];
|
|
@@ -13542,7 +13731,10 @@ async function runLaneBatch(input, persisted) {
|
|
|
13542
13731
|
task_id: taskId,
|
|
13543
13732
|
batch: { registry: input.registry, capability: input.capability, binding: { batch_id: record.batch_id, expected_head: lane.base_head } }
|
|
13544
13733
|
});
|
|
13545
|
-
|
|
13734
|
+
const fresh = await input.kernel.projectTask(lane.path, taskId);
|
|
13735
|
+
if (fresh.error !== null || !fresh.projection.run_id || fresh.claim?.task_id !== taskId)
|
|
13736
|
+
throw new Error("enrolled Lane has no observable owned run");
|
|
13737
|
+
record.children = record.children.map((c) => c.task_id === taskId ? { ...c, state: "enrolled", reason: null, lane: { ...lane, run_id: fresh.projection.run_id } } : c);
|
|
13546
13738
|
persist();
|
|
13547
13739
|
return null;
|
|
13548
13740
|
} catch (error) {
|
|
@@ -13568,10 +13760,14 @@ async function runLaneBatch(input, persisted) {
|
|
|
13568
13760
|
parkChild(child.task_id, "batch_lane_lost");
|
|
13569
13761
|
continue;
|
|
13570
13762
|
}
|
|
13763
|
+
if (child.lane.run_id && fresh.projection.run_id !== child.lane.run_id) {
|
|
13764
|
+
parkChild(child.task_id, "batch_lane_run_mismatch");
|
|
13765
|
+
continue;
|
|
13766
|
+
}
|
|
13571
13767
|
const holdsClaim = fresh.claim !== null && fresh.claim.task_id === child.task_id;
|
|
13572
13768
|
if (child.state === "lane_admitted") {
|
|
13573
13769
|
if (holdsClaim) {
|
|
13574
|
-
record.children = record.children.map((c) => c.task_id === child.task_id ? { ...c, state: "enrolled", reason: "adopted existing lane claim after interruption" } : c);
|
|
13770
|
+
record.children = record.children.map((c) => c.task_id === child.task_id ? { ...c, state: "enrolled", reason: "adopted existing lane claim after interruption", lane: { ...child.lane, run_id: fresh.projection.run_id } } : c);
|
|
13575
13771
|
persist();
|
|
13576
13772
|
} else {
|
|
13577
13773
|
const failed = await enrollInLane(child.task_id);
|
|
@@ -13801,6 +13997,7 @@ var TERMINAL_NEXT_ACTIONS2 = {
|
|
|
13801
13997
|
budget_stopped: "The child budget stopped new enrollments; re-confirm to continue under a new authorization.",
|
|
13802
13998
|
failed: "A commit or lineage failure stopped the batch; inspect the failing child and the branch state.",
|
|
13803
13999
|
rejected: "The batch was rejected before any enrollment; correct the stated reason and re-confirm.",
|
|
14000
|
+
superseded: "The plan moved past this record and an explicit disposition retired it; it grants no handoff and no child delivery.",
|
|
13804
14001
|
needs_human: "A parked child needs a human decision; resolve it, then re-confirm to continue.",
|
|
13805
14002
|
running: "The batch is still running; no terminal report is due yet.",
|
|
13806
14003
|
prepared: "The batch is prepared but not started."
|
|
@@ -13999,7 +14196,7 @@ function rejectionReport(input, reason) {
|
|
|
13999
14196
|
}
|
|
14000
14197
|
})();
|
|
14001
14198
|
if (persisted !== null && isLaneBatchRecord(persisted))
|
|
14002
|
-
return laneRejectionReport(persisted, reason, "settle the reported kernel store condition and retry in the current Host");
|
|
14199
|
+
return laneRejectionReport(input, persisted, reason, "settle the reported kernel store condition and retry in the current Host");
|
|
14003
14200
|
return reportFor({
|
|
14004
14201
|
...persisted ?? prepareBatchRunState({ ...input, children: [], now: input.now }),
|
|
14005
14202
|
batch_state: "rejected"
|
|
@@ -14460,22 +14657,126 @@ function createBatchKernelPort(input) {
|
|
|
14460
14657
|
};
|
|
14461
14658
|
}
|
|
14462
14659
|
|
|
14463
|
-
// plugins/immune-brain/runtime/
|
|
14464
|
-
|
|
14465
|
-
|
|
14660
|
+
// plugins/immune-brain/runtime/unattended/batch_disposition.ts
|
|
14661
|
+
var SERIAL_IN_FLIGHT = new Set(["enrolled", "settled"]);
|
|
14662
|
+
var LANE_IN_FLIGHT = new Set(["lane_admitted", "enrolled", "settled", "lane_committed"]);
|
|
14663
|
+
var RETIRE_REASON = "Retired by an explicit literal-user disposition: the batch plan moved past this record, so its authorization could neither reconfirm nor continue. " + "Recorded child commits, child states and the batch branch are preserved as evidence. " + "Children this batch never executed carry no commit, no batch trailer and no approval; nothing infers their delivery from this record.";
|
|
14664
|
+
var RETIRE_NEXT_ACTION = "The Initiative has no active batch. Run each remaining child with imm-run under its own authorization, or start a new batch for this Initiative after checking out its batch branch.";
|
|
14665
|
+
function childEvidenceOf(record) {
|
|
14666
|
+
if (record.contract === "assurance_kernel/batch_run_state/v2")
|
|
14667
|
+
return record.children.map((child) => ({
|
|
14668
|
+
task_id: child.task_id,
|
|
14669
|
+
slice_id: child.slice_id,
|
|
14670
|
+
state: child.state,
|
|
14671
|
+
commit: child.commit,
|
|
14672
|
+
lane_branch: child.lane?.branch ?? null
|
|
14673
|
+
}));
|
|
14674
|
+
return record.children.map((child) => ({
|
|
14675
|
+
task_id: child.task_id,
|
|
14676
|
+
slice_id: child.slice_id,
|
|
14677
|
+
state: child.state,
|
|
14678
|
+
commit: child.commit,
|
|
14679
|
+
lane_branch: null
|
|
14680
|
+
}));
|
|
14466
14681
|
}
|
|
14467
|
-
function
|
|
14468
|
-
|
|
14682
|
+
function inFlightChildOf(record) {
|
|
14683
|
+
const inFlight = record.contract === "assurance_kernel/batch_run_state/v2" ? LANE_IN_FLIGHT : SERIAL_IN_FLIGHT;
|
|
14684
|
+
return record.children.find((child) => inFlight.has(child.state)) ?? null;
|
|
14469
14685
|
}
|
|
14470
|
-
function
|
|
14471
|
-
|
|
14472
|
-
|
|
14473
|
-
|
|
14686
|
+
function readStaleBatchLookup(root, initiativeSlug) {
|
|
14687
|
+
const found = findExistingActiveBatch(root, initiativeSlug);
|
|
14688
|
+
if (found === null) {
|
|
14689
|
+
const settled = findSettledBatchRecord(root, initiativeSlug);
|
|
14690
|
+
if (settled)
|
|
14691
|
+
return { kind: "terminal", batch_id: settled.batch_id };
|
|
14692
|
+
return { kind: "none" };
|
|
14693
|
+
}
|
|
14694
|
+
if (found.corrupt)
|
|
14695
|
+
return { kind: "corrupt", path: found.path };
|
|
14696
|
+
const record = found.record;
|
|
14697
|
+
if (isTerminalBatchState(record.batch_state))
|
|
14698
|
+
return { kind: "terminal", batch_id: record.batch_id };
|
|
14699
|
+
const inFlight = inFlightChildOf(record);
|
|
14700
|
+
if (inFlight)
|
|
14701
|
+
return { kind: "in_flight", batch_id: record.batch_id, task_id: inFlight.task_id };
|
|
14702
|
+
return {
|
|
14703
|
+
kind: "retirable",
|
|
14704
|
+
record,
|
|
14705
|
+
facts: {
|
|
14706
|
+
batch_id: record.batch_id,
|
|
14707
|
+
initiative_slug: record.initiative_slug,
|
|
14708
|
+
batch_state: record.batch_state,
|
|
14709
|
+
plan_digest: record.plan_digest,
|
|
14710
|
+
branch: record.branch ?? `imm/${record.initiative_slug}`,
|
|
14711
|
+
base_head: record.base_head,
|
|
14712
|
+
confirmation_time: record.confirmation_time,
|
|
14713
|
+
recorded_commits: [...record.commits],
|
|
14714
|
+
children: childEvidenceOf(record)
|
|
14715
|
+
}
|
|
14716
|
+
};
|
|
14474
14717
|
}
|
|
14718
|
+
async function retireStaleBatch(options) {
|
|
14719
|
+
const { root, initiative_slug: initiativeSlug, now } = options;
|
|
14720
|
+
const lookup = readStaleBatchLookup(root, initiativeSlug);
|
|
14721
|
+
if (lookup.kind === "none")
|
|
14722
|
+
return { outcome: "rejected", rejection: batchReason("stale_batch_absent", `Initiative ${initiativeSlug} has no active batch`) };
|
|
14723
|
+
if (lookup.kind === "corrupt")
|
|
14724
|
+
return { outcome: "rejected", rejection: batchReason("batch_state_unreadable", lookup.path) };
|
|
14725
|
+
if (lookup.kind === "terminal")
|
|
14726
|
+
return {
|
|
14727
|
+
outcome: "rejected",
|
|
14728
|
+
rejection: batchReason("stale_batch_absent", `batch ${lookup.batch_id} already reached a terminal state`)
|
|
14729
|
+
};
|
|
14730
|
+
if (lookup.kind === "in_flight")
|
|
14731
|
+
return {
|
|
14732
|
+
outcome: "rejected",
|
|
14733
|
+
rejection: batchReason("stale_batch_in_flight", `batch ${lookup.batch_id}, child ${lookup.task_id}`)
|
|
14734
|
+
};
|
|
14735
|
+
const { record, facts } = lookup;
|
|
14736
|
+
const expected = readSecureProjectBytes(root, batchStatePath(record.batch_id));
|
|
14737
|
+
const decision = await options.gate(facts);
|
|
14738
|
+
if (decision.kind === "host_rejection")
|
|
14739
|
+
return { outcome: "host_rejection", value: decision.value };
|
|
14740
|
+
if (!readSecureProjectBytes(root, batchStatePath(record.batch_id)).equals(expected))
|
|
14741
|
+
return { outcome: "rejected", rejection: batchReason("plan_changed", record.batch_id) };
|
|
14742
|
+
const retired = { ...record, batch_state: "superseded" };
|
|
14743
|
+
const stored = replaceAnyBatchRunState(root, expected, retired, () => {
|
|
14744
|
+
if (!readSecureProjectBytes(root, batchStatePath(record.batch_id)).equals(expected))
|
|
14745
|
+
throw new Error("batch state changed before the disposition was applied");
|
|
14746
|
+
});
|
|
14747
|
+
const report = stored.contract === "assurance_kernel/batch_run_state/v2" ? writeBatchRunReport(root, {
|
|
14748
|
+
contract: "assurance_kernel/batch_run_report/v1",
|
|
14749
|
+
batch_id: stored.batch_id,
|
|
14750
|
+
initiative_slug: stored.initiative_slug,
|
|
14751
|
+
batch_state: stored.batch_state,
|
|
14752
|
+
max_parallel: stored.max_parallel,
|
|
14753
|
+
children: stored.children,
|
|
14754
|
+
commits: stored.commits,
|
|
14755
|
+
reason: RETIRE_REASON,
|
|
14756
|
+
handoffs: [],
|
|
14757
|
+
next_action: RETIRE_NEXT_ACTION,
|
|
14758
|
+
created_at: now
|
|
14759
|
+
}) : writeBatchRunReport(root, {
|
|
14760
|
+
contract: "assurance_kernel/batch_run_report/v1",
|
|
14761
|
+
batch_id: stored.batch_id,
|
|
14762
|
+
initiative_slug: stored.initiative_slug,
|
|
14763
|
+
batch_state: stored.batch_state,
|
|
14764
|
+
children: stored.children,
|
|
14765
|
+
commits: stored.commits,
|
|
14766
|
+
reason: RETIRE_REASON,
|
|
14767
|
+
next_action: RETIRE_NEXT_ACTION,
|
|
14768
|
+
created_at: now
|
|
14769
|
+
});
|
|
14770
|
+
return { outcome: "retired", batch_id: stored.batch_id, record: stored, report };
|
|
14771
|
+
}
|
|
14772
|
+
|
|
14773
|
+
// plugins/immune-brain/runtime/assurance/review_mediation.ts
|
|
14774
|
+
import { createHash as createHash21 } from "node:crypto";
|
|
14475
14775
|
function extractVerdictJson(input) {
|
|
14476
14776
|
if (typeof input === "string") {
|
|
14477
|
-
const
|
|
14478
|
-
|
|
14777
|
+
const first = input.indexOf("{");
|
|
14778
|
+
const last = input.lastIndexOf("}");
|
|
14779
|
+
const cleaned = first >= 0 && last >= first ? input.slice(first, last + 1) : "";
|
|
14479
14780
|
if (!cleaned)
|
|
14480
14781
|
return null;
|
|
14481
14782
|
try {
|
|
@@ -14499,36 +14800,72 @@ function verdictFingerprint(raw) {
|
|
|
14499
14800
|
findings: raw.findings ?? null
|
|
14500
14801
|
});
|
|
14501
14802
|
}
|
|
14803
|
+
function digestOfReviewerBytes(bytes) {
|
|
14804
|
+
return `sha256:${createHash21("sha256").update(bytes).digest("hex")}`;
|
|
14805
|
+
}
|
|
14502
14806
|
var RELEASED_REVIEW_RECOVERY = "Call advance_assurance to obtain a new Review reservation, then dispatch one fresh reviewer with the returned envelope unchanged";
|
|
14503
14807
|
var RETAINED_REVIEW_RECOVERY = "Wait for the dispatched reviewer to finish, then call submit_review again with its verdict; do not dispatch or continue another reviewer";
|
|
14504
|
-
var MISMATCH_REVIEW_RECOVERY = "Resubmit the reviewer's verdict exactly as the reviewer returned it";
|
|
14808
|
+
var MISMATCH_REVIEW_RECOVERY = "Resubmit without a verdict to apply the observed reviewer receipt, or resubmit the reviewer's verdict exactly as the reviewer returned it";
|
|
14505
14809
|
function withReviewRecovery(result, recovery_action) {
|
|
14506
14810
|
if (result.state !== "blocked" || result.code === "verdict_invalid")
|
|
14507
14811
|
return result;
|
|
14508
14812
|
return { ...result, recovery_action };
|
|
14509
14813
|
}
|
|
14510
|
-
async function
|
|
14511
|
-
if (
|
|
14512
|
-
|
|
14513
|
-
|
|
14814
|
+
async function submitMediatedReview(coordinator, ctx, taskId, verdictInput, inspect) {
|
|
14815
|
+
if (coordinator.active(taskId)?.state === "settlement_unknown") {
|
|
14816
|
+
return coordinator.submitReview(taskId, ctx, verdictInput);
|
|
14817
|
+
}
|
|
14818
|
+
const observed = inspect();
|
|
14514
14819
|
if (!observed.ok) {
|
|
14515
14820
|
if (observed.release)
|
|
14516
14821
|
return withReviewRecovery(coordinator.abandonReview(taskId, observed.reason), RELEASED_REVIEW_RECOVERY);
|
|
14517
14822
|
return { state: "blocked", reason: observed.reason, recovery_action: RETAINED_REVIEW_RECOVERY };
|
|
14518
14823
|
}
|
|
14824
|
+
const receiptBytes = observed.receipt.result;
|
|
14825
|
+
const options = {
|
|
14826
|
+
receiptRecoveryAction: RETAINED_REVIEW_RECOVERY,
|
|
14827
|
+
reviewer_verdict_sha256: digestOfReviewerBytes(receiptBytes),
|
|
14828
|
+
validateReceipt: () => {
|
|
14829
|
+
const current = inspect();
|
|
14830
|
+
if (!current.ok)
|
|
14831
|
+
return current.reason;
|
|
14832
|
+
return current.receipt.actorId === observed.receipt.actorId && current.receipt.result === receiptBytes ? null : "reviewer receipt changed during submission";
|
|
14833
|
+
}
|
|
14834
|
+
};
|
|
14835
|
+
if (verdictInput === undefined) {
|
|
14836
|
+
if (!coordinator.isReviewVerdictValid(taskId, receiptBytes)) {
|
|
14837
|
+
return withReviewRecovery(coordinator.abandonReview(taskId, "reviewer receipt is not a valid verdict"), RELEASED_REVIEW_RECOVERY);
|
|
14838
|
+
}
|
|
14839
|
+
return coordinator.submitReview(taskId, ctx, receiptBytes, options);
|
|
14840
|
+
}
|
|
14519
14841
|
const parentValid = coordinator.isReviewVerdictValid(taskId, verdictInput);
|
|
14520
14842
|
if (!parentValid)
|
|
14521
14843
|
return coordinator.submitReview(taskId, ctx, verdictInput);
|
|
14522
|
-
|
|
14523
|
-
if (!receiptValid) {
|
|
14844
|
+
if (!coordinator.isReviewVerdictValid(taskId, receiptBytes)) {
|
|
14524
14845
|
return withReviewRecovery(coordinator.abandonReview(taskId, "reviewer receipt is not a valid verdict"), RELEASED_REVIEW_RECOVERY);
|
|
14525
14846
|
}
|
|
14526
14847
|
const parentJson = extractVerdictJson(verdictInput);
|
|
14527
|
-
const receiptJson = extractVerdictJson(
|
|
14528
|
-
if (verdictFingerprint(parentJson) !== verdictFingerprint(receiptJson)) {
|
|
14848
|
+
const receiptJson = extractVerdictJson(receiptBytes);
|
|
14849
|
+
if (!parentJson || !receiptJson || verdictFingerprint(parentJson) !== verdictFingerprint(receiptJson)) {
|
|
14529
14850
|
return { state: "blocked", reason: "parent verdict does not match reviewer receipt", recovery_action: MISMATCH_REVIEW_RECOVERY };
|
|
14530
14851
|
}
|
|
14531
|
-
return coordinator.submitReview(taskId, ctx, verdictInput);
|
|
14852
|
+
return coordinator.submitReview(taskId, ctx, verdictInput, options);
|
|
14853
|
+
}
|
|
14854
|
+
|
|
14855
|
+
// plugins/immune-brain/runtime/claude/kernel_ports.ts
|
|
14856
|
+
function diffSnapshotOf(root, record) {
|
|
14857
|
+
return taskDeliveryIdentity(root, record);
|
|
14858
|
+
}
|
|
14859
|
+
function diffHashOf(root, record) {
|
|
14860
|
+
return diffSnapshotOf(root, record).diff_hash;
|
|
14861
|
+
}
|
|
14862
|
+
function readTaskIntentForRecord(root, taskId) {
|
|
14863
|
+
recoverKernelStoreFollowUps(root, taskId);
|
|
14864
|
+
const currentPath = readTaskRecordRaw(root, taskId).record?.intent_ref?.path;
|
|
14865
|
+
return readTaskIntent(root, taskId, currentPath);
|
|
14866
|
+
}
|
|
14867
|
+
async function submitClaudeReview(host, coordinator, ctx, taskId, verdictInput) {
|
|
14868
|
+
return submitMediatedReview(coordinator, ctx, taskId, verdictInput, () => host.inspectReviewForTask(taskId));
|
|
14532
14869
|
}
|
|
14533
14870
|
function settledKernelResult(result) {
|
|
14534
14871
|
const state = result.state;
|
|
@@ -15161,6 +15498,81 @@ class ClaudeRuntime {
|
|
|
15161
15498
|
report
|
|
15162
15499
|
};
|
|
15163
15500
|
}
|
|
15501
|
+
async retireStaleBatch(initiativeSlug, meta) {
|
|
15502
|
+
throwIfCancelled(meta.signal);
|
|
15503
|
+
const probe = probeHost(this.env, process.platform, this.hostVersion);
|
|
15504
|
+
if (!probe.ok)
|
|
15505
|
+
throw new NativeAuthorityError("unsupported_host", probe.reason);
|
|
15506
|
+
if (!this.requestConfirmation)
|
|
15507
|
+
throw new NativeAuthorityError("interaction_not_opened", batchReason("confirmation_port_unavailable").reason);
|
|
15508
|
+
const outcome = await retireStaleBatch({
|
|
15509
|
+
root: this.cwd,
|
|
15510
|
+
initiative_slug: initiativeSlug,
|
|
15511
|
+
now: new Date().toISOString(),
|
|
15512
|
+
gate: async (facts) => {
|
|
15513
|
+
let confirmationResult;
|
|
15514
|
+
try {
|
|
15515
|
+
confirmationResult = await this.requestConfirmation({
|
|
15516
|
+
operation: "retire_stale_batch",
|
|
15517
|
+
initiativeSlug,
|
|
15518
|
+
toolCallId: meta.toolCallId,
|
|
15519
|
+
batchDisposition: {
|
|
15520
|
+
batch_id: facts.batch_id,
|
|
15521
|
+
batch_state: facts.batch_state,
|
|
15522
|
+
batch_branch: facts.branch,
|
|
15523
|
+
plan_digest: facts.plan_digest,
|
|
15524
|
+
recorded_commits: facts.recorded_commits.length,
|
|
15525
|
+
children: facts.children.map((child) => ({
|
|
15526
|
+
task_id: child.task_id,
|
|
15527
|
+
slice_id: child.slice_id,
|
|
15528
|
+
state: child.state,
|
|
15529
|
+
commit: child.commit,
|
|
15530
|
+
lane_branch: child.lane_branch
|
|
15531
|
+
}))
|
|
15532
|
+
},
|
|
15533
|
+
signal: meta.signal
|
|
15534
|
+
});
|
|
15535
|
+
} catch (err) {
|
|
15536
|
+
if (meta.signal?.aborted)
|
|
15537
|
+
return { kind: "host_rejection", value: batchReason("confirmation_cancelled") };
|
|
15538
|
+
if (err instanceof NativeAuthorityError) {
|
|
15539
|
+
if (err.reasonCode === "unsupported_host")
|
|
15540
|
+
throw err;
|
|
15541
|
+
if (err.reasonCode === "user_cancelled") {
|
|
15542
|
+
return {
|
|
15543
|
+
kind: "host_rejection",
|
|
15544
|
+
value: { state: "cancelled", reason: err.message, recovery_action: err.recoveryAction }
|
|
15545
|
+
};
|
|
15546
|
+
}
|
|
15547
|
+
return {
|
|
15548
|
+
kind: "host_rejection",
|
|
15549
|
+
value: { state: "rejected", reason: err.message, recovery_action: err.recoveryAction }
|
|
15550
|
+
};
|
|
15551
|
+
}
|
|
15552
|
+
return {
|
|
15553
|
+
kind: "host_rejection",
|
|
15554
|
+
value: batchReason("confirmation_failed", err instanceof Error ? err.message : String(err))
|
|
15555
|
+
};
|
|
15556
|
+
}
|
|
15557
|
+
if (confirmationResult.decision === "cancel" && meta.signal?.aborted)
|
|
15558
|
+
return { kind: "host_rejection", value: batchReason("confirmation_cancelled") };
|
|
15559
|
+
if (meta.signal?.aborted)
|
|
15560
|
+
return { kind: "host_rejection", value: batchReason("cancelled_before_execution") };
|
|
15561
|
+
if (confirmationResult.decision === "decline")
|
|
15562
|
+
return { kind: "host_rejection", value: batchReason("confirmation_declined") };
|
|
15563
|
+
if (confirmationResult.decision === "cancel")
|
|
15564
|
+
return { kind: "host_rejection", value: batchReason("confirmation_cancelled") };
|
|
15565
|
+
if (confirmationResult.decision !== "accept")
|
|
15566
|
+
return { kind: "host_rejection", value: batchReason("confirmation_no_decision") };
|
|
15567
|
+
return { kind: "confirmed", request_id: confirmationResult.requestId };
|
|
15568
|
+
}
|
|
15569
|
+
});
|
|
15570
|
+
if (outcome.outcome === "host_rejection")
|
|
15571
|
+
return outcome.value;
|
|
15572
|
+
if (outcome.outcome === "rejected")
|
|
15573
|
+
return outcome.rejection;
|
|
15574
|
+
return { state: "retired", batch_id: outcome.batch_id, report: outcome.report };
|
|
15575
|
+
}
|
|
15164
15576
|
}
|
|
15165
15577
|
|
|
15166
15578
|
// plugins/immune-brain/runtime/claude/mcp_server.ts
|
|
@@ -15169,12 +15581,13 @@ var TOOLS = [
|
|
|
15169
15581
|
{ name: "status", description: "Read the Kernel Assurance Projection for an exact task.", privileged: false },
|
|
15170
15582
|
{ name: "enroll", description: "Enroll a Git-tracked TaskIntent after native confirmation.", privileged: true },
|
|
15171
15583
|
{ name: "advance_assurance", description: "Advance frozen Assurance through deterministic QA and Review reservation.", privileged: false },
|
|
15172
|
-
{ name: "submit_review", description: "Submit the
|
|
15584
|
+
{ name: "submit_review", description: "Submit the Review verdict; omit the verdict to apply the correlated reviewer receipt, or relay the reviewer's structured verdict for fingerprint comparison.", privileged: false },
|
|
15173
15585
|
{ name: "request_authorization", description: "Apply exact literal-user authorization.", privileged: true },
|
|
15174
15586
|
{ name: "revise_intent", description: "Apply a compatible TaskIntent revision.", privileged: false },
|
|
15175
15587
|
{ name: "approve_breaking_intent_revision", description: "Approve a breaking TaskIntent revision.", privileged: true },
|
|
15176
15588
|
{ name: "stop", description: "Stop the active task with literal-user authority.", privileged: true },
|
|
15177
15589
|
{ name: "start_unattended_batch", description: "Start an unattended serial batch run for an Initiative after native confirmation.", privileged: true },
|
|
15590
|
+
{ name: "retire_stale_batch", description: "Retire an Initiative's parked or drifted batch record to the terminal superseded state after native confirmation.", privileged: true },
|
|
15178
15591
|
{ name: "repair_authority_state", description: "Repair a proven recoverable stale backend claim.", privileged: false },
|
|
15179
15592
|
{ name: "resolve_finding", description: "Resolve one open blocking or advisory finding whose cause is fixed and verified.", privileged: false },
|
|
15180
15593
|
{ name: "refute_finding", description: "Refute one open finding by binding the fresh passing QA attestation that contradicts it.", privileged: false }
|
|
@@ -15186,18 +15599,20 @@ function listMcpTools() {
|
|
|
15186
15599
|
inputSchema: {
|
|
15187
15600
|
type: "object",
|
|
15188
15601
|
properties: {
|
|
15189
|
-
...tool.name === "start_unattended_batch" ? {
|
|
15602
|
+
...tool.name === "start_unattended_batch" || tool.name === "retire_stale_batch" ? {
|
|
15190
15603
|
initiative_slug: { type: "string" },
|
|
15191
|
-
|
|
15192
|
-
|
|
15193
|
-
|
|
15194
|
-
|
|
15195
|
-
|
|
15196
|
-
|
|
15197
|
-
|
|
15198
|
-
|
|
15604
|
+
...tool.name === "start_unattended_batch" ? {
|
|
15605
|
+
max_parallel: { type: "integer", minimum: 1 },
|
|
15606
|
+
lane_offers: {
|
|
15607
|
+
type: "array",
|
|
15608
|
+
items: {
|
|
15609
|
+
type: "object",
|
|
15610
|
+
properties: { task_id: { type: "string" }, path: { type: "string" } },
|
|
15611
|
+
required: ["task_id", "path"],
|
|
15612
|
+
additionalProperties: false
|
|
15613
|
+
}
|
|
15199
15614
|
}
|
|
15200
|
-
}
|
|
15615
|
+
} : {}
|
|
15201
15616
|
} : {
|
|
15202
15617
|
task_id: { type: "string" },
|
|
15203
15618
|
...tool.name === "approve_breaking_intent_revision" || tool.name === "revise_intent" ? { next_intent: { type: "object" } } : {},
|
|
@@ -15207,7 +15622,7 @@ function listMcpTools() {
|
|
|
15207
15622
|
...tool.name === "refute_finding" ? { finding_id: { type: "string" }, attestation_id: { type: "string" } } : {}
|
|
15208
15623
|
}
|
|
15209
15624
|
},
|
|
15210
|
-
required: tool.name === "start_unattended_batch"
|
|
15625
|
+
required: tool.name === "start_unattended_batch" || tool.name === "retire_stale_batch" ? ["initiative_slug"] : tool.name === "resolve_finding" ? ["task_id", "finding_id"] : tool.name === "refute_finding" ? ["task_id", "finding_id", "attestation_id"] : tool.name === "revise_intent" ? ["task_id", "next_intent"] : ["task_id"]
|
|
15211
15626
|
},
|
|
15212
15627
|
annotations: tool.privileged ? privilegedAnnotations() : { readOnlyHint: tool.name === "status" }
|
|
15213
15628
|
}));
|
|
@@ -15276,6 +15691,30 @@ function createMcpRuntime(options = {}) {
|
|
|
15276
15691
|
...laneOffers !== undefined ? { lane_offers: laneOffers } : {}
|
|
15277
15692
|
});
|
|
15278
15693
|
}
|
|
15694
|
+
if (name === "retire_stale_batch") {
|
|
15695
|
+
const initiativeSlug = String(args.initiative_slug ?? "");
|
|
15696
|
+
if (!initiativeSlug)
|
|
15697
|
+
throw new Error("initiative_slug is required");
|
|
15698
|
+
if ("native_decision" in args)
|
|
15699
|
+
throw new Error("native_decision cannot be supplied in tool arguments");
|
|
15700
|
+
if (!negotiatedVersion)
|
|
15701
|
+
throw new NativeAuthorityError("unsupported_host", "Claude Code version is unavailable");
|
|
15702
|
+
if (!negotiatedInteractive) {
|
|
15703
|
+
throw new NativeAuthorityError("unsupported_host", "interactive MCP elicitation is unavailable");
|
|
15704
|
+
}
|
|
15705
|
+
const probe = probeHost(options.env ?? process.env, process.platform, negotiatedVersion);
|
|
15706
|
+
if (!probe.ok)
|
|
15707
|
+
throw new NativeAuthorityError("unsupported_host", probe.reason);
|
|
15708
|
+
const toolMeta = {
|
|
15709
|
+
sessionId: meta.sessionId ?? connectionId,
|
|
15710
|
+
toolCallId: meta.toolCallId ?? `call-${name}`,
|
|
15711
|
+
taskId: initiativeSlug,
|
|
15712
|
+
initiativeSlug,
|
|
15713
|
+
interactive: meta.interactive ?? options.interactive ?? negotiatedInteractive,
|
|
15714
|
+
signal: meta.signal
|
|
15715
|
+
};
|
|
15716
|
+
return runtime.retireStaleBatch(initiativeSlug, toolMeta);
|
|
15717
|
+
}
|
|
15279
15718
|
const taskId = String(args.task_id ?? "");
|
|
15280
15719
|
if (!taskId)
|
|
15281
15720
|
throw new Error("task_id is required");
|
|
@@ -15303,9 +15742,7 @@ function createMcpRuntime(options = {}) {
|
|
|
15303
15742
|
if (name === "advance_assurance")
|
|
15304
15743
|
return runtime.advance(taskId, toolMeta.signal, toolMeta);
|
|
15305
15744
|
if (name === "submit_review") {
|
|
15306
|
-
|
|
15307
|
-
throw new Error("verdict is required");
|
|
15308
|
-
return runtime.submitReview(taskId, args.verdict, toolMeta);
|
|
15745
|
+
return runtime.submitReview(taskId, Object.hasOwn(args, "verdict") ? args.verdict : undefined, toolMeta);
|
|
15309
15746
|
}
|
|
15310
15747
|
if (name === "revise_intent") {
|
|
15311
15748
|
if (!Object.hasOwn(args, "next_intent"))
|
|
@@ -15488,6 +15925,32 @@ async function writeReply(output, reply) {
|
|
|
15488
15925
|
});
|
|
15489
15926
|
}
|
|
15490
15927
|
function elicitationParams(input) {
|
|
15928
|
+
if (input.operation === "retire_stale_batch") {
|
|
15929
|
+
const d = input.batchDisposition;
|
|
15930
|
+
const details = [
|
|
15931
|
+
`Operation: retire_stale_batch`,
|
|
15932
|
+
`Initiative: ${input.initiativeSlug}`,
|
|
15933
|
+
d ? `Batch: ${d.batch_id}` : null,
|
|
15934
|
+
d ? `Batch state: ${d.batch_state}` : null,
|
|
15935
|
+
d ? `Batch branch: ${d.batch_branch}` : null,
|
|
15936
|
+
d ? `Plan digest: ${d.plan_digest}` : null,
|
|
15937
|
+
d ? `Recorded child commits: ${d.recorded_commits}` : null,
|
|
15938
|
+
d ? `Children (${d.children.length}):
|
|
15939
|
+
${d.children.map((child) => ` - ${child.task_id} (${child.slice_id}) [state: ${child.state}]${child.commit ? ` [commit: ${child.commit}]` : " [no commit]"}${child.lane_branch ? ` [lane: ${child.lane_branch}]` : ""}`).join(`
|
|
15940
|
+
`)}` : null,
|
|
15941
|
+
"Retiring writes this record to the terminal superseded state. Child states, recorded commits and the batch branch are preserved as evidence; nothing is deleted and no batch trailer or approval is manufactured.",
|
|
15942
|
+
"A child without a commit was never executed by this batch: it carries no delivery evidence, and the retired authorization is never reused for a revised plan.",
|
|
15943
|
+
"After retiring, run each remaining child with imm-run under its own authorization, or start a new batch for this Initiative."
|
|
15944
|
+
].filter(Boolean);
|
|
15945
|
+
return {
|
|
15946
|
+
mode: "form",
|
|
15947
|
+
message: `Retire this stale batch record for ${input.initiativeSlug}?
|
|
15948
|
+
|
|
15949
|
+
${details.join(`
|
|
15950
|
+
`)}`,
|
|
15951
|
+
requestedSchema: { type: "object", properties: {} }
|
|
15952
|
+
};
|
|
15953
|
+
}
|
|
15491
15954
|
if (input.operation === "start_unattended_batch") {
|
|
15492
15955
|
const b = input.batchDetails;
|
|
15493
15956
|
const details = [
|