immune-brain 3.6.3 → 3.6.5
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 +6 -3
- package/plugins/immune-brain/.claude-plugin/plugin.json +1 -1
- package/plugins/immune-brain/.pi-extension/imm-canary-work.ts +117 -47
- package/plugins/immune-brain/.pi-extension/pi-canary-interaction.ts +6 -4
- package/plugins/immune-brain/.pi-extension/runtime-stub.ts +17 -44
- package/plugins/immune-brain/dist/claude/mcp-server.mjs +213 -81
- package/plugins/immune-brain/dist/imm-loop.md +8 -2
- package/plugins/immune-brain/dist/imm-planner.md +9 -4
- package/plugins/immune-brain/runtime/assurance/coordinator.ts +35 -2
- package/plugins/immune-brain/runtime/assurance/review_evidence.ts +23 -13
- package/plugins/immune-brain/runtime/claude/kernel_ports.ts +133 -45
- package/plugins/immune-brain/runtime/claude/mcp_server.ts +14 -1
- package/plugins/immune-brain/runtime/claude/review_host.ts +0 -10
- package/plugins/immune-brain/runtime/commands/kernel.ts +15 -13
- package/plugins/immune-brain/runtime/github_issue_tracker.ts +107 -11
- package/plugins/immune-brain/runtime/kernel/application.ts +6 -0
- package/plugins/immune-brain/runtime/kernel/assurance_projection.ts +4 -1
- package/plugins/immune-brain/runtime/kernel/batch_authority.ts +407 -0
- package/plugins/immune-brain/runtime/kernel/canary_application.ts +17 -8
- package/plugins/immune-brain/runtime/kernel/enrollment.ts +76 -13
- package/plugins/immune-brain/runtime/kernel/index.ts +2 -0
- package/plugins/immune-brain/runtime/kernel/intent.ts +67 -23
- package/plugins/immune-brain/runtime/kernel/observation.ts +2 -0
- package/plugins/immune-brain/runtime/kernel/reducer.ts +34 -8
- package/plugins/immune-brain/runtime/kernel/storage.ts +17 -2
- package/plugins/immune-brain/runtime/kernel/storage_layout_migration.ts +1 -1
- package/plugins/immune-brain/runtime/kernel/storage_paths.ts +0 -4
- package/plugins/immune-brain/runtime/kernel/types.ts +10 -0
- package/plugins/immune-brain/runtime/kernel/validation.ts +10 -6
- package/plugins/immune-brain/runtime/managed_task_routing_policy.ts +2 -2
- package/plugins/immune-brain/runtime/plugin_version.ts +1 -1
|
@@ -42,7 +42,7 @@ function probeHost(env = process.env, platform = process.platform, hostVersion)
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
// plugins/immune-brain/runtime/plugin_version.ts
|
|
45
|
-
var PLUGIN_VERSION = "3.6.
|
|
45
|
+
var PLUGIN_VERSION = "3.6.5";
|
|
46
46
|
|
|
47
47
|
// plugins/immune-brain/runtime/claude/interaction.ts
|
|
48
48
|
import { createHash, randomUUID } from "node:crypto";
|
|
@@ -1351,6 +1351,8 @@ class AssuranceCoordinator {
|
|
|
1351
1351
|
return this.sessionGeneration;
|
|
1352
1352
|
}
|
|
1353
1353
|
async advance(taskId, ctx, signal, onUpdate) {
|
|
1354
|
+
if (this.isInvocationOpen(taskId))
|
|
1355
|
+
return { state: "blocked", reason: "an authority invocation is already open" };
|
|
1354
1356
|
const active = this.active(taskId);
|
|
1355
1357
|
if (active?.state === "review_ready") {
|
|
1356
1358
|
const reservation = this.reviewReservations.get(taskId);
|
|
@@ -1728,6 +1730,17 @@ class AssuranceCoordinator {
|
|
|
1728
1730
|
}
|
|
1729
1731
|
return { state: "blocked", reason: `Kernel requires ${settled.projection.next_obligation} after Review` };
|
|
1730
1732
|
}
|
|
1733
|
+
isReviewVerdictValid(taskId, verdictInput) {
|
|
1734
|
+
const reservation = this.reviewReservations.get(taskId);
|
|
1735
|
+
if (!reservation)
|
|
1736
|
+
return false;
|
|
1737
|
+
try {
|
|
1738
|
+
parseAssuranceVerdict(verdictInput, reservation.snapshot);
|
|
1739
|
+
return true;
|
|
1740
|
+
} catch {
|
|
1741
|
+
return false;
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1731
1744
|
abandonReview(taskId, reason) {
|
|
1732
1745
|
const reservation = this.reviewReservations.get(taskId);
|
|
1733
1746
|
if (!reservation)
|
|
@@ -1743,6 +1756,12 @@ class AssuranceCoordinator {
|
|
|
1743
1756
|
return { state: "blocked", code: "verdict_invalid", reason: "Review verdict correction is required before advancing" };
|
|
1744
1757
|
return { state: "review_ready", operation: "review", operation_id: reservation.operationId, snapshot_digest: snapshotDigest(reservation.snapshot), review_bundle_digest: reservation.snapshot.review_bundle_digest ?? "", agent_params: reservation.hostReservation.dispatch };
|
|
1745
1758
|
}
|
|
1759
|
+
releaseStoppedReview(taskId) {
|
|
1760
|
+
const reservation = this.reviewReservations.get(taskId);
|
|
1761
|
+
if (reservation)
|
|
1762
|
+
this.releaseReviewReservation(taskId, reservation);
|
|
1763
|
+
this.rejectedReviewOperations.delete(taskId);
|
|
1764
|
+
}
|
|
1746
1765
|
releaseReviewReservation(taskId, reservation, rejectionReason) {
|
|
1747
1766
|
if (this.reviewReservations.get(taskId) !== reservation)
|
|
1748
1767
|
return;
|
|
@@ -2484,9 +2503,6 @@ function captureReviewManifest(root, input) {
|
|
|
2484
2503
|
throw new Error("immutable review manifest metadata exceeds bounded output limit");
|
|
2485
2504
|
return manifest;
|
|
2486
2505
|
}
|
|
2487
|
-
function ensureReviewRevision(root, input) {
|
|
2488
|
-
return publishInput(root, input).revision;
|
|
2489
|
-
}
|
|
2490
2506
|
function writeNativeReviewEvidence(payload) {
|
|
2491
2507
|
const rawDirectory = mkdtempSync(join4(tmpdir2(), "imm-canary-native-review-"));
|
|
2492
2508
|
try {
|
|
@@ -2716,6 +2732,9 @@ var TASK_RECORD_CONTRACT_V2 = "assurance_kernel/task_record/v2";
|
|
|
2716
2732
|
var TASK_RECORD_CONTRACT_V3 = "assurance_kernel/task_record/v3";
|
|
2717
2733
|
var TASK_RECORD_CONTRACT_V4 = "assurance_kernel/task_record/v4";
|
|
2718
2734
|
var REVIEW_REVISION_IDENTITY_CONTRACT = "assurance_kernel/review_revision_identity/v1";
|
|
2735
|
+
function isTaskRecordV4(record) {
|
|
2736
|
+
return record.contract === TASK_RECORD_CONTRACT_V4;
|
|
2737
|
+
}
|
|
2719
2738
|
var REDUCED_MUTATION_BRAND = Symbol("assurance-kernel-reduced-mutation-v2");
|
|
2720
2739
|
var MUTATION_AUTHORITY_CAPABILITY_BRAND = Symbol("assurance-kernel-mutation-authority-capability");
|
|
2721
2740
|
|
|
@@ -3001,6 +3020,15 @@ function classifyIntentRevision(previous, next) {
|
|
|
3001
3020
|
return "breaking";
|
|
3002
3021
|
return next.revision > previous.revision ? "compatible" : "breaking";
|
|
3003
3022
|
}
|
|
3023
|
+
|
|
3024
|
+
class TaskIntentObservationError extends Error {
|
|
3025
|
+
code;
|
|
3026
|
+
constructor(code, message) {
|
|
3027
|
+
super(message);
|
|
3028
|
+
this.name = "TaskIntentObservationError";
|
|
3029
|
+
this.code = code;
|
|
3030
|
+
}
|
|
3031
|
+
}
|
|
3004
3032
|
var intentReaderTestHook = null;
|
|
3005
3033
|
function validateTaskId3(taskId) {
|
|
3006
3034
|
if (!TASK_ID_PATTERN.test(taskId))
|
|
@@ -3065,7 +3093,7 @@ function assertIdentitiesUnchanged(expected, canonicalRoot, relativePath) {
|
|
|
3065
3093
|
throw new Error(`path component changed while being read: ${parts.slice(0, index + 1).join("/")}`);
|
|
3066
3094
|
}
|
|
3067
3095
|
}
|
|
3068
|
-
function
|
|
3096
|
+
function readTaskIntentSource(root, taskId, requestedPath) {
|
|
3069
3097
|
validateTaskId3(taskId);
|
|
3070
3098
|
const canonicalRoot = resolveCanonicalRoot(root);
|
|
3071
3099
|
const activePath = `${INTENT_SIDECAR_RELATIVE_PREFIX}${taskId}.intent.json`;
|
|
@@ -3077,17 +3105,19 @@ function readTaskIntent(root, taskId, requestedPath) {
|
|
|
3077
3105
|
if (!target.startsWith(canonicalRoot + sep3))
|
|
3078
3106
|
throw new Error("intent sidecar escapes project root");
|
|
3079
3107
|
if (!sidecarPresent(canonicalRoot, sidecarPath))
|
|
3080
|
-
throw new
|
|
3108
|
+
throw new TaskIntentObservationError("missing", `TaskIntent sidecar is missing at ${sidecarPath}`);
|
|
3081
3109
|
const pathIdentities = collectPathIdentities(canonicalRoot, sidecarPath);
|
|
3082
3110
|
const fileIdentity = pathIdentities[pathIdentities.length - 1];
|
|
3083
3111
|
try {
|
|
3084
3112
|
execFileSync3("git", ["ls-files", "--error-unmatch", "--", sidecarPath], { cwd: canonicalRoot, stdio: ["ignore", "pipe", "pipe"] });
|
|
3085
|
-
} catch {
|
|
3086
|
-
|
|
3113
|
+
} catch (error) {
|
|
3114
|
+
if (typeof error === "object" && error !== null && "status" in error && error.status === 1)
|
|
3115
|
+
throw new TaskIntentObservationError("invalid", "TaskIntent sidecar is not Git-tracked");
|
|
3116
|
+
throw error;
|
|
3087
3117
|
}
|
|
3088
3118
|
const before = lstatSync4(target);
|
|
3089
3119
|
if (!before.isFile() || before.size > INTENT_MAX_BYTES)
|
|
3090
|
-
throw new
|
|
3120
|
+
throw new TaskIntentObservationError("invalid", "TaskIntent sidecar must be a regular file no larger than 64 KiB");
|
|
3091
3121
|
const fd = openSync2(target, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW);
|
|
3092
3122
|
let bytes;
|
|
3093
3123
|
try {
|
|
@@ -3099,7 +3129,7 @@ function readTaskIntent(root, taskId, requestedPath) {
|
|
|
3099
3129
|
closeSync2(fd);
|
|
3100
3130
|
}
|
|
3101
3131
|
if (bytes.byteLength > INTENT_MAX_BYTES)
|
|
3102
|
-
throw new
|
|
3132
|
+
throw new TaskIntentObservationError("invalid", "TaskIntent sidecar exceeds 64 KiB");
|
|
3103
3133
|
const after = lstatSync4(target);
|
|
3104
3134
|
assertSameIdentity(statIdentity(before), after, "intent sidecar");
|
|
3105
3135
|
assertIdentitiesUnchanged(pathIdentities, canonicalRoot, sidecarPath);
|
|
@@ -3113,23 +3143,11 @@ function readTaskIntent(root, taskId, requestedPath) {
|
|
|
3113
3143
|
try {
|
|
3114
3144
|
intent = parseTaskIntentV1(JSON.parse(bytes.toString("utf8")));
|
|
3115
3145
|
} catch (error) {
|
|
3116
|
-
throw new
|
|
3146
|
+
throw new TaskIntentObservationError("invalid", `TaskIntent sidecar is invalid: ${error instanceof Error ? error.message : String(error)}`);
|
|
3117
3147
|
}
|
|
3118
3148
|
if (intent.task_id !== taskId)
|
|
3119
|
-
throw new
|
|
3149
|
+
throw new TaskIntentObservationError("invalid", "intent.task_id does not match the sidecar filename task id");
|
|
3120
3150
|
const contentHash = canonicalIntentHash(intent);
|
|
3121
|
-
const token = mintToken({
|
|
3122
|
-
canonical_root: canonicalRoot,
|
|
3123
|
-
sidecar_path: sidecarPath,
|
|
3124
|
-
path_dev: fileIdentity.dev,
|
|
3125
|
-
path_ino: fileIdentity.ino,
|
|
3126
|
-
fd_dev: before.dev,
|
|
3127
|
-
fd_ino: before.ino,
|
|
3128
|
-
fd_size: before.size,
|
|
3129
|
-
fd_mtime_ms: before.mtimeMs,
|
|
3130
|
-
source_bytes_sha256: sourceBytesSha256,
|
|
3131
|
-
intent_content_hash: contentHash
|
|
3132
|
-
});
|
|
3133
3151
|
return {
|
|
3134
3152
|
intent,
|
|
3135
3153
|
content_hash: contentHash,
|
|
@@ -3138,9 +3156,24 @@ function readTaskIntent(root, taskId, requestedPath) {
|
|
|
3138
3156
|
revision: intent.revision,
|
|
3139
3157
|
content_hash: contentHash
|
|
3140
3158
|
},
|
|
3141
|
-
|
|
3159
|
+
identity: {
|
|
3160
|
+
canonical_root: canonicalRoot,
|
|
3161
|
+
sidecar_path: sidecarPath,
|
|
3162
|
+
path_dev: fileIdentity.dev,
|
|
3163
|
+
path_ino: fileIdentity.ino,
|
|
3164
|
+
fd_dev: before.dev,
|
|
3165
|
+
fd_ino: before.ino,
|
|
3166
|
+
fd_size: before.size,
|
|
3167
|
+
fd_mtime_ms: before.mtimeMs,
|
|
3168
|
+
source_bytes_sha256: sourceBytesSha256,
|
|
3169
|
+
intent_content_hash: contentHash
|
|
3170
|
+
}
|
|
3142
3171
|
};
|
|
3143
3172
|
}
|
|
3173
|
+
function readTaskIntent(root, taskId, requestedPath) {
|
|
3174
|
+
const { identity, ...observed } = readTaskIntentSource(root, taskId, requestedPath);
|
|
3175
|
+
return { ...observed, token: mintToken(identity) };
|
|
3176
|
+
}
|
|
3144
3177
|
|
|
3145
3178
|
// plugins/immune-brain/runtime/kernel/validation.ts
|
|
3146
3179
|
var GIT_OBJECT_ID3 = /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/;
|
|
@@ -3645,6 +3678,7 @@ var ACTION_V2_TYPES = [
|
|
|
3645
3678
|
"request_rework",
|
|
3646
3679
|
"complete",
|
|
3647
3680
|
"stop",
|
|
3681
|
+
"authorize_rework",
|
|
3648
3682
|
"resolve_user_decision"
|
|
3649
3683
|
];
|
|
3650
3684
|
var ACTION_BASE_FIELDS = [
|
|
@@ -3740,7 +3774,8 @@ function parseTaskAction(raw) {
|
|
|
3740
3774
|
};
|
|
3741
3775
|
break;
|
|
3742
3776
|
}
|
|
3743
|
-
case "complete":
|
|
3777
|
+
case "complete":
|
|
3778
|
+
case "authorize_rework": {
|
|
3744
3779
|
rejectUnknown2(value, [...ACTION_BASE_FIELDS], "action", violations);
|
|
3745
3780
|
action = { ...base, type: base.type };
|
|
3746
3781
|
break;
|
|
@@ -3798,7 +3833,7 @@ function assertTaskRecordUpdateV3(previousRaw, nextRaw, action) {
|
|
|
3798
3833
|
violations.push("non-intent action cannot change the intent snapshot");
|
|
3799
3834
|
if (next.intent_ref.content_hash !== previous.intent_ref.content_hash)
|
|
3800
3835
|
violations.push("non-intent action cannot change intent_ref content hash");
|
|
3801
|
-
if (next.intent_ref.path !== previous.intent_ref.path && action.type !== "request_rework" && action.type !== "stop")
|
|
3836
|
+
if (next.intent_ref.path !== previous.intent_ref.path && action.type !== "request_rework" && action.type !== "authorize_rework" && action.type !== "stop")
|
|
3802
3837
|
violations.push("only artifact transitions may change intent_ref path");
|
|
3803
3838
|
}
|
|
3804
3839
|
if (next.attestations.length < previous.attestations.length)
|
|
@@ -3810,7 +3845,7 @@ function assertTaskRecordUpdateV3(previousRaw, nextRaw, action) {
|
|
|
3810
3845
|
if (!current || JSON.stringify(current) !== JSON.stringify(prior))
|
|
3811
3846
|
violations.push(`attestation ${prior.id} was rewritten`);
|
|
3812
3847
|
}
|
|
3813
|
-
const resolvingFindingIds = action.type === "resolve_finding" ? [action.finding_id] : action.type === "resolve_user_decision" ? [action.finding_id] : action.type === "approve_breaking_intent_revision" ? previous.findings.filter((item) => item.kind === "replan_required" && item.status === "open").map((item) => item.id) : [];
|
|
3848
|
+
const resolvingFindingIds = action.type === "resolve_finding" ? [action.finding_id] : action.type === "resolve_user_decision" ? [action.finding_id] : action.type === "authorize_rework" || action.type === "approve_breaking_intent_revision" ? previous.findings.filter((item) => item.kind === "replan_required" && item.status === "open").map((item) => item.id) : [];
|
|
3814
3849
|
const reworkFindingIds = action.type === "request_rework" ? new Set(action.findings.map((item) => item.id)) : new Set;
|
|
3815
3850
|
for (const prior of previous.findings) {
|
|
3816
3851
|
const current = next.findings.find((item) => item.id === prior.id);
|
|
@@ -5039,6 +5074,8 @@ function deriveAssuranceAuthorization(input) {
|
|
|
5039
5074
|
state: "none",
|
|
5040
5075
|
blocked: `resolve-user-decision requires exactly one open user decision; found ${input.open_user_decision_count}`
|
|
5041
5076
|
};
|
|
5077
|
+
if (input.next_obligation === "revise_intent")
|
|
5078
|
+
return { state: "authorize_rework", blocked: null };
|
|
5042
5079
|
return { state: "none", blocked: null };
|
|
5043
5080
|
}
|
|
5044
5081
|
function emptyProjection() {
|
|
@@ -5289,7 +5326,7 @@ function intentRefMatches(intent, ref) {
|
|
|
5289
5326
|
return ref.path === `docs/plans/${intent.task_id}.intent.json` && ref.content_hash === canonicalIntentHash(intent);
|
|
5290
5327
|
}
|
|
5291
5328
|
function hasPrivilegedKind(action) {
|
|
5292
|
-
return action.type === "record_approval" || action.type === "approve_breaking_intent_revision" || action.type === "request_rework" || action.type === "stop" || action.type === "resolve_user_decision";
|
|
5329
|
+
return action.type === "record_approval" || action.type === "approve_breaking_intent_revision" || action.type === "request_rework" || action.type === "authorize_rework" || action.type === "stop" || action.type === "resolve_user_decision";
|
|
5293
5330
|
}
|
|
5294
5331
|
function findingsDigestV2(findings) {
|
|
5295
5332
|
const normalized = findings.map((finding) => ({
|
|
@@ -5507,8 +5544,8 @@ function reduceTask(recordRaw, actionRaw, authorityAudit = null, changedPaths) {
|
|
|
5507
5544
|
"request_rework requires review, qa, or user authority"
|
|
5508
5545
|
]);
|
|
5509
5546
|
const round = reviewRound(record);
|
|
5510
|
-
const
|
|
5511
|
-
const parkForReplan = authorityAudit.authority_kind === "review" &&
|
|
5547
|
+
const hasPriorBlockingReviewRework = record.findings.some((finding) => finding.source === "review" && finding.kind === "blocking" && finding.review_round !== null);
|
|
5548
|
+
const parkForReplan = authorityAudit.authority_kind === "review" && hasPriorBlockingReviewRework && action.findings.some((finding) => finding.kind === "blocking");
|
|
5512
5549
|
if (!parkForReplan) {
|
|
5513
5550
|
record.artifact_state = "active";
|
|
5514
5551
|
record.intent_ref.path = `docs/plans/${record.task_id}.intent.json`;
|
|
@@ -5523,8 +5560,8 @@ function reduceTask(recordRaw, actionRaw, authorityAudit = null, changedPaths) {
|
|
|
5523
5560
|
record.findings.push({
|
|
5524
5561
|
...finding,
|
|
5525
5562
|
status: "open",
|
|
5526
|
-
source: "review",
|
|
5527
|
-
review_round: round
|
|
5563
|
+
source: authorityAudit.authority_kind === "review" ? "review" : "execution",
|
|
5564
|
+
review_round: authorityAudit.authority_kind === "review" ? round : null
|
|
5528
5565
|
});
|
|
5529
5566
|
}
|
|
5530
5567
|
if (parkForReplan && !record.findings.some((item) => item.status === "open" && item.kind === "replan_required")) {
|
|
@@ -5547,6 +5584,27 @@ function reduceTask(recordRaw, actionRaw, authorityAudit = null, changedPaths) {
|
|
|
5547
5584
|
appendHistory(record, action, from, `review_round_${round}`, authorityAudit);
|
|
5548
5585
|
break;
|
|
5549
5586
|
}
|
|
5587
|
+
case "authorize_rework": {
|
|
5588
|
+
if (record.lifecycle !== "active")
|
|
5589
|
+
throw new KernelInvariantError([
|
|
5590
|
+
`cannot authorize rework while lifecycle is ${record.lifecycle}`
|
|
5591
|
+
]);
|
|
5592
|
+
if (authorityAudit?.authority_kind !== "user")
|
|
5593
|
+
throw new KernelInvariantError([
|
|
5594
|
+
"authorize_rework requires literal-user authority"
|
|
5595
|
+
]);
|
|
5596
|
+
const open = record.findings.filter((finding) => finding.kind === "replan_required" && finding.status === "open");
|
|
5597
|
+
if (open.length === 0)
|
|
5598
|
+
throw new KernelInvariantError([
|
|
5599
|
+
"authorize_rework requires an open replan boundary"
|
|
5600
|
+
]);
|
|
5601
|
+
for (const finding of open)
|
|
5602
|
+
finding.status = "resolved";
|
|
5603
|
+
record.artifact_state = "active";
|
|
5604
|
+
record.intent_ref.path = `docs/plans/${record.task_id}.intent.json`;
|
|
5605
|
+
appendHistory(record, action, from, open.map((finding) => finding.id).join(","), authorityAudit);
|
|
5606
|
+
break;
|
|
5607
|
+
}
|
|
5550
5608
|
case "complete": {
|
|
5551
5609
|
if (record.lifecycle !== "active" || record.artifact_state !== "frozen")
|
|
5552
5610
|
throw new KernelInvariantError([
|
|
@@ -5677,7 +5735,7 @@ function applyTaskAction(input) {
|
|
|
5677
5735
|
"intent token does not match the committed record intent"
|
|
5678
5736
|
]);
|
|
5679
5737
|
}
|
|
5680
|
-
const privileged = action.type === "record_approval" || action.type === "approve_breaking_intent_revision" || action.type === "request_rework" || action.type === "stop" || action.type === "resolve_user_decision";
|
|
5738
|
+
const privileged = action.type === "record_approval" || action.type === "approve_breaking_intent_revision" || action.type === "request_rework" || action.type === "authorize_rework" || action.type === "stop" || action.type === "resolve_user_decision";
|
|
5681
5739
|
const expectedAuthority = privileged ? {
|
|
5682
5740
|
task_id,
|
|
5683
5741
|
action,
|
|
@@ -5737,6 +5795,8 @@ function applyTaskAction(input) {
|
|
|
5737
5795
|
};
|
|
5738
5796
|
}
|
|
5739
5797
|
if (input.terminal) {
|
|
5798
|
+
if (nextRecord.lifecycle === "active")
|
|
5799
|
+
throw new Error("terminal settlement requires a done or stopped TaskRecord lifecycle");
|
|
5740
5800
|
const tombstone = {
|
|
5741
5801
|
contract: TASK_TOMBSTONE_CONTRACT,
|
|
5742
5802
|
task_id,
|
|
@@ -5801,6 +5861,8 @@ function capabilityActionFor(input) {
|
|
|
5801
5861
|
return { ...base, approval: input.approval };
|
|
5802
5862
|
case "request_rework":
|
|
5803
5863
|
return { ...base, findings: input.findings };
|
|
5864
|
+
case "authorize_rework":
|
|
5865
|
+
return { ...base, type: "authorize_rework" };
|
|
5804
5866
|
case "stop":
|
|
5805
5867
|
return { ...base, reason: input.reason };
|
|
5806
5868
|
case "approve_breaking_intent_revision":
|
|
@@ -5947,7 +6009,7 @@ function createCanaryApplication(registry) {
|
|
|
5947
6009
|
const hasBoundSpec = snapshot.intent_snapshot.scope_hint.some((path) => /^docs\/specs\/(?!archive\/)[^/]+\.spec\.md$/.test(path) && snapshot.intent_snapshot.scope_hint.includes(archivePath(path)));
|
|
5948
6010
|
if (operation.op === "complete" && hasBoundSpec && snapshot.record.artifact_state !== "frozen")
|
|
5949
6011
|
throw new KernelInvariantError(["complete requires frozen planning artifacts"]);
|
|
5950
|
-
const artifactTransition = snapshot.record.artifact_state === "frozen" && (operation.op === "request_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;
|
|
6012
|
+
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;
|
|
5951
6013
|
const event_id = `${operation.op}:${input.task_id}:${at}`;
|
|
5952
6014
|
const base = {
|
|
5953
6015
|
event_id,
|
|
@@ -6013,6 +6075,10 @@ function createCanaryApplication(registry) {
|
|
|
6013
6075
|
capability = operation.capability;
|
|
6014
6076
|
action = { ...base, type: "stop", reason: operation.reason };
|
|
6015
6077
|
break;
|
|
6078
|
+
case "authorize_rework":
|
|
6079
|
+
capability = operation.capability;
|
|
6080
|
+
action = { ...base, type: "authorize_rework" };
|
|
6081
|
+
break;
|
|
6016
6082
|
case "resolve_user_decision":
|
|
6017
6083
|
capability = operation.capability;
|
|
6018
6084
|
action = {
|
|
@@ -6475,7 +6541,18 @@ function enrollCanaryTask(root, input, registry) {
|
|
|
6475
6541
|
throw new Error("intent content hash mismatch");
|
|
6476
6542
|
if (checks.gitBaseHead !== gitBaseHead)
|
|
6477
6543
|
throw new Error("Git HEAD moved after the enrollment confirmation");
|
|
6544
|
+
if (input.batch) {
|
|
6545
|
+
const batch = input.batch.registry.inspect(input.batch.capability, input.batch.binding, Date.parse(input.now));
|
|
6546
|
+
if (input.batch.registry.consumedChildren(input.batch.capability).length === 0 && input.batch.expected_head !== batch.base_head)
|
|
6547
|
+
throw new Error(`batch_head_lineage_broken: the first child must enroll on the confirmed base_head ${batch.base_head}, not ${input.batch.expected_head}`);
|
|
6548
|
+
if (checks.gitBaseHead !== input.batch.expected_head)
|
|
6549
|
+
throw new Error(`batch_head_lineage_broken: expected ${input.batch.expected_head}, found ${checks.gitBaseHead}`);
|
|
6550
|
+
}
|
|
6478
6551
|
registry.consume(input.capability, input.capability_binding);
|
|
6552
|
+
if (input.batch)
|
|
6553
|
+
input.batch.registry.consumeChild(input.batch.capability, input.batch.binding, input.task_id, Date.parse(input.now));
|
|
6554
|
+
if (!gitBaseHead)
|
|
6555
|
+
throw new Error("enrollment requires a committed Git HEAD");
|
|
6479
6556
|
const record = buildTaskRecordV4(input, checks.intent, gitBaseHead);
|
|
6480
6557
|
const nextWorkspace = {
|
|
6481
6558
|
...checks.workspace.state,
|
|
@@ -6492,16 +6569,23 @@ function enrollCanaryTask(root, input, registry) {
|
|
|
6492
6569
|
created_at: input.now,
|
|
6493
6570
|
updated_at: input.now
|
|
6494
6571
|
};
|
|
6495
|
-
|
|
6496
|
-
|
|
6497
|
-
|
|
6498
|
-
|
|
6499
|
-
|
|
6572
|
+
let mutation;
|
|
6573
|
+
try {
|
|
6574
|
+
mutation = commitEnrollmentLocked(root, input.task_id, {
|
|
6575
|
+
contract: "assurance_kernel/workspace_transaction/v2",
|
|
6576
|
+
task_id: input.task_id,
|
|
6577
|
+
expected_record_hash: checks.current.revision,
|
|
6578
|
+
next_record_content: `${JSON.stringify(record, null, 2)}
|
|
6500
6579
|
`,
|
|
6501
|
-
|
|
6502
|
-
|
|
6580
|
+
expected_workspace_hash: checks.workspace.revision,
|
|
6581
|
+
next_workspace_content: `${JSON.stringify(nextWorkspace, null, 2)}
|
|
6503
6582
|
`
|
|
6504
|
-
|
|
6583
|
+
}, claim);
|
|
6584
|
+
} catch (error) {
|
|
6585
|
+
if (input.batch)
|
|
6586
|
+
input.batch.registry.releaseChild(input.batch.capability, input.task_id);
|
|
6587
|
+
throw error;
|
|
6588
|
+
}
|
|
6505
6589
|
return {
|
|
6506
6590
|
record: mutation.record,
|
|
6507
6591
|
backend_claim: claim,
|
|
@@ -6641,16 +6725,23 @@ async function submitClaudeReview(host, coordinator, ctx, taskId, verdictInput)
|
|
|
6641
6725
|
return coordinator.abandonReview(taskId, observed.reason);
|
|
6642
6726
|
return { state: "blocked", reason: observed.reason };
|
|
6643
6727
|
}
|
|
6728
|
+
const parentValid = coordinator.isReviewVerdictValid(taskId, verdictInput);
|
|
6729
|
+
if (!parentValid)
|
|
6730
|
+
return coordinator.submitReview(taskId, ctx, verdictInput);
|
|
6731
|
+
const receiptValid = coordinator.isReviewVerdictValid(taskId, observed.receipt.result);
|
|
6732
|
+
if (!receiptValid) {
|
|
6733
|
+
return coordinator.abandonReview(taskId, "reviewer receipt is not a valid verdict");
|
|
6734
|
+
}
|
|
6644
6735
|
const parentJson = extractVerdictJson(verdictInput);
|
|
6645
6736
|
const receiptJson = extractVerdictJson(observed.receipt.result);
|
|
6646
|
-
if (
|
|
6737
|
+
if (verdictFingerprint(parentJson) !== verdictFingerprint(receiptJson)) {
|
|
6647
6738
|
return { state: "blocked", reason: "parent verdict does not match reviewer receipt" };
|
|
6648
6739
|
}
|
|
6649
|
-
if (parentJson && !receiptJson) {
|
|
6650
|
-
return { state: "blocked", reason: "reviewer receipt is not a valid verdict" };
|
|
6651
|
-
}
|
|
6652
6740
|
return coordinator.submitReview(taskId, ctx, verdictInput);
|
|
6653
6741
|
}
|
|
6742
|
+
function stopReason(value) {
|
|
6743
|
+
return typeof value === "string" && value.length > 0 ? value : "user stop";
|
|
6744
|
+
}
|
|
6654
6745
|
function assertProjectionBinding(before, after, allowDiffChange = false) {
|
|
6655
6746
|
const fields = allowDiffChange ? ["record_revision", "workspace_revision", "intent_revision", "intent_content_hash"] : ["record_revision", "workspace_revision", "intent_revision", "intent_content_hash", "diff_hash"];
|
|
6656
6747
|
if (before.error || !before.claim || after.error || !after.claim || before.claim.task_id !== after.claim.task_id || fields.some((field) => before.projection[field] !== after.projection[field])) {
|
|
@@ -6660,22 +6751,57 @@ function assertProjectionBinding(before, after, allowDiffChange = false) {
|
|
|
6660
6751
|
function qaOutcomes(record) {
|
|
6661
6752
|
return Object.fromEntries(record.attestations.filter((item) => item.kind === "qa").flatMap((item) => item.acceptance_results).map((result) => [result.acceptance_id, { status: result.status, summary: result.summary }]));
|
|
6662
6753
|
}
|
|
6754
|
+
async function ensureClaudeReviewRevision(root, taskId, projection) {
|
|
6755
|
+
const current = await readTaskRecord(root, taskId);
|
|
6756
|
+
const record = current.record;
|
|
6757
|
+
if (!record)
|
|
6758
|
+
throw new Error(`task ${taskId} has no TaskRecord`);
|
|
6759
|
+
if (current.revision !== projection.projection.record_revision)
|
|
6760
|
+
throw new Error("TaskRecord changed before Review revision preparation");
|
|
6761
|
+
if (record.contract !== "assurance_kernel/task_record/v4")
|
|
6762
|
+
return null;
|
|
6763
|
+
if (!record.git_base_head)
|
|
6764
|
+
throw new Error("Review revision requires a TaskRecord v4 git_base_head");
|
|
6765
|
+
const manifest = captureReviewManifest(root, {
|
|
6766
|
+
taskId,
|
|
6767
|
+
baseHead: record.git_base_head,
|
|
6768
|
+
scopeHint: record.intent_snapshot.scope_hint,
|
|
6769
|
+
expectedDiffHash: projection.projection.diff_hash,
|
|
6770
|
+
intentRevision: projection.projection.intent_revision,
|
|
6771
|
+
intentContentHash: projection.projection.intent_content_hash,
|
|
6772
|
+
recordRevision: projection.projection.record_revision,
|
|
6773
|
+
workspaceRevision: projection.projection.workspace_revision,
|
|
6774
|
+
lifecycle: projection.projection.lifecycle,
|
|
6775
|
+
artifactState: projection.projection.artifact_state,
|
|
6776
|
+
risk: record.intent_snapshot.risk,
|
|
6777
|
+
outcomes: qaOutcomes(record)
|
|
6778
|
+
});
|
|
6779
|
+
return {
|
|
6780
|
+
contract: "assurance_kernel/review_revision/v1",
|
|
6781
|
+
base_head: manifest.base_head,
|
|
6782
|
+
review_tree: manifest.review_tree,
|
|
6783
|
+
review_commit: manifest.review_commit,
|
|
6784
|
+
review_ref: manifest.review_ref,
|
|
6785
|
+
diff_hash: manifest.diff_hash,
|
|
6786
|
+
manifest_digest: manifest.manifest_digest
|
|
6787
|
+
};
|
|
6788
|
+
}
|
|
6663
6789
|
async function buildAssuranceSnapshot(root, taskId, role, projection, runner) {
|
|
6664
|
-
const
|
|
6665
|
-
|
|
6790
|
+
const read = await readTaskRecord(root, taskId);
|
|
6791
|
+
const record = read.record;
|
|
6792
|
+
if (!record || read.revision !== projection.projection.record_revision)
|
|
6666
6793
|
throw new Error("TaskRecord changed before assurance snapshot capture");
|
|
6667
|
-
const intent = record.
|
|
6794
|
+
const intent = record.intent_snapshot;
|
|
6668
6795
|
const descriptors = new Map;
|
|
6669
6796
|
for (const item of intent.acceptance) {
|
|
6670
6797
|
const descriptor = parseVerificationDescriptor(item.verification);
|
|
6671
6798
|
assertRunnerCompatible(descriptor, runner);
|
|
6672
6799
|
descriptors.set(item.id, descriptor);
|
|
6673
6800
|
}
|
|
6674
|
-
const
|
|
6675
|
-
const
|
|
6676
|
-
const reviewManifest = role === "review" && v4 ? captureReviewManifest(root, {
|
|
6801
|
+
const reviewBundle = role === "review" && !isTaskRecordV4(record) ? captureReviewBundle(root, intent.scope_hint, projection.projection.diff_hash, qaOutcomes(record)) : null;
|
|
6802
|
+
const reviewManifest = role === "review" && isTaskRecordV4(record) ? captureReviewManifest(root, {
|
|
6677
6803
|
taskId,
|
|
6678
|
-
baseHead: record.
|
|
6804
|
+
baseHead: record.git_base_head,
|
|
6679
6805
|
scopeHint: intent.scope_hint,
|
|
6680
6806
|
expectedDiffHash: projection.projection.diff_hash,
|
|
6681
6807
|
intentRevision: projection.projection.intent_revision,
|
|
@@ -6685,7 +6811,7 @@ async function buildAssuranceSnapshot(root, taskId, role, projection, runner) {
|
|
|
6685
6811
|
lifecycle: projection.projection.lifecycle,
|
|
6686
6812
|
artifactState: projection.projection.artifact_state,
|
|
6687
6813
|
risk: intent.risk,
|
|
6688
|
-
outcomes: qaOutcomes(record
|
|
6814
|
+
outcomes: qaOutcomes(record)
|
|
6689
6815
|
}) : null;
|
|
6690
6816
|
const dirtyFiles = reviewManifest ? Object.keys(reviewManifest.changed_paths) : reviewBundle ? Object.keys(reviewBundle.dirty_files) : [];
|
|
6691
6817
|
const snapshot = {
|
|
@@ -6784,11 +6910,11 @@ class ClaudeRuntime {
|
|
|
6784
6910
|
this.interactive = options.interactive ?? true;
|
|
6785
6911
|
this.requestConfirmation = options.requestConfirmation;
|
|
6786
6912
|
this.host = options.host ?? new ClaudeReviewHost(new FileHookEventLog);
|
|
6787
|
-
|
|
6788
|
-
this.
|
|
6789
|
-
|
|
6790
|
-
|
|
6791
|
-
|
|
6913
|
+
this.coordinator = new AssuranceCoordinator({
|
|
6914
|
+
...this.createKernelPorts(),
|
|
6915
|
+
...options.ports,
|
|
6916
|
+
host: this.host
|
|
6917
|
+
});
|
|
6792
6918
|
}
|
|
6793
6919
|
observe(event) {
|
|
6794
6920
|
this.host.observe(event);
|
|
@@ -6802,27 +6928,18 @@ class ClaudeRuntime {
|
|
|
6802
6928
|
async shutdown() {
|
|
6803
6929
|
await this.coordinator.onSessionShutdown();
|
|
6804
6930
|
}
|
|
6931
|
+
kernelPorts() {
|
|
6932
|
+
return this.createKernelPorts();
|
|
6933
|
+
}
|
|
6805
6934
|
createKernelPorts() {
|
|
6806
6935
|
return {
|
|
6807
6936
|
host: this.host,
|
|
6808
6937
|
projectTask: (root, taskId) => projectAssurance(root, taskId, diffSnapshotOf),
|
|
6809
|
-
readTaskRecord: (root, taskId) => readTaskRecord(root, taskId),
|
|
6810
|
-
readTaskIntent: (root, taskId) => readTaskIntentForRecord(root, taskId),
|
|
6938
|
+
readTaskRecord: async (root, taskId) => readTaskRecord(root, taskId),
|
|
6939
|
+
readTaskIntent: async (root, taskId) => readTaskIntentForRecord(root, taskId),
|
|
6811
6940
|
frozenRunner: async () => resolveBunRunner(),
|
|
6812
6941
|
buildAssurance: (root, taskId, role, projection, runner) => buildAssuranceSnapshot(root, taskId, role, projection, runner),
|
|
6813
|
-
ensureReviewRevision:
|
|
6814
|
-
const current = await readTaskRecord(root, taskId);
|
|
6815
|
-
if (!current.record)
|
|
6816
|
-
throw new Error(`task ${taskId} has no TaskRecord`);
|
|
6817
|
-
if (current.record.contract !== "assurance_kernel/task_record/v4")
|
|
6818
|
-
return null;
|
|
6819
|
-
return ensureReviewRevision(root, {
|
|
6820
|
-
taskId,
|
|
6821
|
-
baseHead: current.record.git_base_head,
|
|
6822
|
-
scopeHint: current.record.intent_snapshot.scope_hint,
|
|
6823
|
-
expectedDiffHash: projection.projection.diff_hash
|
|
6824
|
-
});
|
|
6825
|
-
},
|
|
6942
|
+
ensureReviewRevision: (root, taskId, projection) => ensureClaudeReviewRevision(root, taskId, projection),
|
|
6826
6943
|
runQa: (snapshot, descriptors, runner, options) => runDeterministicQa(snapshot, descriptors, runner, options),
|
|
6827
6944
|
writeReviewEvidence: (input) => writeNativeReviewEvidence(input.evidence),
|
|
6828
6945
|
applyVerdict: (ctx, input) => this.applyVerdict(ctx, input),
|
|
@@ -6915,6 +7032,12 @@ class ClaudeRuntime {
|
|
|
6915
7032
|
async submitReview(taskId, verdictInput) {
|
|
6916
7033
|
return submitClaudeReview(this.host, this.coordinator, { cwd: this.cwd }, taskId, verdictInput);
|
|
6917
7034
|
}
|
|
7035
|
+
async resolveFinding(taskId, findingId) {
|
|
7036
|
+
return this.executeOrdinary({ cwd: this.cwd }, {
|
|
7037
|
+
taskId,
|
|
7038
|
+
operation: { op: "resolve_finding", finding_id: findingId, actor_id: "executor" }
|
|
7039
|
+
});
|
|
7040
|
+
}
|
|
6918
7041
|
async authorize(taskId, operation, meta, extra = {}) {
|
|
6919
7042
|
if (operation === "repair_authority_state") {
|
|
6920
7043
|
const authority = reconcileKernelAuthority(this.cwd, taskId);
|
|
@@ -6939,6 +7062,8 @@ class ClaudeRuntime {
|
|
|
6939
7062
|
throw new Error(`resolve-user-decision requires exactly one open user decision; found ${open.length}`);
|
|
6940
7063
|
op = "resolve_user_decision";
|
|
6941
7064
|
decisionOp = { finding_id: open[0].id, resolution: `resume after literal-user decision: ${open[0].summary}` };
|
|
7065
|
+
} else if (readiness.state === "authorize_rework") {
|
|
7066
|
+
op = "authorize_rework";
|
|
6942
7067
|
} else {
|
|
6943
7068
|
throw new Error(readiness.blocked ?? "no unique host-derived authorization operation");
|
|
6944
7069
|
}
|
|
@@ -7037,7 +7162,7 @@ class ClaudeRuntime {
|
|
|
7037
7162
|
confirmation_ref: confirmation,
|
|
7038
7163
|
...op === "approve_breaking_intent_revision" ? { next_intent: nextIntent, next_intent_ref: nextIntentRef } : {},
|
|
7039
7164
|
...op === "resolve_user_decision" && decisionOp ? decisionOp : {},
|
|
7040
|
-
...op === "stop" ? { reason: extra.reason
|
|
7165
|
+
...op === "stop" ? { reason: stopReason(extra.reason) } : {}
|
|
7041
7166
|
});
|
|
7042
7167
|
throwIfCancelled(meta.signal);
|
|
7043
7168
|
const result = app.execute({
|
|
@@ -7049,13 +7174,13 @@ class ClaudeRuntime {
|
|
|
7049
7174
|
actor_id: actorId,
|
|
7050
7175
|
...op === "approve_breaking_intent_revision" ? { next_intent: nextIntent, next_intent_ref: nextIntentRef } : {},
|
|
7051
7176
|
...op === "resolve_user_decision" && decisionOp ? decisionOp : {},
|
|
7052
|
-
...op === "stop" ? { reason: extra.reason
|
|
7177
|
+
...op === "stop" ? { reason: stopReason(extra.reason) } : {}
|
|
7053
7178
|
},
|
|
7054
7179
|
prior_intent_token: priorIntent.token,
|
|
7055
7180
|
diffProvider: diffSnapshotOf,
|
|
7056
7181
|
now
|
|
7057
7182
|
});
|
|
7058
|
-
if (op === "stop" || op === "approve_breaking_intent_revision")
|
|
7183
|
+
if (op === "stop" || op === "authorize_rework" || op === "approve_breaking_intent_revision")
|
|
7059
7184
|
stagePlanningArtifactTransition(this.cwd, result.record);
|
|
7060
7185
|
return result;
|
|
7061
7186
|
} catch (error) {
|
|
@@ -7191,7 +7316,8 @@ var TOOLS = [
|
|
|
7191
7316
|
{ name: "request_authorization", description: "Apply exact literal-user authorization.", privileged: true },
|
|
7192
7317
|
{ name: "approve_breaking_intent_revision", description: "Approve a breaking TaskIntent revision.", privileged: true },
|
|
7193
7318
|
{ name: "stop", description: "Stop the active task with literal-user authority.", privileged: true },
|
|
7194
|
-
{ name: "repair_authority_state", description: "Repair a proven recoverable stale backend claim.", privileged: false }
|
|
7319
|
+
{ name: "repair_authority_state", description: "Repair a proven recoverable stale backend claim.", privileged: false },
|
|
7320
|
+
{ name: "resolve_finding", description: "Resolve one open blocking or advisory finding whose cause is fixed and verified.", privileged: false }
|
|
7195
7321
|
];
|
|
7196
7322
|
function listMcpTools() {
|
|
7197
7323
|
return TOOLS.map((tool) => ({
|
|
@@ -7203,9 +7329,10 @@ function listMcpTools() {
|
|
|
7203
7329
|
task_id: { type: "string" },
|
|
7204
7330
|
...tool.name === "approve_breaking_intent_revision" ? { next_intent: { type: "object" } } : {},
|
|
7205
7331
|
...tool.name === "stop" ? { reason: { type: "string" } } : {},
|
|
7206
|
-
...tool.name === "submit_review" ? { verdict: { type: "object" } } : {}
|
|
7332
|
+
...tool.name === "submit_review" ? { verdict: { type: "object" } } : {},
|
|
7333
|
+
...tool.name === "resolve_finding" ? { finding_id: { type: "string" } } : {}
|
|
7207
7334
|
},
|
|
7208
|
-
required: tool.name === "submit_review" ? ["task_id", "verdict"] : ["task_id"]
|
|
7335
|
+
required: tool.name === "submit_review" ? ["task_id", "verdict"] : tool.name === "resolve_finding" ? ["task_id", "finding_id"] : ["task_id"]
|
|
7209
7336
|
},
|
|
7210
7337
|
annotations: tool.privileged ? privilegedAnnotations() : { readOnlyHint: tool.name === "status" }
|
|
7211
7338
|
}));
|
|
@@ -7272,6 +7399,11 @@ function createMcpRuntime(options = {}) {
|
|
|
7272
7399
|
throw new Error("verdict is required");
|
|
7273
7400
|
return runtime.submitReview(taskId, args.verdict);
|
|
7274
7401
|
}
|
|
7402
|
+
if (name === "resolve_finding") {
|
|
7403
|
+
if (typeof args.finding_id !== "string" || !args.finding_id)
|
|
7404
|
+
throw new Error("finding_id is required");
|
|
7405
|
+
return runtime.resolveFinding(taskId, args.finding_id);
|
|
7406
|
+
}
|
|
7275
7407
|
if (name === "request_authorization" || name === "approve_breaking_intent_revision" || name === "stop" || name === "repair_authority_state") {
|
|
7276
7408
|
return runtime.authorize(taskId, name, toolMeta, args);
|
|
7277
7409
|
}
|
|
@@ -83,8 +83,14 @@ not bypass them. Do not poll or create detached jobs.
|
|
|
83
83
|
- Invoke `approve_breaking_intent_revision` with the complete next intent
|
|
84
84
|
directly; the native Host gate is the single user decision. Do not overwrite
|
|
85
85
|
enrolled intent sidecars or ask for chat pre-confirmation.
|
|
86
|
-
- On `awaiting_user`, invoke `request_authorization` directly
|
|
87
|
-
|
|
86
|
+
- On `awaiting_user`, invoke `request_authorization` directly for a concrete
|
|
87
|
+
unresolved decision or rework authorization, not risk tier alone.
|
|
88
|
+
- When the user explicitly asks to stop a Pi task, invoke
|
|
89
|
+
`imm_kernel_canary({ task_id, action: { op: "request_stop" } })` directly.
|
|
90
|
+
Its single native confirmation authorizes existing Kernel stop settlement.
|
|
91
|
+
Cancellation is not task termination. A busy invocation must finish or be
|
|
92
|
+
cancelled through existing Host controls before requesting stop; never clear
|
|
93
|
+
claims manually or use this operation to force-kill QA.
|
|
88
94
|
- Invoke `repair_authority_state` directly for a proven stale claim. Kernel
|
|
89
95
|
revalidation removes only the redundant claim without user interaction.
|
|
90
96
|
- A Managed native authority failure stays fail-closed. Report its stable reason
|
|
@@ -163,18 +163,23 @@ After approval, author, stage, and validate every TaskIntent in the decompositio
|
|
|
163
163
|
with `valid: true` and `enrollment_ready: true`. Resolve `../bin/imm-tracker` from this packaged contract; do not assume a bare command is on `PATH`. Submit the entire approved set once through
|
|
164
164
|
`imm-tracker publish-initiative --stdin --json`. Its input contains the confirmed
|
|
165
165
|
Initiative slug and goal, Parent projection, and every Child's `slice_id`,
|
|
166
|
-
canonical TaskIntent path,
|
|
166
|
+
canonical TaskIntent path, bounded public `acceptance` summaries, and public
|
|
167
|
+
projection. The Parent projection requires
|
|
167
168
|
`problem`, `result`, and `design`, and may include `decisions`,
|
|
168
169
|
`testing_strategy`, and `out_of_scope`. `design` records Initiative-level
|
|
169
170
|
invariants, Slice boundaries and ordering, shared interfaces or state flow, and
|
|
170
171
|
material compatibility decisions. Every Parent Slice must correspond to one
|
|
171
172
|
published Child; future checklist-only Slices are not allowed in the batch.
|
|
172
173
|
|
|
173
|
-
Each Child
|
|
174
|
+
Each Child must provide public `acceptance` entries with `id` and a 1-500
|
|
175
|
+
character `summary`. Their IDs must match every canonical TaskIntent acceptance
|
|
176
|
+
ID exactly once. Canonical assertion prose is authority evidence and must never
|
|
177
|
+
be copied into public GitHub projection. Each Child projection may contain
|
|
178
|
+
`result`, `current_behavior`,
|
|
174
179
|
`desired_behavior`, `key_interfaces`, `verification`, `blocked_by` Task IDs,
|
|
175
180
|
`out_of_scope`, and `agent_handoff`. The tracker rereads every canonical
|
|
176
|
-
TaskIntent for identity, risk, and acceptance; projection fields
|
|
177
|
-
TaskIntent scope or authority. It validates the complete dependency graph before
|
|
181
|
+
TaskIntent for identity, risk, and acceptance IDs; projection fields and public
|
|
182
|
+
summaries never widen TaskIntent scope or authority. It validates the complete dependency graph before
|
|
178
183
|
remote writes, creates the Parent once, creates all Children, attaches every
|
|
179
184
|
Child as a native Sub-issue, creates native `blocked_by` relations, and rereads
|
|
180
185
|
the complete topology. The Child Agent Brief includes a direct Parent Issue link.
|