immune-brain 5.1.0 → 5.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/plugins/immune-brain/.claude-plugin/plugin.json +1 -1
- package/plugins/immune-brain/.pi-extension/imm-canary-work.ts +17 -5
- package/plugins/immune-brain/.pi-extension/imm-unattended-batch.ts +165 -0
- package/plugins/immune-brain/.pi-extension/pi-canary-assurance-progression.ts +66 -3
- package/plugins/immune-brain/agents/immune-brain-reviewer.md +11 -1
- package/plugins/immune-brain/dist/claude/mcp-server.mjs +542 -79
- package/plugins/immune-brain/dist/docs/reference/subagent-dispatch-protocol.md +8 -6
- package/plugins/immune-brain/dist/imm-planner.md +4 -1
- package/plugins/immune-brain/dist/role-prompts/code-review.md +10 -0
- package/plugins/immune-brain/runtime/assurance/coordinator.ts +22 -4
- package/plugins/immune-brain/runtime/assurance/review_mediation.ts +107 -0
- package/plugins/immune-brain/runtime/assurance/verdict_authority.ts +3 -0
- package/plugins/immune-brain/runtime/claude/interaction.ts +10 -0
- package/plugins/immune-brain/runtime/claude/kernel_ports.ts +89 -54
- package/plugins/immune-brain/runtime/claude/mcp_server.ts +76 -25
- package/plugins/immune-brain/runtime/kernel/assurance_projection.ts +4 -1
- package/plugins/immune-brain/runtime/kernel/canary_application.ts +9 -2
- package/plugins/immune-brain/runtime/kernel/completion.ts +24 -1
- package/plugins/immune-brain/runtime/kernel/reducer.ts +15 -2
- package/plugins/immune-brain/runtime/kernel/spec_binding.ts +6 -0
- package/plugins/immune-brain/runtime/kernel/storage.ts +20 -4
- package/plugins/immune-brain/runtime/kernel/storage_paths.ts +33 -3
- package/plugins/immune-brain/runtime/kernel/types.ts +7 -0
- package/plugins/immune-brain/runtime/kernel/validation.ts +13 -2
- package/plugins/immune-brain/runtime/plugin_version.ts +1 -1
- package/plugins/immune-brain/runtime/prompts/code-review.md +10 -0
- package/plugins/immune-brain/runtime/role_prompt_bridge.ts +1 -1
- package/plugins/immune-brain/runtime/unattended/batch_disposition.ts +222 -0
- package/plugins/immune-brain/runtime/unattended/batch_lanes.ts +51 -14
- package/plugins/immune-brain/runtime/unattended/batch_preflight.ts +8 -12
- package/plugins/immune-brain/runtime/unattended/batch_reasons.ts +13 -1
- package/plugins/immune-brain/runtime/unattended/batch_runner.ts +2 -0
- package/plugins/immune-brain/runtime/unattended/batch_state.ts +44 -6
package/package.json
CHANGED
|
@@ -371,8 +371,12 @@ export default function (
|
|
|
371
371
|
if (ctx.mode !== "tui") return;
|
|
372
372
|
await refreshTaskRail(ctx);
|
|
373
373
|
});
|
|
374
|
-
pi.on("tool_call", (event: { toolName?: string; input?: unknown }, ctx?: ExtensionContext) => {
|
|
374
|
+
pi.on("tool_call", (event: { toolName?: string; input?: unknown; toolCallId?: string }, ctx?: ExtensionContext) => {
|
|
375
375
|
if (ctx) railContext = ctx;
|
|
376
|
+
if (event.toolName === "Agent") {
|
|
377
|
+
// ADR 0017: correlate a reserved reviewer dispatch to its reservation.
|
|
378
|
+
progression.piReviewHost.observeReviewDispatch(event.input as { prompt?: unknown; subagent_type?: unknown } | undefined, event.toolCallId);
|
|
379
|
+
}
|
|
376
380
|
if (event.toolName === "imm_canary_enrollment" && ctx) {
|
|
377
381
|
const input = event.input as { task_id?: string } | undefined;
|
|
378
382
|
if (input?.task_id) presentTaskRail(ctx, {
|
|
@@ -385,7 +389,14 @@ export default function (
|
|
|
385
389
|
});
|
|
386
390
|
pi.on("tool_result", (event: unknown, ctx?: ExtensionContext) => {
|
|
387
391
|
if (ctx) railContext = ctx;
|
|
388
|
-
const result = event as { toolName?: string; details?: Record<string, unknown> };
|
|
392
|
+
const result = event as { toolName?: string; details?: Record<string, unknown>; toolCallId?: string; content?: Array<{ type?: string; text?: string }> };
|
|
393
|
+
if (result.toolName === "Agent" && result.toolCallId) {
|
|
394
|
+
// ADR 0017: record the reviewer's own result bytes for receipt-bound submission.
|
|
395
|
+
const text = Array.isArray(result.content)
|
|
396
|
+
? result.content.filter((part) => typeof part?.text === "string").map((part) => part.text as string).join("\n")
|
|
397
|
+
: "";
|
|
398
|
+
progression.piReviewHost.observeReviewResult(result.toolCallId, text);
|
|
399
|
+
}
|
|
389
400
|
if (result.toolName === "imm_canary_enrollment" && ctx) {
|
|
390
401
|
const taskId = typeof result.details?.task_id === "string" ? result.details.task_id : undefined;
|
|
391
402
|
if (taskId) presentTaskRailResult(ctx, taskId, result.details);
|
|
@@ -406,7 +417,7 @@ export default function (
|
|
|
406
417
|
promptGuidelines: [
|
|
407
418
|
"Only the exact enrolled canary task is routable; verify the active backend claim first via status.",
|
|
408
419
|
"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.",
|
|
409
|
-
"When advance_assurance returns review_ready, invoke the foreground Agent from agent_params once, then
|
|
420
|
+
"When advance_assurance returns review_ready, invoke the foreground Agent from agent_params once, then call submit_review: omit the verdict to apply the observed reviewer result, or relay its structured verdict exactly.",
|
|
410
421
|
"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.",
|
|
411
422
|
"For a proven stale authority claim, call repair_authority_state directly; the Kernel revalidates and removes only the redundant claim without user interaction.",
|
|
412
423
|
"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.",
|
|
@@ -416,7 +427,7 @@ export default function (
|
|
|
416
427
|
action: Type.Union([
|
|
417
428
|
Type.Object({ op: Type.Literal("status") }),
|
|
418
429
|
Type.Object({ op: Type.Literal("advance_assurance") }),
|
|
419
|
-
Type.Object({ op: Type.Literal("submit_review"), verdict: Type.Unknown() }),
|
|
430
|
+
Type.Object({ op: Type.Literal("submit_review"), verdict: Type.Optional(Type.Unknown()) }),
|
|
420
431
|
Type.Object({ op: Type.Literal("request_authorization") }),
|
|
421
432
|
Type.Object({ op: Type.Literal("request_stop") }),
|
|
422
433
|
Type.Object({ op: Type.Literal("repair_authority_state") }),
|
|
@@ -549,7 +560,7 @@ export default function (
|
|
|
549
560
|
presentTaskRailResult(ctx, taskId, update.details as Record<string, unknown> | undefined);
|
|
550
561
|
})
|
|
551
562
|
: action.op === "submit_review"
|
|
552
|
-
? await progression.
|
|
563
|
+
? await progression.submitMediated(taskId, ctx, action.verdict)
|
|
553
564
|
: action.op === "approve_breaking_intent_revision"
|
|
554
565
|
? await authorizeExactOperation(
|
|
555
566
|
taskId,
|
|
@@ -1509,6 +1520,7 @@ async function enrichAssuranceResult(
|
|
|
1509
1520
|
function nextActionForAssuranceResult(result: Record<string, unknown>, taskState: AssuranceTaskState): string {
|
|
1510
1521
|
const derived = result.recovery as { next_action: string } | undefined;
|
|
1511
1522
|
if (derived) return derived.next_action;
|
|
1523
|
+
if (typeof result.recovery_action === "string") return result.recovery_action;
|
|
1512
1524
|
const recovery = recoveryActionForAssuranceFailure(
|
|
1513
1525
|
"error" in taskState ? taskState.error : result.reason,
|
|
1514
1526
|
);
|
|
@@ -26,6 +26,9 @@ import {
|
|
|
26
26
|
authorizeBatch,
|
|
27
27
|
projectBatchPreflight,
|
|
28
28
|
} from "../runtime/unattended/batch_preflight";
|
|
29
|
+
import { retireStaleBatch, type StaleBatchFacts } from "../runtime/unattended/batch_disposition";
|
|
30
|
+
import type { BatchLaneRunReport } from "../runtime/unattended/batch_state";
|
|
31
|
+
|
|
29
32
|
import {
|
|
30
33
|
presentTaskRail,
|
|
31
34
|
renderStructuredCall,
|
|
@@ -305,6 +308,69 @@ export async function executePiUnattendedBatch(
|
|
|
305
308
|
};
|
|
306
309
|
}
|
|
307
310
|
|
|
311
|
+
export type PiRetireStaleBatchResult =
|
|
312
|
+
| { state: "retired"; batch_id: string; report: BatchRunReport | BatchLaneRunReport }
|
|
313
|
+
| { state: "rejected" | "cancelled" | "blocked"; reason: string; recovery_action: string };
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* The gate text for one batch disposition. Every fact rendered is the record's
|
|
317
|
+
* own evidence: what it delivered, what it never executed, and what retiring
|
|
318
|
+
* does and does not claim. Nothing here is a readiness certificate.
|
|
319
|
+
*/
|
|
320
|
+
function retireConfirmationDetails(facts: StaleBatchFacts): {
|
|
321
|
+
title: string;
|
|
322
|
+
summary: string;
|
|
323
|
+
details: string;
|
|
324
|
+
} {
|
|
325
|
+
return {
|
|
326
|
+
title: `Retire Stale Batch: ${facts.initiative_slug}`,
|
|
327
|
+
summary: `Batch: ${facts.batch_id}\nState: ${facts.batch_state}\nBatch branch: ${facts.branch}\nPlan digest: ${facts.plan_digest}\nRecorded child commits: ${facts.recorded_commits.length}\nConfirmed at: ${facts.confirmation_time}`,
|
|
328
|
+
details: `Children (${facts.children.length}):\n${facts.children
|
|
329
|
+
.map(
|
|
330
|
+
(child) =>
|
|
331
|
+
` - ${child.task_id} (${child.slice_id}) [state: ${child.state}]${child.commit ? ` [commit: ${child.commit}]` : " [no commit]"}${child.lane_branch ? ` [lane: ${child.lane_branch}]` : ""}`,
|
|
332
|
+
)
|
|
333
|
+
.join("\n")}\n\nRetiring writes this record to the terminal superseded state. Child states, recorded commits and the batch branch are preserved as evidence; nothing is deleted and no batch trailer or approval is manufactured.\n\nA child without a commit was never executed by this batch: it carries no delivery evidence, and the retired authorization is never reused for a revised plan.\n\nAfter retiring, run each remaining child with imm-run under its own authorization, or start a new batch for this Initiative.`,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export async function executePiRetireStaleBatch(options: {
|
|
338
|
+
root: string;
|
|
339
|
+
initiativeSlug: string;
|
|
340
|
+
signal?: AbortSignal;
|
|
341
|
+
confirmBatch: (details: { title: string; summary: string; details: string; signal?: AbortSignal }) => Promise<"accept" | "decline" | "cancel">;
|
|
342
|
+
}): Promise<PiRetireStaleBatchResult> {
|
|
343
|
+
const { root, initiativeSlug, signal } = options;
|
|
344
|
+
const outcome = await retireStaleBatch({
|
|
345
|
+
root,
|
|
346
|
+
initiative_slug: initiativeSlug,
|
|
347
|
+
now: new Date().toISOString(),
|
|
348
|
+
gate: async (facts) => {
|
|
349
|
+
const details = retireConfirmationDetails(facts);
|
|
350
|
+
let decision: "accept" | "decline" | "cancel";
|
|
351
|
+
try {
|
|
352
|
+
decision = await options.confirmBatch({ ...details, signal });
|
|
353
|
+
} catch (err) {
|
|
354
|
+
if (signal?.aborted) return { kind: "host_rejection", value: batchReason("confirmation_cancelled") };
|
|
355
|
+
return {
|
|
356
|
+
kind: "host_rejection",
|
|
357
|
+
value: batchReason("confirmation_failed", err instanceof Error ? err.message : String(err)),
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
if (decision === "cancel" || signal?.aborted)
|
|
361
|
+
return { kind: "host_rejection", value: batchReason("confirmation_cancelled") };
|
|
362
|
+
if (decision === "decline")
|
|
363
|
+
return { kind: "host_rejection", value: batchReason("confirmation_declined") };
|
|
364
|
+
if (decision !== "accept")
|
|
365
|
+
return { kind: "host_rejection", value: batchReason("confirmation_no_decision") };
|
|
366
|
+
return { kind: "confirmed", request_id: randomUUID() };
|
|
367
|
+
},
|
|
368
|
+
});
|
|
369
|
+
if (outcome.outcome === "host_rejection") return outcome.value;
|
|
370
|
+
if (outcome.outcome === "rejected") return outcome.rejection;
|
|
371
|
+
return { state: "retired", batch_id: outcome.batch_id, report: outcome.report };
|
|
372
|
+
}
|
|
373
|
+
|
|
308
374
|
/**
|
|
309
375
|
* Injectable seams for the batch Tool. Production passes nothing and gets the
|
|
310
376
|
* real Initiative reader, Kernel port and Git port; tests drive the registered
|
|
@@ -439,4 +505,103 @@ export default function (
|
|
|
439
505
|
);
|
|
440
506
|
},
|
|
441
507
|
});
|
|
508
|
+
// The batch disposition: one explicit literal-user decision that closes a
|
|
509
|
+
// batch the plan moved past, preserving its evidence instead of leaving the
|
|
510
|
+
// record parked forever or deleting it by hand. Grants no handoff.
|
|
511
|
+
pi.registerTool({
|
|
512
|
+
name: "retire_stale_batch",
|
|
513
|
+
label: "Retire stale batch",
|
|
514
|
+
description: "Retire an Initiative's parked or drifted batch record to the terminal superseded state after native confirmation.",
|
|
515
|
+
promptSnippet: "Retire stale batch: invoke once in foreground after the stale batch record is inspected.",
|
|
516
|
+
promptGuidelines: [
|
|
517
|
+
"Call only after inspecting the batch record; execute once in the foreground and consume the direct terminal result.",
|
|
518
|
+
"Do not run this Tool in background, poll for completion, or issue a cancel subcommand.",
|
|
519
|
+
],
|
|
520
|
+
parameters: Type.Object({
|
|
521
|
+
initiative_slug: Type.String({ pattern: "^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$" }),
|
|
522
|
+
}, { additionalProperties: false }),
|
|
523
|
+
execute: async (
|
|
524
|
+
_toolCallId: string,
|
|
525
|
+
params: { initiative_slug: string },
|
|
526
|
+
signal: AbortSignal | undefined,
|
|
527
|
+
_onUpdate: unknown,
|
|
528
|
+
ctx: ExtensionContext,
|
|
529
|
+
) => {
|
|
530
|
+
const { initiative_slug: initiativeSlug } = params;
|
|
531
|
+
if (ctx.mode !== "tui") {
|
|
532
|
+
const refusal = nonInteractiveRefusal();
|
|
533
|
+
throwToolFailure({
|
|
534
|
+
tool: "imm_canary_enrollment",
|
|
535
|
+
task_id: initiativeSlug,
|
|
536
|
+
operation: "retire_stale_batch",
|
|
537
|
+
state: "blocked",
|
|
538
|
+
code: "unsupported_host",
|
|
539
|
+
message: refusal.reason,
|
|
540
|
+
next_action: refusal.recovery_action,
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
const result = await executePiRetireStaleBatch({
|
|
545
|
+
root: ctx.cwd,
|
|
546
|
+
initiativeSlug,
|
|
547
|
+
signal,
|
|
548
|
+
confirmBatch: async (details) => {
|
|
549
|
+
presentTaskRail(ctx, {
|
|
550
|
+
task_id: initiativeSlug,
|
|
551
|
+
state: "Approval required",
|
|
552
|
+
result: details.title,
|
|
553
|
+
next: "Review batch record evidence",
|
|
554
|
+
});
|
|
555
|
+
const selected = await requestAuthorityDialog(
|
|
556
|
+
pi,
|
|
557
|
+
ctx,
|
|
558
|
+
{
|
|
559
|
+
attention_id: randomUUID(),
|
|
560
|
+
task_id: initiativeSlug,
|
|
561
|
+
reason: "enrollment",
|
|
562
|
+
label: details.title,
|
|
563
|
+
},
|
|
564
|
+
{
|
|
565
|
+
title: details.title,
|
|
566
|
+
summary: details.summary,
|
|
567
|
+
details: details.details,
|
|
568
|
+
signal: details.signal,
|
|
569
|
+
actions: [
|
|
570
|
+
{ value: "confirm", label: "Retire batch record", description: "Write the terminal superseded state and its report" },
|
|
571
|
+
{ value: "decline", label: "Keep batch parked", description: "Leave the batch record and its authorization unchanged" },
|
|
572
|
+
{ value: "cancel", label: "Cancel", description: "Leave repository and authority unchanged" },
|
|
573
|
+
],
|
|
574
|
+
},
|
|
575
|
+
);
|
|
576
|
+
return mapDialogSelection(selected);
|
|
577
|
+
},
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
if (result.state !== "retired") {
|
|
581
|
+
throwToolFailure({
|
|
582
|
+
tool: "imm_canary_enrollment",
|
|
583
|
+
task_id: initiativeSlug,
|
|
584
|
+
operation: "retire_stale_batch",
|
|
585
|
+
state: result.state === "blocked" ? "blocked" : "failed",
|
|
586
|
+
code: `batch_${result.state}`,
|
|
587
|
+
message: result.reason,
|
|
588
|
+
next_action: result.recovery_action,
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
return {
|
|
592
|
+
content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }],
|
|
593
|
+
details: result,
|
|
594
|
+
};
|
|
595
|
+
},
|
|
596
|
+
renderCall(args, theme) {
|
|
597
|
+
const params = args as { initiative_slug?: string };
|
|
598
|
+
return renderStructuredCall("retire_stale_batch", "retire", params.initiative_slug, theme);
|
|
599
|
+
},
|
|
600
|
+
renderResult(result, _options, theme) {
|
|
601
|
+
return renderStructuredResult(
|
|
602
|
+
result as Parameters<typeof renderStructuredResult>[0],
|
|
603
|
+
theme,
|
|
604
|
+
);
|
|
605
|
+
},
|
|
606
|
+
});
|
|
442
607
|
}
|
|
@@ -46,9 +46,10 @@ export type {
|
|
|
46
46
|
ReviewTimingProfile,
|
|
47
47
|
} from "../runtime/assurance/coordinator";
|
|
48
48
|
|
|
49
|
-
import { AssuranceCoordinator, type AssuranceCoordinatorPorts } from "../runtime/assurance/coordinator";
|
|
49
|
+
import { AssuranceCoordinator, type AssuranceCoordinatorPorts, type HostContext, type AssuranceSubmitReviewResult } from "../runtime/assurance/coordinator";
|
|
50
50
|
import type { AssuranceHostPort, HostReviewReservation, ReviewRequest } from "../runtime/assurance/host_port";
|
|
51
|
-
import {
|
|
51
|
+
import { submitMediatedReview, type ReviewObservation } from "../runtime/assurance/review_mediation";
|
|
52
|
+
import { reservedAgentParams, promptDigest } from "./pi-canary-native-review";
|
|
52
53
|
|
|
53
54
|
import type { VerdictAuthority } from "../runtime/assurance/verdict_authority";
|
|
54
55
|
|
|
@@ -57,9 +58,18 @@ export type AssuranceProgressionPorts = Omit<AssuranceCoordinatorPorts, "host">
|
|
|
57
58
|
authorityOverrides?: Partial<VerdictAuthority>;
|
|
58
59
|
};
|
|
59
60
|
|
|
61
|
+
interface RecordedReviewReceipt {
|
|
62
|
+
taskId: string;
|
|
63
|
+
promptDigest: string;
|
|
64
|
+
toolCallId?: string;
|
|
65
|
+
result?: string;
|
|
66
|
+
error?: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
60
69
|
class PiReviewHost implements AssuranceHostPort {
|
|
61
70
|
readonly host = "pi" as const;
|
|
62
71
|
private readonly pending = new Set<string>();
|
|
72
|
+
private readonly receipts = new Map<string, RecordedReviewReceipt>();
|
|
63
73
|
|
|
64
74
|
prepareReview(request: ReviewRequest): HostReviewReservation {
|
|
65
75
|
const params = reservedAgentParams({
|
|
@@ -69,17 +79,70 @@ class PiReviewHost implements AssuranceHostPort {
|
|
|
69
79
|
max_turns: request.maxTurns,
|
|
70
80
|
});
|
|
71
81
|
this.pending.add(request.operationId);
|
|
82
|
+
this.receipts.set(request.operationId, { taskId: request.taskId, promptDigest: promptDigest(params.prompt) });
|
|
72
83
|
return { id: request.operationId, dispatch: params };
|
|
73
84
|
}
|
|
74
85
|
|
|
75
86
|
releaseReview(reservation: HostReviewReservation): void {
|
|
76
87
|
this.pending.delete(reservation.id);
|
|
88
|
+
this.receipts.delete(reservation.id);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Extension event seam: correlate one observed `Agent` dispatch to its reservation (ADR 0017). */
|
|
92
|
+
observeReviewDispatch(input: { prompt?: unknown; subagent_type?: unknown } | undefined, toolCallId: string | undefined): void {
|
|
93
|
+
if (typeof input?.prompt !== "string") return;
|
|
94
|
+
for (const record of this.receipts.values()) {
|
|
95
|
+
if (record.promptDigest !== promptDigest(input.prompt)) continue;
|
|
96
|
+
if (input.subagent_type !== "Review") {
|
|
97
|
+
record.error = "the reserved reviewer prompt was dispatched with a different Agent type";
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (record.toolCallId !== undefined && record.toolCallId !== toolCallId) {
|
|
101
|
+
record.error = "the reserved reviewer was dispatched more than once";
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
record.toolCallId = toolCallId;
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Extension event seam: store the observed reviewer result bytes (ADR 0017). */
|
|
110
|
+
observeReviewResult(toolCallId: string | undefined, bytes: string): void {
|
|
111
|
+
if (!toolCallId) return;
|
|
112
|
+
for (const record of this.receipts.values()) {
|
|
113
|
+
if (record.toolCallId !== toolCallId) continue;
|
|
114
|
+
if (record.result === undefined) record.result = bytes;
|
|
115
|
+
else if (record.result !== bytes) record.error = "the reserved reviewer returned conflicting result bytes";
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
inspectReview(taskId: string): ReviewObservation {
|
|
121
|
+
for (const record of this.receipts.values()) {
|
|
122
|
+
if (record.taskId !== taskId) continue;
|
|
123
|
+
if (record.error) return { ok: false, release: false, reason: record.error };
|
|
124
|
+
if (record.toolCallId === undefined)
|
|
125
|
+
return { ok: false, release: false, reason: "the reserved foreground Agent was not observed in this session" };
|
|
126
|
+
if (record.result === undefined)
|
|
127
|
+
return { ok: false, release: false, reason: "the reserved foreground reviewer has not returned its result yet; wait for it, then call submit_review again" };
|
|
128
|
+
return { ok: true, receipt: { actorId: "pi-review-agent", result: record.result } };
|
|
129
|
+
}
|
|
130
|
+
return { ok: false, release: true, reason: "the reserved foreground Agent was not observed in this session" };
|
|
77
131
|
}
|
|
78
132
|
}
|
|
79
133
|
|
|
80
134
|
export class AssuranceProgression extends AssuranceCoordinator {
|
|
135
|
+
readonly piReviewHost: PiReviewHost;
|
|
136
|
+
|
|
81
137
|
constructor(ports: AssuranceProgressionPorts) {
|
|
82
|
-
|
|
138
|
+
const host = new PiReviewHost();
|
|
139
|
+
(ports as AssuranceCoordinatorPorts).host = host;
|
|
83
140
|
super(ports as AssuranceCoordinatorPorts, ports.authorityOverrides);
|
|
141
|
+
this.piReviewHost = host;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** ADR 0017: verdict-source resolution happens here, before the shared submit path. */
|
|
145
|
+
submitMediated(taskId: string, ctx: HostContext, verdictInput: unknown): Promise<AssuranceSubmitReviewResult> {
|
|
146
|
+
return submitMediatedReview(this, ctx, taskId, verdictInput, () => this.piReviewHost.inspectReview(taskId));
|
|
84
147
|
}
|
|
85
148
|
}
|
|
@@ -59,6 +59,16 @@ seam, or a seam silently replaced by a different one, is a finding carrying
|
|
|
59
59
|
named, its absence is not a finding: candidates written before this rule carries
|
|
60
60
|
no defect for lacking one.
|
|
61
61
|
|
|
62
|
+
Before pass, enumerate the immutable change set and read each complete per-file
|
|
63
|
+
diff, including deletions. Page bounded reads until every hunk is visible; a
|
|
64
|
+
truncated aggregate output or summary is incomplete coverage. Trace changed
|
|
65
|
+
high-risk behavior through its callers and state owner, and inspect a concrete
|
|
66
|
+
negative case against the embedded acceptance. If immutable evidence remains
|
|
67
|
+
unavailable or incomplete, report the concrete acceptance coverage gap in a
|
|
68
|
+
rework finding with required evidence. `inspected_paths` declares coverage; it
|
|
69
|
+
does not itself prove tool reading. Judge quality by evidence, not elapsed time
|
|
70
|
+
or tool-call count.
|
|
71
|
+
|
|
62
72
|
A required check that is skipped, that matches zero tests, or whose prerequisite
|
|
63
73
|
is absent does not prove the acceptance; do not accept it as evidence. Full
|
|
64
74
|
suite coverage reported at a coarse pass count is not per-invariant evidence.
|
|
@@ -96,5 +106,5 @@ Every rework finding must carry machine-checkable provenance: evidence.trigger (
|
|
|
96
106
|
|
|
97
107
|
A pass verdict's approval must carry `inspected_paths`: an array of unique repository-relative path strings listing every path of the reviewed change set (changed_paths for a Git review revision, dirty_files for a bundle), deleted paths included; an empty change set is listed as an empty array. A path may be listed only after its diff was read. A pass that omits any changed path, lists a path outside the change set, or duplicates a path is rejected as a correctable invalid verdict.
|
|
98
108
|
|
|
99
|
-
A dispatched reviewer is never continued or re-prompted, including through SendMessage. The reserved prompt is dispatched verbatim. A blocked `submit_review` is recovered only through its returned `recovery_action`.
|
|
109
|
+
A dispatched reviewer is never continued or re-prompted, including through SendMessage. The reserved prompt is dispatched verbatim. The host binds this reviewer's final result bytes to the reservation, so a Parent may submit them without transcribing them. A blocked `submit_review` is recovered only through its returned `recovery_action`.
|
|
100
110
|
|