immune-brain 3.2.2 → 3.3.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 +1 -1
- package/README.zh-CN.md +1 -1
- package/package.json +5 -3
- package/plugins/immune-brain/.claude-plugin/plugin.json +1 -1
- package/plugins/immune-brain/.pi-extension/imm-canary-work.ts +19 -73
- package/plugins/immune-brain/.pi-extension/pi-canary-interaction.ts +1 -2
- package/plugins/immune-brain/.pi-extension/runtime-stub.ts +2 -2
- package/plugins/immune-brain/dist/claude/mcp-server.mjs +27 -85
- package/plugins/immune-brain/dist/docs/reference/subagent-dispatch-protocol.md +1 -1
- package/plugins/immune-brain/dist/imm-loop.md +10 -6
- package/plugins/immune-brain/runtime/assurance/coordinator.ts +0 -8
- package/plugins/immune-brain/runtime/claude/interaction.ts +0 -1
- package/plugins/immune-brain/runtime/claude/kernel_ports.ts +5 -24
- package/plugins/immune-brain/runtime/claude/mcp_server.ts +12 -8
- package/plugins/immune-brain/runtime/kernel/application.ts +0 -1
- package/plugins/immune-brain/runtime/kernel/assurance_projection.ts +1 -7
- package/plugins/immune-brain/runtime/kernel/canary_application.ts +0 -7
- package/plugins/immune-brain/runtime/kernel/completion.ts +1 -3
- package/plugins/immune-brain/runtime/kernel/reducer.ts +0 -31
- package/plugins/immune-brain/runtime/kernel/types.ts +0 -2
- package/plugins/immune-brain/runtime/kernel/validation.ts +1 -3
- package/plugins/immune-brain/runtime/plugin_version.ts +2 -0
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ Immune-Brain adds a structured engineering workflow on top of Pi:
|
|
|
14
14
|
- **Plans become trackable tasks** (`TaskIntent` + `TaskRecord`) so progress survives across sessions, not just chat history.
|
|
15
15
|
- **Quality is enforced by code, not promises** — automated QA and isolated review must pass before a task is marked done.
|
|
16
16
|
|
|
17
|
-
Pi and Claude Code are the supported hosts. Undeclared adapters remain unsupported. Minimum Claude Code is `2.1.199`.
|
|
17
|
+
Pi and Claude Code are the supported hosts. Undeclared adapters remain unsupported. Minimum Claude Code is `2.1.199`. Historical real-Host evidence is archived under [docs/verification/archive/](docs/verification/archive/); deterministic package and authority checks gate current releases. Either host can use the model provider you configure — Immune-Brain works on top of Kernel authority, not a vendor chat.
|
|
18
18
|
|
|
19
19
|
---
|
|
20
20
|
|
package/README.zh-CN.md
CHANGED
|
@@ -14,7 +14,7 @@ Immune-Brain 在 Pi 之上提供结构化的工程工作流:
|
|
|
14
14
|
- **计划变为可追踪的任务**(`TaskIntent` + `TaskRecord`),进度落盘持久化,不依赖对话历史。
|
|
15
15
|
- **质量由代码强制保障** — 自动化 QA 与隔离式 Review 必须通过,任务才会完成。
|
|
16
16
|
|
|
17
|
-
Pi 与 Claude Code 是支持的宿主。未声明的适配器仍不受支持。Claude Code 最低版本为 `2.1.199
|
|
17
|
+
Pi 与 Claude Code 是支持的宿主。未声明的适配器仍不受支持。Claude Code 最低版本为 `2.1.199`。历史真实 Host 证据归档于 `docs/verification/archive/`;当前发布由确定性的 package 与 authority 检查把关。
|
|
18
18
|
|
|
19
19
|
---
|
|
20
20
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "immune-brain",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"description": "Immune-Brain agent skill system",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public",
|
|
@@ -29,8 +29,9 @@
|
|
|
29
29
|
},
|
|
30
30
|
"scripts": {
|
|
31
31
|
"changeset": "changeset",
|
|
32
|
-
"changeset:version": "changeset version && bun scripts/
|
|
33
|
-
"
|
|
32
|
+
"changeset:version": "changeset version && bun scripts/build-claude-plugin.ts && bun scripts/plugin_versioning.ts validate",
|
|
33
|
+
"verify:release": "bun scripts/plugin_versioning.ts validate && bun scripts/build-claude-plugin.ts --check && bun scripts/sync-dist-docs.ts --check && bun test && npm pack --dry-run --ignore-scripts",
|
|
34
|
+
"changeset:publish": "bun run verify:release && changeset publish",
|
|
34
35
|
"release": "bun run changeset:publish"
|
|
35
36
|
},
|
|
36
37
|
"devDependencies": {
|
|
@@ -76,6 +77,7 @@
|
|
|
76
77
|
"plugins/immune-brain/runtime/verification_descriptor.ts",
|
|
77
78
|
"plugins/immune-brain/runtime/role_prompt_bridge.ts",
|
|
78
79
|
"plugins/immune-brain/runtime/loop_contract.ts",
|
|
80
|
+
"plugins/immune-brain/runtime/plugin_version.ts",
|
|
79
81
|
"plugins/immune-brain/runtime/assurance",
|
|
80
82
|
"plugins/immune-brain/runtime/claude",
|
|
81
83
|
"plugins/immune-brain/runtime/prompts",
|
|
@@ -20,6 +20,7 @@ import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
|
20
20
|
import { join, resolve } from "node:path";
|
|
21
21
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
22
22
|
import { Type } from "typebox";
|
|
23
|
+
import { PLUGIN_VERSION } from "../runtime/plugin_version";
|
|
23
24
|
import {
|
|
24
25
|
parseVerificationDescriptor,
|
|
25
26
|
canonicalDescriptorBytes,
|
|
@@ -171,7 +172,6 @@ const TASK_INTENT_SCHEMA = Type.Object({
|
|
|
171
172
|
|
|
172
173
|
export type { AssuranceRole } from "./pi-canary-assurance";
|
|
173
174
|
export type AuthorizeOperation =
|
|
174
|
-
| "record-user-approval"
|
|
175
175
|
| "approve-breaking-intent-revision"
|
|
176
176
|
| "resolve-user-decision"
|
|
177
177
|
| "stop";
|
|
@@ -391,7 +391,7 @@ export default function (
|
|
|
391
391
|
"After implementation and focused verification, freeze the artifacts, call advance_assurance, and consume its direct terminal result; do not poll or create a detached job.",
|
|
392
392
|
"When advance_assurance returns review_ready, invoke the foreground Agent from agent_params once, then pass its structured verdict to submit_review.",
|
|
393
393
|
"For a complete breaking revision, call approve_breaking_intent_revision with the complete next_intent directly; do not ask for chat pre-confirmation because the host opens the single native confirmation before applying it.",
|
|
394
|
-
"For a proven stale authority claim, call repair_authority_state directly;
|
|
394
|
+
"For a proven stale authority claim, call repair_authority_state directly; the Kernel revalidates and removes only the redundant claim without user interaction.",
|
|
395
395
|
"After awaiting_user, call request_authorization directly so the host opens the single native confirmation; do not ask for chat pre-confirmation or ask the user to copy or report a command.",
|
|
396
396
|
],
|
|
397
397
|
parameters: Type.Object({
|
|
@@ -474,43 +474,6 @@ export default function (
|
|
|
474
474
|
presentTaskRailResult(ctx, taskId, blocked);
|
|
475
475
|
return failCanaryTool(taskId, action.op, "authority_conflict", "authority_conflict", blocked.result, blocked.next_action);
|
|
476
476
|
}
|
|
477
|
-
presentTaskRail(ctx, {
|
|
478
|
-
task_id: taskId,
|
|
479
|
-
state: "Approval required",
|
|
480
|
-
result: "Authority repair needs your approval",
|
|
481
|
-
next: "Decide whether to repair the stale claim",
|
|
482
|
-
});
|
|
483
|
-
const repairSelection = await requestAuthorityDialog(pi, ctx, {
|
|
484
|
-
attention_id: randomUUID(),
|
|
485
|
-
task_id: taskId,
|
|
486
|
-
reason: "authority_repair",
|
|
487
|
-
label: "Kernel authority repair required",
|
|
488
|
-
}, {
|
|
489
|
-
title: "Repair Kernel authority state?",
|
|
490
|
-
summary: [
|
|
491
|
-
`Owner: ${taskId}`,
|
|
492
|
-
`Terminal lifecycle: ${authority.owner_lifecycle}`,
|
|
493
|
-
`Claim lifecycle: ${authority.claim_lifecycle_status}`,
|
|
494
|
-
].join("\n"),
|
|
495
|
-
details: [
|
|
496
|
-
`Projection revision: ${authority.revision}`,
|
|
497
|
-
"Action: remove only the stale global claim; preserve TaskRecord and tombstone.",
|
|
498
|
-
].join("\n"),
|
|
499
|
-
signal: ctx.signal ?? signal,
|
|
500
|
-
actions: [
|
|
501
|
-
{ value: "repair", label: "Repair stale claim", description: "Remove only the stale global claim" },
|
|
502
|
-
{ value: "cancel", label: "Cancel", description: "Preserve all authority state unchanged" },
|
|
503
|
-
],
|
|
504
|
-
});
|
|
505
|
-
if (repairSelection !== "repair") {
|
|
506
|
-
const cancelled = {
|
|
507
|
-
state: "cancelled",
|
|
508
|
-
operation: action.op,
|
|
509
|
-
result: "Authority repair cancelled with zero writes",
|
|
510
|
-
next_action: "request authorization again if repair is still intended",
|
|
511
|
-
};
|
|
512
|
-
return toolResult(JSON.stringify(cancelled, null, 2), cancelled);
|
|
513
|
-
}
|
|
514
477
|
try {
|
|
515
478
|
const repaired = await repairKernelAuthority(ctx.cwd, taskId, authority.revision);
|
|
516
479
|
const result = {
|
|
@@ -574,9 +537,11 @@ export default function (
|
|
|
574
537
|
const blockers = state.blocking_finding_ids.length
|
|
575
538
|
+ state.unresolved_user_decision_ids.length
|
|
576
539
|
+ state.replan_required_ids.length;
|
|
540
|
+
const status = { plugin_version: PLUGIN_VERSION, ...state };
|
|
577
541
|
const details = {
|
|
578
542
|
state: "status",
|
|
579
543
|
operation: "status",
|
|
544
|
+
plugin_version: PLUGIN_VERSION,
|
|
580
545
|
lifecycle: state.lifecycle,
|
|
581
546
|
artifact_state: state.artifact_state,
|
|
582
547
|
task_state: state,
|
|
@@ -584,7 +549,7 @@ export default function (
|
|
|
584
549
|
next_action: state.next_obligation,
|
|
585
550
|
};
|
|
586
551
|
presentTaskRailResult(ctx, taskId, details);
|
|
587
|
-
return toolResult(JSON.stringify(
|
|
552
|
+
return toolResult(JSON.stringify(status, null, 2), details);
|
|
588
553
|
}
|
|
589
554
|
const claim = projection.claim;
|
|
590
555
|
if (!claim || claim.task_id !== taskId) {
|
|
@@ -823,6 +788,17 @@ export default function (
|
|
|
823
788
|
`Decision: ${operation}`,
|
|
824
789
|
`State: ${projection.projection.lifecycle}:${projection.projection.artifact_state} | Claim: ${projection.claim.lifecycle_status}`,
|
|
825
790
|
].join("\n");
|
|
791
|
+
const intentDelta = nextIntent
|
|
792
|
+
? [
|
|
793
|
+
`Risk: ${priorIntent.intent.risk} -> ${nextIntent.risk}`,
|
|
794
|
+
`Goal: ${priorIntent.intent.goal} -> ${nextIntent.goal}`,
|
|
795
|
+
`Scope added: ${nextIntent.scope_hint.filter((path) => !priorIntent.intent.scope_hint.includes(path)).join(", ") || "none"}`,
|
|
796
|
+
`Scope removed: ${priorIntent.intent.scope_hint.filter((path) => !nextIntent.scope_hint.includes(path)).join(", ") || "none"}`,
|
|
797
|
+
`Acceptance added: ${nextIntent.acceptance.filter((item) => !priorIntent.intent.acceptance.some((prior) => prior.id === item.id)).map((item) => item.id).join(", ") || "none"}`,
|
|
798
|
+
`Acceptance removed: ${priorIntent.intent.acceptance.filter((item) => !nextIntent.acceptance.some((next) => next.id === item.id)).map((item) => item.id).join(", ") || "none"}`,
|
|
799
|
+
`Acceptance changed: ${nextIntent.acceptance.filter((item) => { const prior = priorIntent.intent.acceptance.find((candidate) => candidate.id === item.id); return prior && (prior.assertion !== item.assertion || prior.verification !== item.verification); }).map((item) => item.id).join(", ") || "none"}`,
|
|
800
|
+
]
|
|
801
|
+
: [];
|
|
826
802
|
const dialogDetails = [
|
|
827
803
|
`Operation: ${operation}`,
|
|
828
804
|
...(userDecisionOperation
|
|
@@ -834,9 +810,8 @@ export default function (
|
|
|
834
810
|
...(nextIntent
|
|
835
811
|
? [
|
|
836
812
|
`Next Intent: rev ${nextIntent.revision} (${nextIntentHash})`,
|
|
837
|
-
|
|
838
|
-
`
|
|
839
|
-
`Next Acceptance Items: ${nextIntent.acceptance.length}`,
|
|
813
|
+
...intentDelta,
|
|
814
|
+
`Acceptance Items: ${priorIntent.intent.acceptance.length} -> ${nextIntent.acceptance.length}`,
|
|
840
815
|
`Next staged diff: ${stagedNextDiffHash}`,
|
|
841
816
|
]
|
|
842
817
|
: []),
|
|
@@ -922,23 +897,6 @@ export default function (
|
|
|
922
897
|
content_hash: nextIntentHash!,
|
|
923
898
|
};
|
|
924
899
|
}
|
|
925
|
-
// record-user-approval: literal-user approval for critical-task-completion. The approval payload is bound to the fresh
|
|
926
|
-
// projection (task revision, intent content hash, diff hash) and
|
|
927
|
-
// applied through the same exact-action capability path; the
|
|
928
|
-
// The reducer requires kind user, user authority, and active:frozen state.
|
|
929
|
-
const isUserApproval = operation === "record-user-approval";
|
|
930
|
-
const approval = isUserApproval
|
|
931
|
-
? {
|
|
932
|
-
id: `approval-user-${randomUUID().slice(0, 8)}`,
|
|
933
|
-
kind: "user" as const,
|
|
934
|
-
authority_role: "user" as const,
|
|
935
|
-
task_revision: projection.projection.intent_revision,
|
|
936
|
-
intent_content_hash: projection.projection.intent_content_hash,
|
|
937
|
-
diff_hash: projection.projection.diff_hash,
|
|
938
|
-
actor_id: "literal-user",
|
|
939
|
-
summary: "literal user approval",
|
|
940
|
-
}
|
|
941
|
-
: undefined;
|
|
942
900
|
const exactOperation = operation === "stop"
|
|
943
901
|
? { op: "stop" as const, reason: "literal user stopped task parked for replan" }
|
|
944
902
|
: operation === "approve-breaking-intent-revision"
|
|
@@ -947,7 +905,7 @@ export default function (
|
|
|
947
905
|
next_intent: nextIntent!,
|
|
948
906
|
next_intent_ref: nextIntentRef!,
|
|
949
907
|
}
|
|
950
|
-
: userDecisionOperation
|
|
908
|
+
: userDecisionOperation!;
|
|
951
909
|
// The exact host-built operation is shared by capability digest and
|
|
952
910
|
// application payload; command arguments cannot inject authority fields.
|
|
953
911
|
try {
|
|
@@ -971,7 +929,6 @@ export default function (
|
|
|
971
929
|
intent_content_hash: nextIntentHash ?? projection.projection.intent_content_hash,
|
|
972
930
|
diff_hash: operationDiffHash,
|
|
973
931
|
actor_id: "literal-user",
|
|
974
|
-
...(exactOperation.op === "record_user_approval" ? { approval: exactOperation.approval } : {}),
|
|
975
932
|
...(exactOperation.op === "approve_breaking_intent_revision"
|
|
976
933
|
? { next_intent: exactOperation.next_intent, next_intent_ref: exactOperation.next_intent_ref }
|
|
977
934
|
: {}),
|
|
@@ -1039,7 +996,6 @@ export default function (
|
|
|
1039
996
|
|
|
1040
997
|
export type DerivedAuthorizationOperation =
|
|
1041
998
|
| "resolve-user-decision"
|
|
1042
|
-
| "record-user-approval"
|
|
1043
999
|
| "stop";
|
|
1044
1000
|
|
|
1045
1001
|
// Kernel projection is the sole source of authorization readiness.
|
|
@@ -1049,7 +1005,6 @@ export function deriveAuthorizationOperation(input: {
|
|
|
1049
1005
|
}): { operation: DerivedAuthorizationOperation } | { blocked: string } {
|
|
1050
1006
|
if (input.hasOpenReplanRequired) return { operation: "stop" };
|
|
1051
1007
|
if (input.readiness.state === "resolve_user_decision") return { operation: "resolve-user-decision" };
|
|
1052
|
-
if (input.readiness.state === "record_user_approval") return { operation: "record-user-approval" };
|
|
1053
1008
|
if (input.readiness.blocked) return { blocked: input.readiness.blocked };
|
|
1054
1009
|
return { blocked: "no unique host-derived authorization operation" };
|
|
1055
1010
|
}
|
|
@@ -1126,15 +1081,6 @@ export function buildUserDecisionOperation(record: {
|
|
|
1126
1081
|
};
|
|
1127
1082
|
}
|
|
1128
1083
|
|
|
1129
|
-
export function userOperationFor(operation: AuthorizeOperation, approval?: unknown) {
|
|
1130
|
-
if (operation !== "record-user-approval")
|
|
1131
|
-
throw new Error(`unsupported authorize operation: ${operation}`);
|
|
1132
|
-
// The approval payload is constructed by the authorize handler from
|
|
1133
|
-
// the fresh projection; it is never derived from untrusted input.
|
|
1134
|
-
if (approval === undefined) throw new Error("record-user-approval requires an approval payload");
|
|
1135
|
-
return { op: "record_user_approval" as const, approval };
|
|
1136
|
-
}
|
|
1137
|
-
|
|
1138
1084
|
/**
|
|
1139
1085
|
* One record-aware freshness identity. v4 derives the scoped revision digest
|
|
1140
1086
|
* from the immutable Enrollment base so committed and staged task work share a
|
|
@@ -179,7 +179,7 @@ export interface TaskRecordRead {
|
|
|
179
179
|
// --- Assurance projection (host-neutral Kernel facts, not exported from the
|
|
180
180
|
// public Kernel index) ---
|
|
181
181
|
export interface AssuranceAuthorizationReadiness {
|
|
182
|
-
state: "resolve_user_decision" | "
|
|
182
|
+
state: "resolve_user_decision" | "none";
|
|
183
183
|
blocked: string | null;
|
|
184
184
|
}
|
|
185
185
|
export interface AssuranceProjection {
|
|
@@ -191,7 +191,7 @@ export interface AssuranceProjection {
|
|
|
191
191
|
lifecycle: "active" | "done" | "stopped" | "";
|
|
192
192
|
artifact_state: "active" | "frozen" | "";
|
|
193
193
|
risk: "routine" | "material" | "critical" | "";
|
|
194
|
-
next_obligation: "resolve_findings" | "resolve_user_decision" | "revise_intent" | "submit_assurance" | "run_qa" | "run_review" | "
|
|
194
|
+
next_obligation: "resolve_findings" | "resolve_user_decision" | "revise_intent" | "submit_assurance" | "run_qa" | "run_review" | "complete" | "none";
|
|
195
195
|
fresh_acceptance_ids: string[];
|
|
196
196
|
missing_acceptance_ids: string[];
|
|
197
197
|
stale_attestation_ids: string[];
|
|
@@ -52,14 +52,16 @@ function probeHost(env = process.env, platform = process.platform, hostVersion)
|
|
|
52
52
|
return { ok: true, version, permissionMode: "manual", platform };
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
// plugins/immune-brain/runtime/plugin_version.ts
|
|
56
|
+
var PLUGIN_VERSION = "3.3.0";
|
|
57
|
+
|
|
55
58
|
// plugins/immune-brain/runtime/claude/interaction.ts
|
|
56
59
|
import { createHash, randomUUID } from "node:crypto";
|
|
57
60
|
var PRIVILEGED_OPERATIONS = [
|
|
58
61
|
"enroll",
|
|
59
62
|
"request_authorization",
|
|
60
63
|
"approve_breaking_intent_revision",
|
|
61
|
-
"stop"
|
|
62
|
-
"repair_authority_state"
|
|
64
|
+
"stop"
|
|
63
65
|
];
|
|
64
66
|
function isPrivilegedOperation(operation) {
|
|
65
67
|
return PRIVILEGED_OPERATIONS.includes(operation);
|
|
@@ -1484,8 +1486,6 @@ class AssuranceCoordinator {
|
|
|
1484
1486
|
return this.unknownAfterCommit(taskId, "qa", operationId, boundedAssuranceError(error));
|
|
1485
1487
|
}
|
|
1486
1488
|
}
|
|
1487
|
-
if (projection.projection.next_obligation === "authorize_user")
|
|
1488
|
-
return { state: "awaiting_user", operation: "record-user-approval", operation_id: operationId };
|
|
1489
1489
|
if (projection.projection.next_obligation !== "run_qa" && projection.projection.next_obligation !== "run_review")
|
|
1490
1490
|
return { state: "blocked", reason: `Kernel requires ${projection.projection.next_obligation}` };
|
|
1491
1491
|
const qaAlreadySettled = projection.projection.next_obligation === "run_review";
|
|
@@ -1583,8 +1583,6 @@ class AssuranceCoordinator {
|
|
|
1583
1583
|
return this.unknownAfterCommit(taskId, "qa", operationId, boundedAssuranceError(error));
|
|
1584
1584
|
}
|
|
1585
1585
|
}
|
|
1586
|
-
if (fresh.projection.next_obligation === "authorize_user")
|
|
1587
|
-
return { state: "awaiting_user", operation: "record-user-approval", operation_id: operationId };
|
|
1588
1586
|
if (fresh.projection.next_obligation !== "run_review") {
|
|
1589
1587
|
if (authorityCommitted && aborted())
|
|
1590
1588
|
return this.unknownAfterCommit(taskId, "qa", operationId, "QA settlement projection did not require Review after cancellation");
|
|
@@ -1747,8 +1745,6 @@ class AssuranceCoordinator {
|
|
|
1747
1745
|
return this.unknownAfterCommit(taskId, "review", reservation.operationId, boundedAssuranceError(error));
|
|
1748
1746
|
}
|
|
1749
1747
|
}
|
|
1750
|
-
if (settled.projection.next_obligation === "authorize_user")
|
|
1751
|
-
return { state: "awaiting_user", operation: "record-user-approval", operation_id: reservation.operationId };
|
|
1752
1748
|
return { state: "blocked", reason: `Kernel requires ${settled.projection.next_obligation} after Review` };
|
|
1753
1749
|
}
|
|
1754
1750
|
abandonReview(taskId, reason) {
|
|
@@ -3633,7 +3629,6 @@ var ACTION_V2_TYPES = [
|
|
|
3633
3629
|
"record_finding",
|
|
3634
3630
|
"resolve_finding",
|
|
3635
3631
|
"record_approval",
|
|
3636
|
-
"record_user_approval",
|
|
3637
3632
|
"revise_intent",
|
|
3638
3633
|
"approve_breaking_intent_revision",
|
|
3639
3634
|
"request_rework",
|
|
@@ -3696,8 +3691,7 @@ function parseTaskAction(raw) {
|
|
|
3696
3691
|
};
|
|
3697
3692
|
break;
|
|
3698
3693
|
}
|
|
3699
|
-
case "record_approval":
|
|
3700
|
-
case "record_user_approval": {
|
|
3694
|
+
case "record_approval": {
|
|
3701
3695
|
rejectUnknown2(value, [...ACTION_BASE_FIELDS, "approval"], "action", violations);
|
|
3702
3696
|
const approval = parseApprovalV2(value.approval, 0, violations, true);
|
|
3703
3697
|
if (approval.review_revision && approval.kind !== "review")
|
|
@@ -4904,7 +4898,7 @@ function commitTerminalLocked(root, taskId, transaction, tombstone) {
|
|
|
4904
4898
|
var REQUIRED_ATTESTATIONS = {
|
|
4905
4899
|
routine: ["qa"],
|
|
4906
4900
|
material: ["qa", "review"],
|
|
4907
|
-
critical: ["qa", "review"
|
|
4901
|
+
critical: ["qa", "review"]
|
|
4908
4902
|
};
|
|
4909
4903
|
function archiveActivePlanningPath(path) {
|
|
4910
4904
|
const matched = path.match(/^docs\/(plans|specs)\/([^/]+)$/);
|
|
@@ -5009,8 +5003,6 @@ function projectTask(intent, record, currentDiffHash, currentIntentContentHash,
|
|
|
5009
5003
|
nextObligation = "run_qa";
|
|
5010
5004
|
} else if (decision.missing_approval_kinds.includes("review")) {
|
|
5011
5005
|
nextObligation = "run_review";
|
|
5012
|
-
} else if (decision.missing_approval_kinds.includes("user")) {
|
|
5013
|
-
nextObligation = "authorize_user";
|
|
5014
5006
|
} else if (decision.complete) {
|
|
5015
5007
|
nextObligation = "complete";
|
|
5016
5008
|
}
|
|
@@ -5036,8 +5028,6 @@ function deriveAssuranceAuthorization(input) {
|
|
|
5036
5028
|
state: "none",
|
|
5037
5029
|
blocked: `resolve-user-decision requires exactly one open user decision; found ${input.open_user_decision_count}`
|
|
5038
5030
|
};
|
|
5039
|
-
if (input.next_obligation === "authorize_user")
|
|
5040
|
-
return { state: "record_user_approval", blocked: null };
|
|
5041
5031
|
return { state: "none", blocked: null };
|
|
5042
5032
|
}
|
|
5043
5033
|
function emptyProjection() {
|
|
@@ -5288,7 +5278,7 @@ function intentRefMatches(intent, ref) {
|
|
|
5288
5278
|
return ref.path === `docs/plans/${intent.task_id}.intent.json` && ref.content_hash === canonicalIntentHash(intent);
|
|
5289
5279
|
}
|
|
5290
5280
|
function hasPrivilegedKind(action) {
|
|
5291
|
-
return action.type === "record_approval" || action.type === "
|
|
5281
|
+
return action.type === "record_approval" || action.type === "approve_breaking_intent_revision" || action.type === "request_rework" || action.type === "stop" || action.type === "resolve_user_decision";
|
|
5292
5282
|
}
|
|
5293
5283
|
function findingsDigestV2(findings) {
|
|
5294
5284
|
const normalized = findings.map((finding) => ({
|
|
@@ -5440,36 +5430,6 @@ function reduceTask(recordRaw, actionRaw, authorityAudit = null, changedPaths) {
|
|
|
5440
5430
|
appendHistory(record, action, from, approval.id, authorityAudit);
|
|
5441
5431
|
break;
|
|
5442
5432
|
}
|
|
5443
|
-
case "record_user_approval": {
|
|
5444
|
-
if (record.lifecycle !== "active" || record.artifact_state !== "frozen")
|
|
5445
|
-
throw new KernelInvariantError([
|
|
5446
|
-
`cannot record user approval while state is ${stateOf(record)}`
|
|
5447
|
-
]);
|
|
5448
|
-
const approval = action.approval;
|
|
5449
|
-
if (approval.kind !== "user")
|
|
5450
|
-
throw new KernelInvariantError([
|
|
5451
|
-
"record_user_approval requires kind user"
|
|
5452
|
-
]);
|
|
5453
|
-
if (!authorityAudit || authorityAudit.authority_kind !== "user")
|
|
5454
|
-
throw new KernelInvariantError([
|
|
5455
|
-
"record_user_approval requires user authority"
|
|
5456
|
-
]);
|
|
5457
|
-
if (approval.task_revision !== record.intent_snapshot.revision)
|
|
5458
|
-
throw new KernelInvariantError(["approval task_revision must equal the current intent revision"]);
|
|
5459
|
-
if (approval.intent_content_hash !== record.intent_ref.content_hash)
|
|
5460
|
-
throw new KernelInvariantError(["approval intent_content_hash must equal the current intent hash"]);
|
|
5461
|
-
if (approval.diff_hash !== diffHash)
|
|
5462
|
-
throw new KernelInvariantError(["approval diff_hash must equal the action diff hash"]);
|
|
5463
|
-
if (record.attestations.some((item) => item.id === approval.id))
|
|
5464
|
-
throw new KernelInvariantError([
|
|
5465
|
-
`attestations contains duplicate id ${approval.id}`
|
|
5466
|
-
]);
|
|
5467
|
-
if (approval.review_revision)
|
|
5468
|
-
throw new KernelInvariantError(["review_revision is only valid on review approvals"]);
|
|
5469
|
-
record.attestations.push({ ...approval, acceptance_results: [] });
|
|
5470
|
-
appendHistory(record, action, from, approval.id, authorityAudit);
|
|
5471
|
-
break;
|
|
5472
|
-
}
|
|
5473
5433
|
case "revise_intent":
|
|
5474
5434
|
case "approve_breaking_intent_revision": {
|
|
5475
5435
|
if (record.lifecycle !== "active")
|
|
@@ -5706,7 +5666,7 @@ function applyTaskAction(input) {
|
|
|
5706
5666
|
"intent token does not match the committed record intent"
|
|
5707
5667
|
]);
|
|
5708
5668
|
}
|
|
5709
|
-
const privileged = action.type === "record_approval" || action.type === "
|
|
5669
|
+
const privileged = action.type === "record_approval" || action.type === "approve_breaking_intent_revision" || action.type === "request_rework" || action.type === "stop" || action.type === "resolve_user_decision";
|
|
5710
5670
|
const expectedAuthority = privileged ? {
|
|
5711
5671
|
task_id,
|
|
5712
5672
|
action,
|
|
@@ -5828,8 +5788,6 @@ function capabilityActionFor(input) {
|
|
|
5828
5788
|
switch (input.op) {
|
|
5829
5789
|
case "record_approval":
|
|
5830
5790
|
return { ...base, approval: input.approval };
|
|
5831
|
-
case "record_user_approval":
|
|
5832
|
-
return { ...base, approval: input.approval };
|
|
5833
5791
|
case "request_rework":
|
|
5834
5792
|
return { ...base, findings: input.findings };
|
|
5835
5793
|
case "stop":
|
|
@@ -6014,10 +5972,6 @@ function createCanaryApplication(registry) {
|
|
|
6014
5972
|
capability = operation.capability;
|
|
6015
5973
|
action = { ...base, type: "record_approval", approval: operation.approval };
|
|
6016
5974
|
break;
|
|
6017
|
-
case "record_user_approval":
|
|
6018
|
-
capability = operation.capability;
|
|
6019
|
-
action = { ...base, type: "record_user_approval", approval: operation.approval };
|
|
6020
|
-
break;
|
|
6021
5975
|
case "revise_intent":
|
|
6022
5976
|
action = {
|
|
6023
5977
|
...base,
|
|
@@ -6914,22 +6868,17 @@ class ClaudeRuntime {
|
|
|
6914
6868
|
return submitClaudeReview(this.host, this.coordinator, { cwd: this.cwd }, taskId, verdictInput);
|
|
6915
6869
|
}
|
|
6916
6870
|
async authorize(taskId, operation, meta, extra = {}) {
|
|
6917
|
-
if (!isPrivilegedOperation(operation) && operation !== "request_authorization")
|
|
6918
|
-
throw new Error(`unsupported privileged operation ${operation}`);
|
|
6919
|
-
this.rejectBeforePreparation(operation, { ...meta, taskId });
|
|
6920
6871
|
if (operation === "repair_authority_state") {
|
|
6921
6872
|
const authority = reconcileKernelAuthority(this.cwd, taskId);
|
|
6922
6873
|
if (authority.state !== "repairable_stale_claim" || authority.owner_task_id !== taskId) {
|
|
6923
6874
|
throw new Error(authority.diagnostic ?? "authority repair requires a repairable stale claim");
|
|
6924
6875
|
}
|
|
6925
|
-
|
|
6926
|
-
const current = reconcileKernelAuthority(this.cwd, taskId);
|
|
6927
|
-
if (current.state !== authority.state || current.owner_task_id !== authority.owner_task_id || current.revision !== authority.revision) {
|
|
6928
|
-
throw new Error("authority changed after native confirmation; repair aborted before capability issuance");
|
|
6929
|
-
}
|
|
6930
|
-
return repairKernelAuthority(this.cwd, taskId, current.revision);
|
|
6876
|
+
return repairKernelAuthority(this.cwd, taskId, authority.revision);
|
|
6931
6877
|
}
|
|
6932
|
-
|
|
6878
|
+
if (!isPrivilegedOperation(operation) && operation !== "request_authorization")
|
|
6879
|
+
throw new Error(`unsupported privileged operation ${operation}`);
|
|
6880
|
+
this.rejectBeforePreparation(operation, { ...meta, taskId });
|
|
6881
|
+
let op = operation;
|
|
6933
6882
|
let decisionOp;
|
|
6934
6883
|
const projection = await this.status(taskId);
|
|
6935
6884
|
if (projection.error || !projection.claim)
|
|
@@ -6943,7 +6892,7 @@ class ClaudeRuntime {
|
|
|
6943
6892
|
throw new Error(`resolve-user-decision requires exactly one open user decision; found ${open.length}`);
|
|
6944
6893
|
op = "resolve_user_decision";
|
|
6945
6894
|
decisionOp = { finding_id: open[0].id, resolution: `resume after literal-user decision: ${open[0].summary}` };
|
|
6946
|
-
} else
|
|
6895
|
+
} else {
|
|
6947
6896
|
throw new Error(readiness.blocked ?? "no unique host-derived authorization operation");
|
|
6948
6897
|
}
|
|
6949
6898
|
}
|
|
@@ -7010,16 +6959,6 @@ class ClaudeRuntime {
|
|
|
7010
6959
|
}
|
|
7011
6960
|
const { registry, app } = this.authority();
|
|
7012
6961
|
const confirmation = gate.confirmation_ref;
|
|
7013
|
-
const approval = op === "record_user_approval" ? {
|
|
7014
|
-
id: `approval-user-${randomUUID5().slice(0, 8)}`,
|
|
7015
|
-
kind: "user",
|
|
7016
|
-
authority_role: "user",
|
|
7017
|
-
task_revision: projection.projection.intent_revision,
|
|
7018
|
-
intent_content_hash: projection.projection.intent_content_hash,
|
|
7019
|
-
diff_hash: projection.projection.diff_hash,
|
|
7020
|
-
actor_id: actorId,
|
|
7021
|
-
summary: "literal user approval"
|
|
7022
|
-
} : undefined;
|
|
7023
6962
|
try {
|
|
7024
6963
|
const capabilityProjection = await this.status(taskId);
|
|
7025
6964
|
assertProjectionBinding(projection, capabilityProjection, Boolean(nextIntent));
|
|
@@ -7038,7 +6977,6 @@ class ClaudeRuntime {
|
|
|
7038
6977
|
actor_id: actorId,
|
|
7039
6978
|
now,
|
|
7040
6979
|
confirmation_ref: confirmation,
|
|
7041
|
-
...approval ? { approval } : {},
|
|
7042
6980
|
...op === "approve_breaking_intent_revision" ? { next_intent: nextIntent, next_intent_ref: nextIntentRef } : {},
|
|
7043
6981
|
...op === "resolve_user_decision" && decisionOp ? decisionOp : {},
|
|
7044
6982
|
...op === "stop" ? { reason: extra.reason ?? "user stop" } : {}
|
|
@@ -7050,7 +6988,6 @@ class ClaudeRuntime {
|
|
|
7050
6988
|
op,
|
|
7051
6989
|
capability,
|
|
7052
6990
|
actor_id: actorId,
|
|
7053
|
-
...approval ? { approval } : {},
|
|
7054
6991
|
...op === "approve_breaking_intent_revision" ? { next_intent: nextIntent, next_intent_ref: nextIntentRef } : {},
|
|
7055
6992
|
...op === "resolve_user_decision" && decisionOp ? decisionOp : {},
|
|
7056
6993
|
...op === "stop" ? { reason: extra.reason ?? "user stop" } : {}
|
|
@@ -7194,7 +7131,7 @@ var TOOLS = [
|
|
|
7194
7131
|
{ name: "request_authorization", description: "Apply exact literal-user authorization.", privileged: true },
|
|
7195
7132
|
{ name: "approve_breaking_intent_revision", description: "Approve a breaking TaskIntent revision.", privileged: true },
|
|
7196
7133
|
{ name: "stop", description: "Stop the active task with literal-user authority.", privileged: true },
|
|
7197
|
-
{ name: "repair_authority_state", description: "Repair a recoverable stale backend claim.", privileged:
|
|
7134
|
+
{ name: "repair_authority_state", description: "Repair a proven recoverable stale backend claim.", privileged: false }
|
|
7198
7135
|
];
|
|
7199
7136
|
function listMcpTools() {
|
|
7200
7137
|
return TOOLS.map((tool) => ({
|
|
@@ -7242,10 +7179,10 @@ function createMcpRuntime(options = {}) {
|
|
|
7242
7179
|
if ("native_decision" in args)
|
|
7243
7180
|
throw new Error("native_decision cannot be supplied in tool arguments");
|
|
7244
7181
|
if (name === "status")
|
|
7245
|
-
return runtime.status(taskId);
|
|
7182
|
+
return { plugin_version: PLUGIN_VERSION, ...await runtime.status(taskId) };
|
|
7246
7183
|
if (!negotiatedVersion)
|
|
7247
7184
|
throw new Error("Claude Code version is unavailable");
|
|
7248
|
-
if (!negotiatedInteractive)
|
|
7185
|
+
if (!negotiatedInteractive && name !== "repair_authority_state")
|
|
7249
7186
|
throw new Error("non-interactive host session cannot execute authority tools");
|
|
7250
7187
|
const probe = probeHost(options.env ?? process.env, process.platform, negotiatedVersion);
|
|
7251
7188
|
if (!probe.ok)
|
|
@@ -7306,7 +7243,12 @@ async function handleJsonRpc(message, mcp = createMcpRuntime()) {
|
|
|
7306
7243
|
id: message.id ?? null,
|
|
7307
7244
|
result: {
|
|
7308
7245
|
protocolVersion: "2024-11-05",
|
|
7309
|
-
serverInfo: {
|
|
7246
|
+
serverInfo: {
|
|
7247
|
+
name: HOST_ID,
|
|
7248
|
+
version: PLUGIN_VERSION,
|
|
7249
|
+
contract: CORE_CONTRACT,
|
|
7250
|
+
minimumHostVersion: MIN_CLAUDE_CODE_VERSION
|
|
7251
|
+
},
|
|
7310
7252
|
capabilities: { tools: {} }
|
|
7311
7253
|
}
|
|
7312
7254
|
};
|
|
@@ -7506,9 +7448,9 @@ if (entry.endsWith("mcp_server.ts") || entry.endsWith("mcp-server.mjs")) {
|
|
|
7506
7448
|
serveStdio();
|
|
7507
7449
|
}
|
|
7508
7450
|
export {
|
|
7509
|
-
|
|
7510
|
-
listMcpTools,
|
|
7511
|
-
handleJsonRpc,
|
|
7451
|
+
TOOLS,
|
|
7512
7452
|
createMcpRuntime,
|
|
7513
|
-
|
|
7453
|
+
handleJsonRpc,
|
|
7454
|
+
listMcpTools,
|
|
7455
|
+
serveStdio
|
|
7514
7456
|
};
|
|
@@ -90,7 +90,7 @@ Pi `Agent` has no `readonly` parameter; the empty tool policy, child type, and p
|
|
|
90
90
|
4. Kernel Assurance chat uses native Tool rendering only. It does not publish completion messages or wake a later parent turn; the direct Tool result is the continuation boundary. `awaiting_user` remains valid until the host-built authorization operation is confirmed and the immutable snapshot is revalidated.
|
|
91
91
|
5. Child 不得再次派发 child,nested delegation 一律禁止。Parent 保留综合与最终判断责任。
|
|
92
92
|
6. Kernel authority Review 对每个 immutable snapshot 恰好一个 primary reviewer,turn 预算按 workload 缩放(Quick 12 / Standard 16 / Heavy 24),并使用该 snapshot 对应的 Quick/Standard/Heavy 执行档位;不存在从 initial dispatch 起算的单一端到端总预算。Reviewer 必须先验证 immutable v5 manifest 的 `base_head`、`review_commit`、单一 parent、`review_tree` 与 `manifest_digest`,再用只读 Git 命令从 synthetic revision 获取源码;manifest 只有 metadata,不复制 source bytes,也不枚举 neighborhood files。审查只围绕 acceptance assertions 与 `changed_paths`,未变更路径只有在 acceptance、changed caller 或同一 state machine 直接需要时按需读取并注明理由;Reviewer 不探索无关 repository paths。对 settlement-class change,Reviewer 必须先枚举 immutable revision 内每条 terminal、cancellation、timeout 与 race path,再对全部路径给出判断;finding summary 必须以受影响路径开头。每个 acceptance 的执行结果已由 deterministic QA 在 review 前验证并内嵌于 manifest 的 `outcomes` 字段(acceptance_id -> {status, summary});Reviewer 不得重跑 descriptor,也不得把本地没有测试运行当作 finding——Review 只审 revision provenance、代码正确性、回归、安全与缺失测试。除该 reviewer 外,同一触发点最多两个相互独立的 advisory/discovery children;它们只能并行读,不能写 workflow state、关闭 QA 或产生 authority。
|
|
93
|
-
7. Foreground assurance never sleeps, polls, or schedules a completion callback. One Tool call owns QA; one Parent turn obtains and submits the reviewer verdict
|
|
93
|
+
7. Foreground assurance never sleeps, polls, or schedules a completion callback. One Tool call owns QA; one Parent turn obtains and submits the reviewer verdict. User authorization is reserved for unresolved decisions, explicit stop, and breaking Intent revisions; no risk tier adds a generic final confirmation.
|
|
94
94
|
8. `submit_review` validates the verdict contract, task identity, immutable snapshot digest, and fresh record/Intent/workspace/diff revisions before applying Review authority. Malformed verdicts are retryable without rebuilding evidence. Stale verdicts leave the TaskRecord unchanged and release the stale reservation. Local execution trusts the Parent to relay the reviewer verdict and does not require Agent lifecycle receipts.
|
|
95
95
|
9. The assurance Tool checks the immutable snapshot before each phase and returns a terminal structured state directly: `cancelled`, `rework`, `review_ready`, `awaiting_user`, `blocked`, or `settlement_unknown`. The commit boundary is non-cancellable. No silent task, status timer, completion notification, or result retrieval path is permitted.
|
|
96
96
|
|
|
@@ -52,7 +52,7 @@ Repeat this sequence; do not silently stop while a valid action remains:
|
|
|
52
52
|
1. Call `imm_loop_action` with `op: route` (or `dispatch_role` at a QA/review boundary) and follow the projected `next` authority.
|
|
53
53
|
2. Emit one progress line: `[target][phase] result | next: action`.
|
|
54
54
|
3. Execute exactly one allowed action:
|
|
55
|
-
- Kernel ownership: call `imm_kernel_canary` for that owned task. Freeze the completed artifacts, then call `advance_assurance`; when it returns `review_ready`, invoke the foreground reviewer and pass its structured verdict to `submit_review`. When the projection calls for `request_authorization
|
|
55
|
+
- Kernel ownership: call `imm_kernel_canary` for that owned task. Freeze the completed artifacts, then call `advance_assurance`; when it returns `review_ready`, invoke the foreground reviewer and pass its structured verdict to `submit_review`. When the projection calls for `request_authorization` or `approve_breaking_intent_revision`, invoke the exact Tool operation directly without asking the user for chat pre-confirmation; the native host interaction is the single authority decision. Invoke `repair_authority_state` directly for a proven stale claim; Kernel revalidation removes only the redundant claim without user interaction.
|
|
56
56
|
- Active Step / `rework_needed`: follow the returned `executor` context in the current conversation, implement only the active Step or pending same-boundary `follow_up`, verify, record structured execution evidence through the Loop runtime action, and continue. A bounded test-only repair may request internal `test-fixer` with `focus_delta.specific_changes`; PR review or CI repair may request internal `pr-fix` with the current `plan_id`, changed-file boundary, and verification. Both return child evidence to the Parent and cannot widen scope.
|
|
57
57
|
- `awaiting_qa_decision`: call `imm_loop_action` with `op: dispatch_role`, role `qa`, the current projection, Plan verification, recorded evidence, and current target identity. Invoke the returned foreground Agent envelope exactly. A `rework` or `replan` must carry validated `notes`.
|
|
58
58
|
- `review_required`: map the exact `pending_review_gate` (`imm-code-review` or `imm-ui-review`) to the internal `code-review` or `ui-review` role and call `imm_loop_action` with `op: dispatch_role`, passing `pending_review_gate`, `review_changed_files`, and `review_changed_files_signature`. Invoke the returned foreground Agent envelope exactly. Record a validated pass, or open a same-boundary follow-up through the Loop runtime action.
|
|
@@ -142,15 +142,19 @@ When the Kernel projection reports an active/draining backend claim, keep
|
|
|
142
142
|
`imm-loop` as the user-facing entry and call the `imm_kernel_canary` Tool for
|
|
143
143
|
that owned task. Enrollment uses the `imm_canary_enrollment` Tool and Review
|
|
144
144
|
authorization remains a native TUI gate. When the projection calls for
|
|
145
|
-
`request_authorization
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
145
|
+
`request_authorization` or `approve_breaking_intent_revision`, invoke the exact
|
|
146
|
+
Tool operation directly without asking the user for chat pre-confirmation; the
|
|
147
|
+
native host interaction is the single authority decision. Invoke
|
|
148
|
+
`repair_authority_state` directly for a proven stale claim; Kernel revalidation
|
|
149
|
+
removes only the redundant claim without user interaction. Do not invoke the
|
|
150
|
+
removed `imm-canary-work` Skill as
|
|
149
151
|
a separate entry point. Invalid or contradictory projections fail closed. After
|
|
150
152
|
implementation and focused verification, freeze the artifacts and call
|
|
151
153
|
`advance_assurance`. If it returns `review_ready`, invoke the foreground
|
|
152
154
|
reviewer and pass its structured verdict to `submit_review`;
|
|
153
|
-
`request_authorization`
|
|
155
|
+
`request_authorization` is reserved for an unresolved user decision or an
|
|
156
|
+
explicit stop. Critical work completes after fresh QA and Review without a
|
|
157
|
+
second user confirmation.
|
|
154
158
|
Every QA/Review operation stays foreground and returns its next projected
|
|
155
159
|
obligation directly to the Parent. The host performs any opted-in GitHub Issue
|
|
156
160
|
projection only after the corresponding authority mutation: only a fresh
|
|
@@ -133,7 +133,6 @@ export interface ForegroundToolUpdate {
|
|
|
133
133
|
|
|
134
134
|
export type AssuranceAdvanceResult =
|
|
135
135
|
| { state: "review_ready"; operation: "review"; operation_id: string; snapshot_digest: string; review_bundle_digest: string; agent_params: unknown }
|
|
136
|
-
| { state: "awaiting_user"; operation: "record-user-approval"; operation_id: string }
|
|
137
136
|
| { state: "rework"; operation: "qa"; operation_id: string; summary: string }
|
|
138
137
|
| { state: "cancelled"; operation: "qa" | "review"; operation_id: string; reason: string }
|
|
139
138
|
| { state: "failed"; operation: "qa" | "review"; operation_id: string; reason: string }
|
|
@@ -150,7 +149,6 @@ export type AssuranceAdvanceResult =
|
|
|
150
149
|
|
|
151
150
|
export type AssuranceSubmitReviewResult =
|
|
152
151
|
| { state: "rework"; operation: "review"; operation_id: string; summary: string }
|
|
153
|
-
| { state: "awaiting_user"; operation: "record-user-approval"; operation_id: string }
|
|
154
152
|
| { state: "review_preparation_failed"; operation: "review"; operation_id: string; reason: string }
|
|
155
153
|
| { state: "completed" }
|
|
156
154
|
| { state: "settlement_unknown"; operation: "qa" | "review"; operation_id: string; reason: string }
|
|
@@ -528,8 +526,6 @@ export class AssuranceCoordinator {
|
|
|
528
526
|
return this.unknownAfterCommit(taskId, "qa", operationId, boundedAssuranceError(error));
|
|
529
527
|
}
|
|
530
528
|
}
|
|
531
|
-
if (projection.projection.next_obligation === "authorize_user")
|
|
532
|
-
return { state: "awaiting_user", operation: "record-user-approval", operation_id: operationId };
|
|
533
529
|
if (projection.projection.next_obligation !== "run_qa" && projection.projection.next_obligation !== "run_review")
|
|
534
530
|
return { state: "blocked", reason: `Kernel requires ${projection.projection.next_obligation}` };
|
|
535
531
|
const qaAlreadySettled = projection.projection.next_obligation === "run_review";
|
|
@@ -623,8 +619,6 @@ export class AssuranceCoordinator {
|
|
|
623
619
|
return this.unknownAfterCommit(taskId, "qa", operationId, boundedAssuranceError(error));
|
|
624
620
|
}
|
|
625
621
|
}
|
|
626
|
-
if (fresh.projection.next_obligation === "authorize_user")
|
|
627
|
-
return { state: "awaiting_user", operation: "record-user-approval", operation_id: operationId };
|
|
628
622
|
if (fresh.projection.next_obligation !== "run_review") {
|
|
629
623
|
if (authorityCommitted && aborted()) return this.unknownAfterCommit(taskId, "qa", operationId, "QA settlement projection did not require Review after cancellation");
|
|
630
624
|
return { state: "blocked", reason: `Kernel requires ${fresh.projection.next_obligation} after QA` };
|
|
@@ -777,8 +771,6 @@ export class AssuranceCoordinator {
|
|
|
777
771
|
return this.unknownAfterCommit(taskId, "review", reservation.operationId, boundedAssuranceError(error));
|
|
778
772
|
}
|
|
779
773
|
}
|
|
780
|
-
if (settled.projection.next_obligation === "authorize_user")
|
|
781
|
-
return { state: "awaiting_user", operation: "record-user-approval", operation_id: reservation.operationId };
|
|
782
774
|
return { state: "blocked", reason: `Kernel requires ${settled.projection.next_obligation} after Review` };
|
|
783
775
|
}
|
|
784
776
|
|
|
@@ -493,21 +493,16 @@ export class ClaudeRuntime {
|
|
|
493
493
|
}
|
|
494
494
|
|
|
495
495
|
async authorize(taskId: string, operation: string, meta: ToolMeta, extra: Record<string, unknown> = {}) {
|
|
496
|
-
if (!isPrivilegedOperation(operation) && operation !== "request_authorization") throw new Error(`unsupported privileged operation ${operation}`);
|
|
497
|
-
this.rejectBeforePreparation(operation, { ...meta, taskId });
|
|
498
496
|
if (operation === "repair_authority_state") {
|
|
499
497
|
const authority = reconcileKernelAuthority(this.cwd, taskId);
|
|
500
498
|
if (authority.state !== "repairable_stale_claim" || authority.owner_task_id !== taskId) {
|
|
501
499
|
throw new Error(authority.diagnostic ?? "authority repair requires a repairable stale claim");
|
|
502
500
|
}
|
|
503
|
-
|
|
504
|
-
const current = reconcileKernelAuthority(this.cwd, taskId);
|
|
505
|
-
if (current.state !== authority.state || current.owner_task_id !== authority.owner_task_id || current.revision !== authority.revision) {
|
|
506
|
-
throw new Error("authority changed after native confirmation; repair aborted before capability issuance");
|
|
507
|
-
}
|
|
508
|
-
return repairKernelAuthority(this.cwd, taskId, current.revision);
|
|
501
|
+
return repairKernelAuthority(this.cwd, taskId, authority.revision);
|
|
509
502
|
}
|
|
510
|
-
|
|
503
|
+
if (!isPrivilegedOperation(operation) && operation !== "request_authorization") throw new Error(`unsupported privileged operation ${operation}`);
|
|
504
|
+
this.rejectBeforePreparation(operation, { ...meta, taskId });
|
|
505
|
+
let op = operation;
|
|
511
506
|
let decisionOp: { finding_id: string; resolution: string } | undefined;
|
|
512
507
|
const projection = await this.status(taskId);
|
|
513
508
|
if (projection.error || !projection.claim) throw new Error(projection.error ?? "no active backend claim");
|
|
@@ -524,7 +519,7 @@ export class ClaudeRuntime {
|
|
|
524
519
|
if (open.length !== 1) throw new Error(`resolve-user-decision requires exactly one open user decision; found ${open.length}`);
|
|
525
520
|
op = "resolve_user_decision";
|
|
526
521
|
decisionOp = { finding_id: open[0].id, resolution: `resume after literal-user decision: ${open[0].summary}` };
|
|
527
|
-
} else
|
|
522
|
+
} else {
|
|
528
523
|
throw new Error(readiness.blocked ?? "no unique host-derived authorization operation");
|
|
529
524
|
}
|
|
530
525
|
}
|
|
@@ -591,18 +586,6 @@ export class ClaudeRuntime {
|
|
|
591
586
|
}
|
|
592
587
|
const { registry, app } = this.authority();
|
|
593
588
|
const confirmation = gate.confirmation_ref;
|
|
594
|
-
const approval = op === "record_user_approval"
|
|
595
|
-
? {
|
|
596
|
-
id: `approval-user-${randomUUID().slice(0, 8)}`,
|
|
597
|
-
kind: "user" as const,
|
|
598
|
-
authority_role: "user" as const,
|
|
599
|
-
task_revision: projection.projection.intent_revision,
|
|
600
|
-
intent_content_hash: projection.projection.intent_content_hash,
|
|
601
|
-
diff_hash: projection.projection.diff_hash,
|
|
602
|
-
actor_id: actorId,
|
|
603
|
-
summary: "literal user approval",
|
|
604
|
-
}
|
|
605
|
-
: undefined;
|
|
606
589
|
try {
|
|
607
590
|
const capabilityProjection = await this.status(taskId);
|
|
608
591
|
assertProjectionBinding(projection, capabilityProjection, Boolean(nextIntent));
|
|
@@ -621,7 +604,6 @@ export class ClaudeRuntime {
|
|
|
621
604
|
actor_id: actorId,
|
|
622
605
|
now,
|
|
623
606
|
confirmation_ref: confirmation,
|
|
624
|
-
...(approval ? { approval } : {}),
|
|
625
607
|
...(op === "approve_breaking_intent_revision" ? { next_intent: nextIntent, next_intent_ref: nextIntentRef } : {}),
|
|
626
608
|
...(op === "resolve_user_decision" && decisionOp ? decisionOp : {}),
|
|
627
609
|
...(op === "stop" ? { reason: extra.reason ?? "user stop" } : {}),
|
|
@@ -633,7 +615,6 @@ export class ClaudeRuntime {
|
|
|
633
615
|
op,
|
|
634
616
|
capability,
|
|
635
617
|
actor_id: actorId,
|
|
636
|
-
...(approval ? { approval } : {}),
|
|
637
618
|
...(op === "approve_breaking_intent_revision" ? { next_intent: nextIntent, next_intent_ref: nextIntentRef } : {}),
|
|
638
619
|
...(op === "resolve_user_decision" && decisionOp ? decisionOp : {}),
|
|
639
620
|
...(op === "stop" ? { reason: extra.reason ?? "user stop" } : {}),
|
|
@@ -2,6 +2,7 @@ import { createInterface } from "node:readline";
|
|
|
2
2
|
import { stdin, stdout } from "node:process";
|
|
3
3
|
import type { Readable, Writable } from "node:stream";
|
|
4
4
|
import { CORE_CONTRACT, HOST_ID, MIN_CLAUDE_CODE_VERSION, probeHost } from "./capability";
|
|
5
|
+
import { PLUGIN_VERSION } from "../plugin_version";
|
|
5
6
|
import { isPrivilegedOperation, privilegedAnnotations, type NativeDecision } from "./interaction";
|
|
6
7
|
import { ClaudeReviewHost, FileHookEventLog, parseHookStdin } from "./review_host";
|
|
7
8
|
import { ClaudeRuntime, type ToolMeta } from "./kernel_ports";
|
|
@@ -15,7 +16,7 @@ export const TOOLS = [
|
|
|
15
16
|
{ name: "request_authorization", description: "Apply exact literal-user authorization.", privileged: true },
|
|
16
17
|
{ name: "approve_breaking_intent_revision", description: "Approve a breaking TaskIntent revision.", privileged: true },
|
|
17
18
|
{ name: "stop", description: "Stop the active task with literal-user authority.", privileged: true },
|
|
18
|
-
{ name: "repair_authority_state", description: "Repair a recoverable stale backend claim.", privileged:
|
|
19
|
+
{ name: "repair_authority_state", description: "Repair a proven recoverable stale backend claim.", privileged: false },
|
|
19
20
|
] as const;
|
|
20
21
|
|
|
21
22
|
export function listMcpTools() {
|
|
@@ -75,16 +76,14 @@ export function createMcpRuntime(options: McpRuntimeOptions = {}) {
|
|
|
75
76
|
if ("native_decision" in args) throw new Error("native_decision cannot be supplied in tool arguments");
|
|
76
77
|
// Read-only status must stay usable without trusted Host evidence, so it
|
|
77
78
|
// is dispatched before the capability probe rejects unversioned hosts.
|
|
78
|
-
if (name === "status") return runtime.status(taskId);
|
|
79
|
+
if (name === "status") return { plugin_version: PLUGIN_VERSION, ...(await runtime.status(taskId)) };
|
|
79
80
|
// The environment version fallback is disabled on this connection:
|
|
80
81
|
// only a version bound from the trusted initialize handshake may
|
|
81
82
|
// reach authority-mutating tools.
|
|
82
83
|
if (!negotiatedVersion) throw new Error("Claude Code version is unavailable");
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
|
|
86
|
-
// Kernel state without native interaction capability.
|
|
87
|
-
if (!negotiatedInteractive) throw new Error("non-interactive host session cannot execute authority tools");
|
|
84
|
+
// Automatic stale-claim repair is deterministic and needs no native
|
|
85
|
+
// interaction; other mutations retain the Host capability requirement.
|
|
86
|
+
if (!negotiatedInteractive && name !== "repair_authority_state") throw new Error("non-interactive host session cannot execute authority tools");
|
|
88
87
|
const probe = probeHost(options.env ?? process.env, process.platform, negotiatedVersion);
|
|
89
88
|
if (!probe.ok) throw new Error(probe.reason);
|
|
90
89
|
const toolMeta: ToolMeta = {
|
|
@@ -168,7 +167,12 @@ export async function handleJsonRpc(message: JsonRpc, mcp = createMcpRuntime()):
|
|
|
168
167
|
id: message.id ?? null,
|
|
169
168
|
result: {
|
|
170
169
|
protocolVersion: "2024-11-05",
|
|
171
|
-
serverInfo: {
|
|
170
|
+
serverInfo: {
|
|
171
|
+
name: HOST_ID,
|
|
172
|
+
version: PLUGIN_VERSION,
|
|
173
|
+
contract: CORE_CONTRACT,
|
|
174
|
+
minimumHostVersion: MIN_CLAUDE_CODE_VERSION,
|
|
175
|
+
},
|
|
172
176
|
capabilities: { tools: {} },
|
|
173
177
|
},
|
|
174
178
|
};
|
|
@@ -158,7 +158,6 @@ export function applyTaskAction(
|
|
|
158
158
|
|
|
159
159
|
const privileged =
|
|
160
160
|
action.type === "record_approval" ||
|
|
161
|
-
action.type === "record_user_approval" ||
|
|
162
161
|
action.type === "approve_breaking_intent_revision" ||
|
|
163
162
|
action.type === "request_rework" ||
|
|
164
163
|
action.type === "stop" ||
|
|
@@ -25,13 +25,9 @@ export interface AssuranceAuthorizationReadiness {
|
|
|
25
25
|
/**
|
|
26
26
|
* Kernel-decidable authorization readiness:
|
|
27
27
|
* - "resolve_user_decision": exactly one open unresolved-user-decision finding;
|
|
28
|
-
* - "record_user_approval": Kernel projects authorize_user after fresh QA
|
|
29
|
-
* and Review attestations for a critical task;
|
|
30
28
|
* - "none": nothing uniquely decidable from Kernel facts.
|
|
31
|
-
* A pending Pi native Review verdict is a session fact and is NOT visible
|
|
32
|
-
* here; the host composes it before this readiness.
|
|
33
29
|
*/
|
|
34
|
-
state: "resolve_user_decision" | "
|
|
30
|
+
state: "resolve_user_decision" | "none";
|
|
35
31
|
/** Non-null only when Kernel facts prove the authorization is blocked. */
|
|
36
32
|
blocked: string | null;
|
|
37
33
|
}
|
|
@@ -79,8 +75,6 @@ export function deriveAssuranceAuthorization(input: {
|
|
|
79
75
|
state: "none",
|
|
80
76
|
blocked: `resolve-user-decision requires exactly one open user decision; found ${input.open_user_decision_count}`,
|
|
81
77
|
};
|
|
82
|
-
if (input.next_obligation === "authorize_user")
|
|
83
|
-
return { state: "record_user_approval", blocked: null };
|
|
84
78
|
return { state: "none", blocked: null };
|
|
85
79
|
}
|
|
86
80
|
|
|
@@ -51,7 +51,6 @@ export type CanaryOperation =
|
|
|
51
51
|
| { op: "resolve_finding"; finding_id: string; actor_id: string }
|
|
52
52
|
| { op: "request_rework"; capability: object; findings: TaskFinding[]; actor_id: string }
|
|
53
53
|
| { op: "record_approval"; capability: object; approval: TaskApprovalV2; actor_id: string }
|
|
54
|
-
| { op: "record_user_approval"; capability: object; approval: TaskApprovalV2; actor_id: string }
|
|
55
54
|
| { op: "revise_intent"; next_intent: TaskIntentV1; actor_id: string }
|
|
56
55
|
| { op: "approve_breaking_intent_revision"; capability: object; next_intent: TaskIntentV1; actor_id: string }
|
|
57
56
|
| { op: "complete"; actor_id: string }
|
|
@@ -141,8 +140,6 @@ export function capabilityActionFor(input: {
|
|
|
141
140
|
switch (input.op) {
|
|
142
141
|
case "record_approval":
|
|
143
142
|
return { ...base, approval: input.approval } as TaskAction;
|
|
144
|
-
case "record_user_approval":
|
|
145
|
-
return { ...base, approval: input.approval } as TaskAction;
|
|
146
143
|
case "request_rework":
|
|
147
144
|
return { ...base, findings: input.findings } as TaskAction;
|
|
148
145
|
case "stop":
|
|
@@ -373,10 +370,6 @@ export function createCanaryApplication(
|
|
|
373
370
|
capability = operation.capability;
|
|
374
371
|
action = { ...base, type: "record_approval", approval: operation.approval };
|
|
375
372
|
break;
|
|
376
|
-
case "record_user_approval":
|
|
377
|
-
capability = operation.capability;
|
|
378
|
-
action = { ...base, type: "record_user_approval", approval: operation.approval };
|
|
379
|
-
break;
|
|
380
373
|
case "revise_intent":
|
|
381
374
|
action = {
|
|
382
375
|
...base,
|
|
@@ -12,7 +12,7 @@ import { assertKernelInvariantsV3, KernelInvariantError } from "./validation";
|
|
|
12
12
|
const REQUIRED_ATTESTATIONS: Record<TaskIntentV1["risk"], ApprovalKind[]> = {
|
|
13
13
|
routine: ["qa"],
|
|
14
14
|
material: ["qa", "review"],
|
|
15
|
-
critical: ["qa", "review"
|
|
15
|
+
critical: ["qa", "review"],
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
function archiveActivePlanningPath(path: string): string | null {
|
|
@@ -199,8 +199,6 @@ export function projectTask(
|
|
|
199
199
|
nextObligation = "run_qa";
|
|
200
200
|
} else if (decision.missing_approval_kinds.includes("review")) {
|
|
201
201
|
nextObligation = "run_review";
|
|
202
|
-
} else if (decision.missing_approval_kinds.includes("user")) {
|
|
203
|
-
nextObligation = "authorize_user";
|
|
204
202
|
} else if (decision.complete) {
|
|
205
203
|
nextObligation = "complete";
|
|
206
204
|
}
|
|
@@ -176,7 +176,6 @@ function intentRefMatches(intent: TaskIntentV1, ref: TaskIntentRefV3): boolean {
|
|
|
176
176
|
function hasPrivilegedKind(action: TaskAction): boolean {
|
|
177
177
|
return (
|
|
178
178
|
action.type === "record_approval" ||
|
|
179
|
-
action.type === "record_user_approval" ||
|
|
180
179
|
action.type === "approve_breaking_intent_revision" ||
|
|
181
180
|
action.type === "request_rework" ||
|
|
182
181
|
action.type === "stop" ||
|
|
@@ -374,36 +373,6 @@ export function reduceTask(
|
|
|
374
373
|
appendHistory(record, action, from, approval.id, authorityAudit);
|
|
375
374
|
break;
|
|
376
375
|
}
|
|
377
|
-
case "record_user_approval": {
|
|
378
|
-
if (record.lifecycle !== "active" || record.artifact_state !== "frozen")
|
|
379
|
-
throw new KernelInvariantError([
|
|
380
|
-
`cannot record user approval while state is ${stateOf(record)}`,
|
|
381
|
-
]);
|
|
382
|
-
const approval = action.approval;
|
|
383
|
-
if (approval.kind !== "user")
|
|
384
|
-
throw new KernelInvariantError([
|
|
385
|
-
"record_user_approval requires kind user",
|
|
386
|
-
]);
|
|
387
|
-
if (!authorityAudit || authorityAudit.authority_kind !== "user")
|
|
388
|
-
throw new KernelInvariantError([
|
|
389
|
-
"record_user_approval requires user authority",
|
|
390
|
-
]);
|
|
391
|
-
if (approval.task_revision !== record.intent_snapshot.revision)
|
|
392
|
-
throw new KernelInvariantError(["approval task_revision must equal the current intent revision"]);
|
|
393
|
-
if (approval.intent_content_hash !== record.intent_ref.content_hash)
|
|
394
|
-
throw new KernelInvariantError(["approval intent_content_hash must equal the current intent hash"]);
|
|
395
|
-
if (approval.diff_hash !== diffHash)
|
|
396
|
-
throw new KernelInvariantError(["approval diff_hash must equal the action diff hash"]);
|
|
397
|
-
if (record.attestations.some((item) => item.id === approval.id))
|
|
398
|
-
throw new KernelInvariantError([
|
|
399
|
-
`attestations contains duplicate id ${approval.id}`,
|
|
400
|
-
]);
|
|
401
|
-
if (approval.review_revision)
|
|
402
|
-
throw new KernelInvariantError(["review_revision is only valid on review approvals"]);
|
|
403
|
-
record.attestations.push({ ...approval, acceptance_results: [] });
|
|
404
|
-
appendHistory(record, action, from, approval.id, authorityAudit);
|
|
405
|
-
break;
|
|
406
|
-
}
|
|
407
376
|
case "revise_intent":
|
|
408
377
|
case "approve_breaking_intent_revision": {
|
|
409
378
|
if (record.lifecycle !== "active")
|
|
@@ -57,7 +57,6 @@ export type AssuranceObligation =
|
|
|
57
57
|
| "submit_assurance"
|
|
58
58
|
| "run_qa"
|
|
59
59
|
| "run_review"
|
|
60
|
-
| "authorize_user"
|
|
61
60
|
| "complete"
|
|
62
61
|
| "none";
|
|
63
62
|
|
|
@@ -276,7 +275,6 @@ export type TaskAction =
|
|
|
276
275
|
| (TaskActionBase & { type: "record_finding"; finding: TaskFinding })
|
|
277
276
|
| (TaskActionBase & { type: "resolve_finding"; finding_id: string })
|
|
278
277
|
| (TaskActionBase & { type: "record_approval"; approval: TaskApprovalV2 })
|
|
279
|
-
| (TaskActionBase & { type: "record_user_approval"; approval: TaskApprovalV2 })
|
|
280
278
|
| (TaskActionBase & {
|
|
281
279
|
type: "revise_intent";
|
|
282
280
|
next_intent: TaskIntentV1;
|
|
@@ -730,7 +730,6 @@ const ACTION_V2_TYPES = [
|
|
|
730
730
|
"record_finding",
|
|
731
731
|
"resolve_finding",
|
|
732
732
|
"record_approval",
|
|
733
|
-
"record_user_approval",
|
|
734
733
|
"revise_intent",
|
|
735
734
|
"approve_breaking_intent_revision",
|
|
736
735
|
"request_rework",
|
|
@@ -831,8 +830,7 @@ export function parseTaskAction(raw: unknown): TaskAction {
|
|
|
831
830
|
};
|
|
832
831
|
break;
|
|
833
832
|
}
|
|
834
|
-
case "record_approval":
|
|
835
|
-
case "record_user_approval": {
|
|
833
|
+
case "record_approval": {
|
|
836
834
|
rejectUnknown(
|
|
837
835
|
value,
|
|
838
836
|
[...ACTION_BASE_FIELDS, "approval"],
|