immune-brain 4.4.1 → 4.6.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 +3 -2
- package/package.json +1 -1
- package/plugins/immune-brain/.claude-plugin/plugin.json +1 -1
- package/plugins/immune-brain/.pi-extension/imm-canary-enroll.ts +0 -1
- package/plugins/immune-brain/.pi-extension/imm-canary-work.ts +47 -2
- package/plugins/immune-brain/.pi-extension/imm-unattended-batch.ts +23 -18
- package/plugins/immune-brain/.pi-extension/runtime-stub.ts +0 -3
- package/plugins/immune-brain/dist/BASELINE.md +18 -1
- package/plugins/immune-brain/dist/claude/mcp-server.mjs +1401 -1101
- package/plugins/immune-brain/dist/docs/reference/subagent-dispatch-protocol.md +30 -0
- package/plugins/immune-brain/dist/imm-brainstorm.md +21 -0
- package/plugins/immune-brain/dist/imm-loop.md +52 -6
- package/plugins/immune-brain/dist/imm-planner.md +102 -13
- package/plugins/immune-brain/dist/role-prompts/code-review.md +19 -0
- package/plugins/immune-brain/dist/role-prompts/qa.md +13 -1
- package/plugins/immune-brain/runtime/assurance/coordinator.ts +55 -2
- package/plugins/immune-brain/runtime/assurance/review_evidence.ts +4 -0
- package/plugins/immune-brain/runtime/claude/interaction.ts +1 -2
- package/plugins/immune-brain/runtime/claude/kernel_ports.ts +73 -27
- package/plugins/immune-brain/runtime/claude/mcp_server.ts +3 -4
- package/plugins/immune-brain/runtime/claude/review_host.ts +15 -0
- package/plugins/immune-brain/runtime/github_issue_tracker.ts +956 -634
- package/plugins/immune-brain/runtime/kernel/application.ts +2 -5
- package/plugins/immune-brain/runtime/kernel/authority_port.ts +5 -14
- package/plugins/immune-brain/runtime/kernel/batch_authority.ts +30 -49
- package/plugins/immune-brain/runtime/kernel/canary_application.ts +1 -2
- package/plugins/immune-brain/runtime/kernel/canary_eligibility.ts +0 -1
- package/plugins/immune-brain/runtime/kernel/capability_registry.ts +7 -9
- package/plugins/immune-brain/runtime/kernel/enrollment.ts +21 -12
- package/plugins/immune-brain/runtime/kernel/enrollment_authority.ts +5 -11
- package/plugins/immune-brain/runtime/kernel/storage.ts +8 -3
- package/plugins/immune-brain/runtime/kernel/types.ts +2 -1
- package/plugins/immune-brain/runtime/kernel/validation.ts +3 -1
- package/plugins/immune-brain/runtime/plugin_version.ts +1 -1
- package/plugins/immune-brain/runtime/prompts/code-review.md +19 -0
- package/plugins/immune-brain/runtime/prompts/qa.md +13 -1
- package/plugins/immune-brain/runtime/unattended/batch_git.ts +9 -1
- package/plugins/immune-brain/runtime/unattended/batch_plan.ts +3 -9
- package/plugins/immune-brain/runtime/unattended/batch_preflight.ts +152 -46
- package/plugins/immune-brain/runtime/unattended/batch_reasons.ts +0 -6
- package/plugins/immune-brain/runtime/unattended/batch_reconfirmation.ts +193 -0
- package/plugins/immune-brain/runtime/unattended/batch_runner.ts +112 -122
- package/plugins/immune-brain/runtime/unattended/batch_state.ts +57 -16
- package/plugins/immune-brain/runtime/unattended/types.ts +0 -2
- package/plugins/immune-brain/runtime/v4_runtime.ts +54 -0
- package/plugins/immune-brain/runtime/workspace_scope.ts +75 -12
- package/plugins/immune-brain/skills/BASELINE.md +18 -1
- package/plugins/immune-brain/skills/imm-brainstorm/SKILL.md +5 -0
- package/plugins/immune-brain/skills/imm-loop/SKILL.md +4 -2
- package/plugins/immune-brain/skills/imm-planner/SKILL.md +10 -1
- package/plugins/immune-brain/runtime/unattended/confirmation_deadline.ts +0 -33
|
@@ -176,6 +176,15 @@ async function runKernelCli(args: string[], root: string): Promise<{
|
|
|
176
176
|
// status --json, and the explicit audit command. All other kernel
|
|
177
177
|
// subcommands (readiness, journal, migrate) are retired.
|
|
178
178
|
const sub = args[0] ?? "";
|
|
179
|
+
// The canonical author shape is discoverable before any error probing: a
|
|
180
|
+
// caller who does not know the destination/stdin contract reads it here
|
|
181
|
+
// instead of discovering it through a failed call.
|
|
182
|
+
if (sub === "intent" && (args[1] === "--help" || args[1] === "help" || args[1] === undefined))
|
|
183
|
+
return {
|
|
184
|
+
stdout: `${intentAuthorHelp()}\n`,
|
|
185
|
+
stderr: "",
|
|
186
|
+
returncode: 0,
|
|
187
|
+
};
|
|
179
188
|
// The explicit claimless storage-layout migration is reachable here: a
|
|
180
189
|
// worktree still on the retired file store has no other entry point, and
|
|
181
190
|
// every mutating subcommand stays fail-closed behind it.
|
|
@@ -224,6 +233,51 @@ async function runCli(command: string, args: string[], root: string): Promise<{
|
|
|
224
233
|
};
|
|
225
234
|
}
|
|
226
235
|
|
|
236
|
+
/**
|
|
237
|
+
* The canonical TaskIntent author contract, printed by `imm-kernel intent` and
|
|
238
|
+
* `imm-kernel intent --help`. It states destination, stdin, and the strict v2
|
|
239
|
+
* descriptor shape before a caller has to discover them by failing a call.
|
|
240
|
+
*/
|
|
241
|
+
export function intentAuthorHelp(): string {
|
|
242
|
+
return [
|
|
243
|
+
"imm-kernel intent author <destination> --stdin --json",
|
|
244
|
+
" destination: docs/plans/<task-id>.intent.json (created exclusively; never overwritten)",
|
|
245
|
+
" stdin: the complete candidate TaskIntent JSON on file descriptor 0",
|
|
246
|
+
" stdout: { valid, enrollment_ready, intent_ref, git_base_initialization_required, ... }",
|
|
247
|
+
"",
|
|
248
|
+
"imm-kernel intent validate <path> --json",
|
|
249
|
+
"",
|
|
250
|
+
"Candidate shape (assurance_kernel/task_intent/v1):",
|
|
251
|
+
JSON.stringify(
|
|
252
|
+
{
|
|
253
|
+
contract: "assurance_kernel/task_intent/v1",
|
|
254
|
+
task_id: "<task-id>",
|
|
255
|
+
owner: "user",
|
|
256
|
+
goal: "<one closable outcome>",
|
|
257
|
+
risk: "routine|material|critical",
|
|
258
|
+
revision: 1,
|
|
259
|
+
scope_hint: ["<every file the slice touches>"],
|
|
260
|
+
acceptance: [
|
|
261
|
+
{
|
|
262
|
+
id: "<ID>",
|
|
263
|
+
assertion: "<observable invariant>",
|
|
264
|
+
verification: JSON.stringify({
|
|
265
|
+
contract: "assurance_kernel/verification_descriptor/v2",
|
|
266
|
+
command: { executable: "bun", argv: ["test", "tests/<focused>.test.ts"], cwd: ".", timeout_ms: 180000, max_output_bytes: 196608 },
|
|
267
|
+
environment: { prepare: null, writable_paths: [] },
|
|
268
|
+
}),
|
|
269
|
+
},
|
|
270
|
+
],
|
|
271
|
+
},
|
|
272
|
+
null,
|
|
273
|
+
2,
|
|
274
|
+
),
|
|
275
|
+
"",
|
|
276
|
+
"Unknown fields are rejected; the descriptor parser is strict v2 and never infers a runner.",
|
|
277
|
+
"A v1 descriptor is historical-only and needs an explicit Intent revision before execution.",
|
|
278
|
+
].join("\n");
|
|
279
|
+
}
|
|
280
|
+
|
|
227
281
|
async function main(argv: string[]): Promise<number> {
|
|
228
282
|
const mode = argv[0];
|
|
229
283
|
const root = process.cwd();
|
|
@@ -19,11 +19,13 @@ export interface GitWorkspaceSnapshot {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
function git(root: string, args: string[]): string | null {
|
|
22
|
-
const
|
|
22
|
+
const diff = args[0] === "diff";
|
|
23
|
+
const command = diff ? ["diff", "--exit-code", ...args.slice(1)] : args;
|
|
24
|
+
const result = spawnSync("git", ["--no-optional-locks", "-c", "diff.autoRefreshIndex=false", "-C", root, ...command], {
|
|
23
25
|
encoding: "utf8",
|
|
24
26
|
stdio: ["ignore", "pipe", "pipe"],
|
|
25
27
|
});
|
|
26
|
-
return result.status === 0 ? result.stdout : null;
|
|
28
|
+
return result.status === 0 || (diff && result.status === 1) ? result.stdout : null;
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
function splitNull(value: string): string[] {
|
|
@@ -135,7 +137,9 @@ function fileFingerprint(root: string, relativePath: string): string {
|
|
|
135
137
|
}
|
|
136
138
|
|
|
137
139
|
function dirtyPaths(root: string): string[] | null {
|
|
138
|
-
|
|
140
|
+
// --no-renames, like the task-path derivations: rename detection would list
|
|
141
|
+
// only the destination and hide the source path's deletion.
|
|
142
|
+
const tracked = git(root, ["diff", "--name-only", "--no-renames", "-z", "HEAD", "--"]);
|
|
139
143
|
const untracked = git(root, [
|
|
140
144
|
"ls-files",
|
|
141
145
|
"--others",
|
|
@@ -217,12 +221,14 @@ export function setGitTaskSnapshotTestHook(hook: () => void): () => void {
|
|
|
217
221
|
}
|
|
218
222
|
|
|
219
223
|
function gitBytes(root: string, args: string[]): Buffer {
|
|
220
|
-
const
|
|
224
|
+
const diff = args[0] === "diff";
|
|
225
|
+
const command = diff ? ["diff", "--exit-code", ...args.slice(1)] : args;
|
|
226
|
+
const result = spawnSync("git", ["--no-optional-locks", "-c", "diff.autoRefreshIndex=false", "-C", root, ...command], {
|
|
221
227
|
encoding: null,
|
|
222
228
|
stdio: ["ignore", "pipe", "pipe"],
|
|
223
229
|
maxBuffer: 8 * 1024 * 1024,
|
|
224
230
|
});
|
|
225
|
-
if (result.status !== 0 || !Buffer.isBuffer(result.stdout)) {
|
|
231
|
+
if ((result.status !== 0 && !(diff && result.status === 1)) || !Buffer.isBuffer(result.stdout)) {
|
|
226
232
|
throw new Error(`Git task snapshot command failed: git ${args.join(" ")}`);
|
|
227
233
|
}
|
|
228
234
|
return result.stdout;
|
|
@@ -386,6 +392,44 @@ function taskPathMatchesScope(path: string, scope: string[]): boolean {
|
|
|
386
392
|
return scope.some((scopePath) => pathMatchesScope(path, scopePath));
|
|
387
393
|
}
|
|
388
394
|
|
|
395
|
+
/**
|
|
396
|
+
* A staged in-scope path that still equals the Enrollment baseline predates the
|
|
397
|
+
* task. Dropping it silently let QA attest workspace bytes that the Review
|
|
398
|
+
* revision did not carry, so it is a hard stop instead: reverting and reapplying
|
|
399
|
+
* yields the same bytes, and only a fresh Enrollment from a clean scope recovers.
|
|
400
|
+
* The task's own planning sidecar is exempt, as it is in dirtyScopePaths:
|
|
401
|
+
* Enrollment requires it tracked, so it is routinely staged in the baseline.
|
|
402
|
+
*/
|
|
403
|
+
function assertNoPreEnrollmentScopeChanges(root: string, scopedStagedPaths: readonly string[], taskId?: string): void {
|
|
404
|
+
const baseline = readEnrollmentBaseline(root);
|
|
405
|
+
if (!baseline) return;
|
|
406
|
+
const current = captureGitWorkspaceSnapshot(root);
|
|
407
|
+
if (!current) throw new Error("enrollment baseline cannot be compared because Git is unavailable");
|
|
408
|
+
const withheld = [...new Set(scopedStagedPaths)]
|
|
409
|
+
.filter((path) => !isOwnPlanningSidecar(path, taskId))
|
|
410
|
+
.filter((path) => baseline.dirty_files[path] !== undefined && baseline.dirty_files[path] === current.dirty_files[path])
|
|
411
|
+
.sort(comparePaths);
|
|
412
|
+
if (withheld.length > 0)
|
|
413
|
+
throw new Error(
|
|
414
|
+
`task scope contains staged changes that predate Enrollment and cannot become task work: ${withheld.join(", ")}; stop the task and re-enroll from a clean scope`,
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* In-scope paths that are already staged, modified or untracked relative to
|
|
420
|
+
* HEAD. Enrollment refuses these: the baseline would record them as user dirt
|
|
421
|
+
* and they could never be delivered by the task.
|
|
422
|
+
*/
|
|
423
|
+
export function dirtyScopePaths(projectRoot: string, scopeHint: unknown, taskId?: string): string[] {
|
|
424
|
+
const root = realpathSync(resolve(projectRoot));
|
|
425
|
+
const scope = assertCanonicalTaskScope(scopeHint);
|
|
426
|
+
const paths = dirtyPaths(root);
|
|
427
|
+
if (!paths) throw new Error("cannot inspect the task scope outside a committed Git workspace");
|
|
428
|
+
return paths.filter(
|
|
429
|
+
(path) => !isNonDeliveryPath(path) && !isOwnPlanningSidecar(path, taskId) && taskPathMatchesScope(path, scope),
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
|
|
389
433
|
function taskSnapshotOnce(root: string, scope: string[], taskId?: string): GitTaskSnapshot {
|
|
390
434
|
const repositoryRoot = git(root, ["rev-parse", "--show-toplevel"])?.trim();
|
|
391
435
|
const head = git(root, ["rev-parse", "--verify", "HEAD^{commit}"])?.trim();
|
|
@@ -421,12 +465,10 @@ function taskSnapshotOnce(root: string, scope: string[], taskId?: string): GitTa
|
|
|
421
465
|
if (uncommittedInScope.length > 0)
|
|
422
466
|
throw new Error(`task scope contains unstaged or untracked changes: ${uncommittedInScope.join(", ")}`);
|
|
423
467
|
|
|
424
|
-
const baseline = readEnrollmentBaseline(root);
|
|
425
|
-
const current = baseline ? captureGitWorkspaceSnapshot(root) : null;
|
|
426
468
|
const taskPaths = [...new Set(stagedPaths)]
|
|
427
469
|
.filter((path) => !isNonDeliveryPath(path) && taskPathMatchesScope(path, scope))
|
|
428
|
-
.filter((path) => !(baseline && current && baseline.dirty_files[path] === current.dirty_files[path] && baseline.dirty_files[path] !== undefined))
|
|
429
470
|
.sort(comparePaths);
|
|
471
|
+
assertNoPreEnrollmentScopeChanges(root, taskPaths, taskId);
|
|
430
472
|
const stagedFiles: Record<string, GitTaskIndexEntry> = {};
|
|
431
473
|
for (const path of taskPaths) {
|
|
432
474
|
const current = indexEntry(root, path);
|
|
@@ -555,10 +597,8 @@ function taskRevisionSnapshotOnce(
|
|
|
555
597
|
"untracked task revision paths",
|
|
556
598
|
);
|
|
557
599
|
assertNoEnvelopeEscape(root, stagedPaths, scope, taskId);
|
|
558
|
-
const
|
|
559
|
-
|
|
560
|
-
const scopedStagedPaths = stagedPaths.filter((path) => !isNonDeliveryPath(path) && taskPathMatchesScope(path, scope))
|
|
561
|
-
.filter((path) => !(baseline && current && baseline.dirty_files[path] === current.dirty_files[path] && baseline.dirty_files[path] !== undefined));
|
|
600
|
+
const scopedStagedPaths = stagedPaths.filter((path) => !isNonDeliveryPath(path) && taskPathMatchesScope(path, scope));
|
|
601
|
+
assertNoPreEnrollmentScopeChanges(root, scopedStagedPaths, taskId);
|
|
562
602
|
const scopedUnstagedPaths = unstagedPaths.filter((path) => taskPathMatchesScope(path, scope));
|
|
563
603
|
const scopedUntrackedPaths = untrackedPaths.filter((path) => taskPathMatchesScope(path, scope));
|
|
564
604
|
assertNoCaseFoldCollisions(
|
|
@@ -631,6 +671,29 @@ export function taskRevisionIdentity(
|
|
|
631
671
|
};
|
|
632
672
|
}
|
|
633
673
|
|
|
674
|
+
/** The same v4 identity for an immutable delivered commit, without touching the index. */
|
|
675
|
+
export function taskCommitRevisionIdentity(projectRoot: string, scopeHint: unknown, baseHead: string, commit: string): GitTaskDiffIdentity {
|
|
676
|
+
const requested = resolve(projectRoot);
|
|
677
|
+
if (lstatSync(requested).isSymbolicLink()) throw new Error("task commit root must be a real directory");
|
|
678
|
+
const root = realpathSync(requested), scope = assertCanonicalTaskScope(scopeHint);
|
|
679
|
+
if (!GIT_OBJECT_ID.test(baseHead) || !GIT_OBJECT_ID.test(commit)) throw new Error("invalid task commit identity");
|
|
680
|
+
if (gitRequired(root, ["cat-file", "-t", baseHead], "unreadable task base") !== "commit" ||
|
|
681
|
+
gitRequired(root, ["cat-file", "-t", commit], "unreadable task commit") !== "commit" ||
|
|
682
|
+
git(root, ["merge-base", "--is-ancestor", baseHead, commit]) === null) throw new Error("invalid task commit ancestry");
|
|
683
|
+
const paths = decodeNullPaths(gitBytes(root, ["diff", "--no-renames", "--name-only", "-z", baseHead, commit, "--"]), "task commit paths")
|
|
684
|
+
.filter(path => !isNonDeliveryPath(path) && taskPathMatchesScope(path, scope)).sort(comparePaths);
|
|
685
|
+
assertNoCaseFoldCollisions(paths, "task commit paths");
|
|
686
|
+
const changed_paths: Record<string, GitTaskIndexEntry> = {};
|
|
687
|
+
for (const path of paths) {
|
|
688
|
+
const current = headEntry(root, commit, path), base = headEntry(root, baseHead, path);
|
|
689
|
+
changed_paths[path] = { status: !base ? "added" : !current ? "deleted" : "modified",
|
|
690
|
+
mode: current?.mode ?? null, oid: current?.oid ?? null, base_mode: base?.mode ?? null, base_oid: base?.oid ?? null };
|
|
691
|
+
}
|
|
692
|
+
const snapshot: GitTaskRevisionSnapshot = { kind: "git-task-revision-v1", repository_root: root, base_head: baseHead,
|
|
693
|
+
base_tree: gitRequired(root, ["rev-parse", `${baseHead}^{tree}`], "unreadable task base tree"), scope, changed_paths };
|
|
694
|
+
return { diff_hash: hashTaskSnapshot(snapshot), changed_paths: paths };
|
|
695
|
+
}
|
|
696
|
+
|
|
634
697
|
/** The single v4 freshness identity shared by QA, Review, authorization, and completion. */
|
|
635
698
|
export function taskRevisionDiffHash(
|
|
636
699
|
projectRoot: string,
|
|
@@ -9,6 +9,20 @@
|
|
|
9
9
|
- Stage only explicit task-owned paths. This staging authority does not grant
|
|
10
10
|
commit, push, broad staging, or authority over pre-existing user changes.
|
|
11
11
|
Never use `git add .` or `git add -A` in a dirty worktree.
|
|
12
|
+
- Grant effects separately: staging, commit, push, publication, and outbound
|
|
13
|
+
model/data calls are distinct operations. Recovering a stage-only instruction
|
|
14
|
+
restores staging and nothing more; commit, push, and publication each keep
|
|
15
|
+
their own existing grant. An explicit approval already covering the same
|
|
16
|
+
operation, target, and impact — including a valid batch capability — stays
|
|
17
|
+
usable without another gate.
|
|
18
|
+
- Before a new outbound effect, check the project's existing research channel and
|
|
19
|
+
data scope. A public article, a mock, a documentation example, or a local
|
|
20
|
+
read-only database gives no authority to call an arbitrary provider or send new
|
|
21
|
+
data; authorize only a genuinely new channel/data/effect delta. Distinguish
|
|
22
|
+
documentation, mock, real-channel, and model-quality evidence when reporting.
|
|
23
|
+
- These text rules are instruction contracts, not a hard bash sandbox. They
|
|
24
|
+
guide behavior and are checked as contracts; they do not technically prevent an
|
|
25
|
+
arbitrary shell command, so authority gates and review remain the real boundary.
|
|
12
26
|
- Do not create, switch, or delete Git worktrees; operate only in the Host launch directory.
|
|
13
27
|
- Record reproducible evidence before reporting closure.
|
|
14
28
|
- Required verification must pass before reporting completion; disclosing a gap is not a substitute. Autonomously diagnose, repair, and rerun failing conventional local checks within the authorized scope; never delete, skip, or weaken a valid check to manufacture a pass. If a required check remains failing or cannot run, report the work as incomplete with the concrete blocker.
|
|
@@ -81,7 +95,6 @@ non-Kernel operations, an explicit user approval already covering the same
|
|
|
81
95
|
operation, target, and impact is sufficient; ask again only for a material delta.
|
|
82
96
|
A generic continuation or configured preference is not blanket authorization.
|
|
83
97
|
Mandatory native gates and hash-bound manifest approvals still apply.
|
|
84
|
-
|
|
85
98
|
Privileged effects include:
|
|
86
99
|
|
|
87
100
|
- publish, release, deployment, or remote-system mutation;
|
|
@@ -91,6 +104,10 @@ Privileged effects include:
|
|
|
91
104
|
override; and
|
|
92
105
|
- external writes whose target or impact cannot be safely reversed locally.
|
|
93
106
|
|
|
107
|
+
An existing grant for one of these never inherits another: a stage-only
|
|
108
|
+
instruction stays stage-only, a commit is not push or publication authority,
|
|
109
|
+
and a valid batch capability commits only its own scope-bounded child.
|
|
110
|
+
|
|
94
111
|
Routine Managed enrollment uses one current-Host native confirmation bound to the TaskIntent content hash after Planner validation. Explicit Plan-only requests stop with candidate artifacts and do not invoke Enrollment; execution-bearing requests open the native gate directly without chat pre-confirmation. Enrollment validates intent, Git ownership, scope, workspace claim, and final authority preconditions without executing acceptance descriptors; deterministic QA executes them after implementation. The routine task proceeds from that single confirmation through enrollment, execution, and QA without a second human stop. Do not request confirmation for local in-scope edits, local verification, ordinary Direct rework, scoped diff review, or completion reporting. Managed evidence, QA, Review, and completion authority remain governed by their Managed contracts; R2 does not weaken them. Managed native-authority failures fail closed with one stable reason and exactly one same-Host recovery action; never offer a Pi, Direct Path, cross-Host/worktree, unmanaged, or automatic-retry fallback.
|
|
95
112
|
|
|
96
113
|
For an unborn symbolic Git HEAD, the same Enrollment confirmation discloses an empty root commit. After approval, the Host creates that commit without staging project content or changing the index/worktree, then revalidates ownership and rehearses Enrollment against the new base. Git identity uses existing environment/config values with an explicit automation fallback, never config writes. Decline or pre-initialization cancellation leaves HEAD unborn; if later Enrollment fails or is cancelled, report the remaining commit. Invalid Git refs are errors, not initialization candidates.
|
|
@@ -28,3 +28,8 @@ When framing discusses later execution, describe Enrollment only as the
|
|
|
28
28
|
current Host's native gate. A failed Managed authority interaction stays
|
|
29
29
|
fail-closed: report one same-Host recovery action, never another Host,
|
|
30
30
|
worktree, or unmanaged implementation.
|
|
31
|
+
|
|
32
|
+
Every sourced branch closes as resolved, explicitly excluded, explicitly
|
|
33
|
+
deferred, or dependent-blocked with its blocking node named, including for a
|
|
34
|
+
clear zero-question request; a confirmed outcome is never conserved as a
|
|
35
|
+
smaller deliverable.
|
|
@@ -11,7 +11,9 @@ ordinary host input never resumes a Managed owner implicitly.
|
|
|
11
11
|
|
|
12
12
|
Mandatory constraints before any action: verify the active backend claim,
|
|
13
13
|
TaskIntent, and TaskRecord via `imm_kernel_canary` `status` first; invalid or
|
|
14
|
-
contradictory projections fail closed.
|
|
14
|
+
contradictory projections fail closed. Resume only from those authority facts:
|
|
15
|
+
a compaction heading, HANDOFF summary, or Issue state is prose, never authority.
|
|
16
|
+
All Managed authority gates use the
|
|
15
17
|
current Host's native interaction. A failed gate stays fail-closed and
|
|
16
18
|
reports one same-Host recovery action; never suggest another Host, worktree,
|
|
17
19
|
or unmanaged implementation as a fallback.
|
|
@@ -24,5 +26,5 @@ or all references as an entry prerequisite.
|
|
|
24
26
|
- common: [Shared Guards](../../dist/BASELINE.md#shared-guards), [Workflow Activation](../../dist/BASELINE.md#workflow-activation), [Host Confirmation Boundary](../../dist/BASELINE.md#host-confirmation-boundary), [Kernel Canary Routing and Authority](../../dist/imm-loop.md#kernel-canary-routing-and-authority)
|
|
25
27
|
- unattended batch run, or any question about whether `imm-loop` starts one: [Unattended Batch Opt-In](../../dist/imm-loop.md#unattended-batch-opt-in)
|
|
26
28
|
- steady execution: [Verification and Local Recovery](../../dist/BASELINE.md#verification-and-local-recovery), [Execution Loop](../../dist/imm-loop.md#execution-loop), [Observable Output](../../dist/imm-loop.md#observable-output)
|
|
27
|
-
- rework, scope expansion, breaking revision, user decision, stop, interruption or unknown state before any action: [Decisions and Recovery](../../dist/imm-loop.md#decisions-and-recovery), [Failure Output](../../dist/imm-loop.md#failure-output); re-read `status`, then the pending obligation
|
|
29
|
+
- rework, scope expansion, breaking revision, user decision, stop, interruption or unknown state before any action: [Decisions and Recovery](../../dist/imm-loop.md#decisions-and-recovery), [Failure Output](../../dist/imm-loop.md#failure-output); re-read `status`, then the pending obligation. Rework submits the verdict before editing, and an uncertain interruption resumes from exact task/run authority facts rather than a summary.
|
|
28
30
|
- review or post-settlement learning: [Review and Learning](../../dist/imm-loop.md#review-and-learning)
|
|
@@ -11,7 +11,10 @@ host-native.
|
|
|
11
11
|
|
|
12
12
|
Mandatory constraints before any action: Planner writes a TaskIntent, and a Spec
|
|
13
13
|
only for complex work; it never implements, overwrites an enrolled TaskIntent, or
|
|
14
|
-
grants execution authority — only the native Enrollment gate can.
|
|
14
|
+
grants execution authority — only the native Enrollment gate can. Before
|
|
15
|
+
handoff it maps every upstream item and required phase exactly once and reports
|
|
16
|
+
each Slice result plus the remaining gap; a plan that conserves a smaller
|
|
17
|
+
deliverable than the confirmed outcome fails that check instead of passing.
|
|
15
18
|
|
|
16
19
|
Section routes - load a section's instructions only when its branch applies.
|
|
17
20
|
Read each linked heading body up to the next heading; nested sections and
|
|
@@ -40,3 +43,9 @@ that include execution invoke the current Host's native Enrollment gate
|
|
|
40
43
|
directly, without chat pre-confirmation. Native-gate failure stays fail-closed
|
|
41
44
|
in that Host: report its reason and one retry action only; never suggest
|
|
42
45
|
another Host, worktree, or unmanaged implementation as a fallback.
|
|
46
|
+
|
|
47
|
+
Before that handoff, confirm complete mapping of every upstream item and
|
|
48
|
+
required phase with each Slice's result and the remaining gap; reject a plan
|
|
49
|
+
that conserved only part of the confirmed outcome, and ask again only for a
|
|
50
|
+
newly evidenced material delta rather than reconfirming an unchanged clear
|
|
51
|
+
request or an already-confirmed decision.
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
// A native confirmation is bounded on both Hosts. Claude bounded its MCP
|
|
2
|
-
// elicitation with IMMUNE_BRAIN_BATCH_TIMEOUT_MS and Pi waited indefinitely; the
|
|
3
|
-
// bound, its default, and the "this deadline expired rather than the user
|
|
4
|
-
// cancelling" distinction are shared here. Each Host still owns its transport:
|
|
5
|
-
// this module only produces the signal to hand it and reports expiry.
|
|
6
|
-
|
|
7
|
-
export const CONFIRMATION_TIMEOUT_DEFAULT_MS = 60_000;
|
|
8
|
-
export const CONFIRMATION_TIMEOUT_ENV = "IMMUNE_BRAIN_BATCH_TIMEOUT_MS";
|
|
9
|
-
|
|
10
|
-
export interface ConfirmationDeadline {
|
|
11
|
-
/** The caller's signal, combined with this deadline. */
|
|
12
|
-
signal: AbortSignal;
|
|
13
|
-
/** True when this deadline fired and the caller's own signal did not. */
|
|
14
|
-
timedOut(): boolean;
|
|
15
|
-
clear(): void;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function startConfirmationDeadline(input: {
|
|
19
|
-
env?: Record<string, string | undefined>;
|
|
20
|
-
signal?: AbortSignal;
|
|
21
|
-
}): ConfirmationDeadline {
|
|
22
|
-
const configured = Number(input.env?.[CONFIRMATION_TIMEOUT_ENV]);
|
|
23
|
-
const timeoutMs = Number.isFinite(configured) && configured > 0 ? configured : CONFIRMATION_TIMEOUT_DEFAULT_MS;
|
|
24
|
-
const controller = new AbortController();
|
|
25
|
-
const timer = setTimeout(() => {
|
|
26
|
-
controller.abort(new Error("native confirmation timed out waiting for user interaction"));
|
|
27
|
-
}, timeoutMs);
|
|
28
|
-
return {
|
|
29
|
-
signal: input.signal ? AbortSignal.any([input.signal, controller.signal]) : controller.signal,
|
|
30
|
-
timedOut: () => controller.signal.aborted && !input.signal?.aborted,
|
|
31
|
-
clear: () => clearTimeout(timer),
|
|
32
|
-
};
|
|
33
|
-
}
|