immune-brain 4.4.0 → 4.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "immune-brain",
3
- "version": "4.4.0",
3
+ "version": "4.4.1",
4
4
  "description": "Immune-Brain agent skill system",
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "immune-brain",
3
- "version": "4.4.0",
3
+ "version": "4.4.1",
4
4
  "description": "Immune-Brain Claude Code Host: native Enrollment, QA, Review, and Kernel settlement.",
5
5
  "author": {
6
6
  "name": "Immune-Brain Team"
@@ -64,7 +64,7 @@ import {
64
64
  type UserAttentionEventV1,
65
65
  type UserAttentionReason,
66
66
  } from "./pi-canary-interaction";
67
- import { isToolFailureState, throwToolFailure, type ToolFailureV1 } from "./pi-canary-tool-failure";
67
+ import { isToolFailureState, throwToolFailure, type AssuranceFailureDetails, type ToolFailureV1 } from "./pi-canary-tool-failure";
68
68
  import { resolveUxLanguage, uxText } from "./ux-language";
69
69
 
70
70
  /** Host-native UI language; see ux-language.ts. Resolved once per process. */
@@ -1737,6 +1737,11 @@ async function enrichAssuranceResult(
1737
1737
  taskId: string,
1738
1738
  result: Record<string, unknown>,
1739
1739
  ): Promise<Record<string, unknown>> {
1740
+ if (result.recovery_error) {
1741
+ // Do not replay a failed observation for Host decoration or tracker hygiene.
1742
+ const taskState = { error: typeof result.reason === "string" ? result.reason : "authority projection unavailable" };
1743
+ return { ...result, task_state: taskState, next_action: nextActionForAssuranceResult(result, taskState) };
1744
+ }
1740
1745
  const projection = await projectAssuranceState(ctx.cwd, taskId);
1741
1746
  const taskState: AssuranceTaskState = projection.error
1742
1747
  ? { error: projection.error }
@@ -1761,11 +1766,13 @@ async function enrichAssuranceResult(
1761
1766
  }
1762
1767
 
1763
1768
  function nextActionForAssuranceResult(result: Record<string, unknown>, taskState: AssuranceTaskState): string {
1769
+ const derived = result.recovery as { next_action: string } | undefined;
1770
+ if (derived) return derived.next_action;
1764
1771
  const recovery = recoveryActionForAssuranceFailure(
1765
1772
  "error" in taskState ? taskState.error : result.reason,
1766
1773
  );
1767
1774
  if (recovery) return recovery;
1768
- if ("error" in taskState) return "inspect authority state";
1775
+ if (result.recovery_error || "error" in taskState) return "inspect authority state";
1769
1776
  if (result.state === "review_preparation_failed") return "repair Review preparation, then retry advance_assurance; QA is already committed";
1770
1777
  if (result.code === "verdict_invalid") return "fix the verdict payload and resubmit submit_review; the Review reservation remains active; do not re-dispatch the reviewer";
1771
1778
  if (taskState.lifecycle === "done" || taskState.lifecycle === "stopped") return "none";
@@ -1872,6 +1879,7 @@ function failCanaryTool(
1872
1879
  code: string,
1873
1880
  message: string,
1874
1881
  nextAction: string,
1882
+ details: AssuranceFailureDetails = {},
1875
1883
  ): never {
1876
1884
  return throwToolFailure({
1877
1885
  tool: "imm_kernel_canary",
@@ -1881,6 +1889,7 @@ function failCanaryTool(
1881
1889
  code,
1882
1890
  message,
1883
1891
  next_action: nextAction,
1892
+ ...details,
1884
1893
  });
1885
1894
  }
1886
1895
 
@@ -1890,6 +1899,7 @@ function throwIfCanaryToolFailure(
1890
1899
  result: Record<string, unknown>,
1891
1900
  ): void {
1892
1901
  if (!isToolFailureState(result.state)) return;
1902
+ const details = result as AssuranceFailureDetails;
1893
1903
  failCanaryTool(
1894
1904
  taskId,
1895
1905
  operation,
@@ -1903,6 +1913,13 @@ function throwIfCanaryToolFailure(
1903
1913
  typeof result.next_action === "string"
1904
1914
  ? result.next_action
1905
1915
  : "inspect authority state",
1916
+ // Preserve only the coordinator's bounded observation metadata.
1917
+ {
1918
+ ...(details.diagnostics ? { diagnostics: details.diagnostics } : {}),
1919
+ ...(details.environment_failure ? { environment_failure: true } : {}),
1920
+ ...(details.recovery ? { recovery: details.recovery } : {}),
1921
+ ...(details.recovery_error ? { recovery_error: details.recovery_error } : {}),
1922
+ },
1906
1923
  );
1907
1924
  }
1908
1925
 
@@ -5,6 +5,7 @@ import {
5
5
  createBatchAuthorityRegistry,
6
6
  deriveChildEnrollment,
7
7
  startBatch,
8
+ batchQaFailureFacts,
8
9
  readTaskRecord,
9
10
  advancePiTask,
10
11
  projectAssuranceForTask,
@@ -114,7 +115,7 @@ export async function executePiUnattendedBatch(
114
115
  // working-tree cleanliness against the authorized scope, recovery children,
115
116
  // plan digest, and base HEAD are one shared projection, so neither Host
116
117
  // re-implements a batch decision.
117
- const now = new Date().toISOString();
118
+ let now = new Date().toISOString();
118
119
  const preflight = await projectBatchPreflight({
119
120
  root,
120
121
  initiative_slug: initiativeSlug,
@@ -203,6 +204,9 @@ export async function executePiUnattendedBatch(
203
204
  // review-2: verify cancellation signal right before authority issuance and startBatch
204
205
  if (signal?.aborted) return batchReason("cancelled_before_execution");
205
206
 
207
+ now = new Date().toISOString();
208
+ if (Date.parse(expiresAt) <= Date.parse(now))
209
+ return batchReason("confirmation_failed", "confirmed batch deadline expired before authorization issuance");
206
210
  const capability = batchRegistry.issue(binding, recoveryChildren as any, now);
207
211
 
208
212
  let activeReviewDispatch: { operation_id: string; agent_params: Record<string, unknown> } | null = null;
@@ -251,7 +255,11 @@ export async function executePiUnattendedBatch(
251
255
  return res;
252
256
  },
253
257
  projectTask: async (taskRoot, taskId) => {
254
- return projectAssuranceForTask(taskRoot, taskId);
258
+ const fresh = await projectAssuranceForTask(taskRoot, taskId);
259
+ if (!fresh.claim || fresh.projection.lifecycle !== "active" || fresh.error !== null) return fresh;
260
+ const { record, revision } = await readTaskRecord(taskRoot, taskId);
261
+ if (!record || revision !== fresh.projection.record_revision) throw new Error(`Kernel recovery projection changed for ${taskId}`);
262
+ return { ...fresh, ...batchQaFailureFacts(record) };
255
263
  },
256
264
  ownsTaskClaim: (taskId) => {
257
265
  if (isResuming && taskId === readActiveClaimTaskId(root)) {
@@ -1,4 +1,9 @@
1
- export interface ToolFailureV1 {
1
+ import type { AssuranceAdvanceResult } from "../runtime/assurance/coordinator";
2
+
3
+ export type AssuranceFailureDetails = Pick<AssuranceAdvanceResult,
4
+ "diagnostics" | "environment_failure" | "recovery" | "recovery_error">;
5
+
6
+ export interface ToolFailureV1 extends AssuranceFailureDetails {
2
7
  contract: "immune_brain/tool_failure/v1";
3
8
  tool: "imm_canary_enrollment" | "imm_kernel_canary";
4
9
  task_id: string;
@@ -171,10 +171,12 @@ export interface TaskRecordRead {
171
171
  manifest_digest: string;
172
172
  };
173
173
  }>;
174
+ history: import("../runtime/kernel/types").TaskRecord["history"];
174
175
  findings: Array<{
175
176
  id: string;
176
177
  kind: string;
177
178
  status: string;
179
+ acceptance_id?: string | null;
178
180
  summary?: string;
179
181
  anchor?: string | null;
180
182
  evidence?: unknown;
@@ -451,6 +453,7 @@ export type {
451
453
  BatchAuthorizationBinding,
452
454
  ValidatedBatchAuthorization,
453
455
  } from "../runtime/kernel/batch_authority";
456
+ export { batchQaFailureFacts } from "../runtime/unattended/batch_runner";
454
457
  export type {
455
458
  BatchRunnerKernelPort,
456
459
  StartBatchInput,
@@ -553,12 +556,14 @@ export async function getSharedPiProgression(): Promise<any> {
553
556
  export async function advancePiTask(root: string, taskId: string): Promise<any> {
554
557
  const progression = await getSharedPiProgression();
555
558
  const result = await progression.advance(taskId, { cwd: root });
556
- if (result.state === "completed") return { state: "completed" };
557
- if (result.state === "stopped") return { state: "stopped" };
558
- if (result.state === "rework") return { state: "rework", operation: result.operation, summary: result.summary };
559
- if (result.state === "review_ready") return { state: "review_ready", operation_id: result.operation_id, agent_params: result.agent_params };
560
- if (result.state === "blocked") return { state: "blocked", reason: result.reason };
561
- return { state: "failed", reason: (result as { reason?: string }).reason ?? "advance failed" };
559
+ const facts = { diagnostics: result.diagnostics, environment_failure: result.environment_failure, recovery: result.recovery };
560
+ if (result.state === "completed") return { state: "completed", ...facts };
561
+ if (result.state === "stopped") return { state: "stopped", ...facts };
562
+ if (result.state === "rework") return { state: "rework", operation: result.operation, summary: result.summary, ...facts };
563
+ if (result.state === "review_ready") return { state: "review_ready", operation_id: result.operation_id, agent_params: result.agent_params, ...facts };
564
+ if (result.state === "review_preparation_failed") return { state: result.state, operation: result.operation, operation_id: result.operation_id, reason: result.reason, ...facts };
565
+ if (result.state === "blocked") return { state: "blocked", reason: result.reason, ...facts };
566
+ return { state: "failed", reason: (result as { reason?: string }).reason ?? "advance failed", ...facts };
562
567
  }
563
568
 
564
569
  export async function projectAssuranceForTask(root: string, taskId: string): Promise<any> {