immune-brain 3.6.8 → 4.0.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 +11 -4
- package/README.zh-CN.md +10 -3
- package/package.json +3 -2
- package/plugins/immune-brain/.claude-plugin/plugin.json +1 -1
- package/plugins/immune-brain/.pi-extension/imm-canary-enroll.ts +18 -2
- package/plugins/immune-brain/.pi-extension/imm-canary-work.ts +76 -121
- package/plugins/immune-brain/.pi-extension/imm-unattended-batch.ts +106 -600
- package/plugins/immune-brain/.pi-extension/pi-canary-assurance-progression.ts +1 -0
- package/plugins/immune-brain/.pi-extension/pi-canary-verification.ts +3 -3
- package/plugins/immune-brain/.pi-extension/runtime-stub.ts +17 -43
- package/plugins/immune-brain/dist/claude/mcp-server.mjs +7581 -5042
- package/plugins/immune-brain/dist/docs/reference/planning-artifact-retention.md +11 -12
- package/plugins/immune-brain/dist/docs/reference/subagent-dispatch-protocol.md +1 -1
- package/plugins/immune-brain/dist/imm-loop.md +27 -25
- package/plugins/immune-brain/dist/imm-planner.md +53 -32
- package/plugins/immune-brain/dist/imm-review-retro.md +123 -0
- package/plugins/immune-brain/dist/registry.yaml +9 -0
- package/plugins/immune-brain/dist/role-prompts/code-review.md +3 -1
- package/plugins/immune-brain/dist/role-prompts/executor.md +4 -4
- package/plugins/immune-brain/runtime/assurance/coordinator.ts +183 -40
- package/plugins/immune-brain/runtime/assurance/delivery_workspace.ts +240 -0
- package/plugins/immune-brain/runtime/assurance/qa.ts +132 -58
- package/plugins/immune-brain/runtime/assurance/review_evidence.ts +15 -7
- package/plugins/immune-brain/runtime/assurance/verification.ts +246 -206
- package/plugins/immune-brain/runtime/authorization_operation.ts +20 -0
- package/plugins/immune-brain/runtime/claude/kernel_ports.ts +288 -721
- package/plugins/immune-brain/runtime/commands/kernel.ts +158 -67
- package/plugins/immune-brain/runtime/github_issue_tracker.ts +254 -29
- package/plugins/immune-brain/runtime/kernel/actor_identity.ts +33 -0
- package/plugins/immune-brain/runtime/kernel/application.ts +22 -6
- package/plugins/immune-brain/runtime/kernel/assurance_projection.ts +94 -5
- package/plugins/immune-brain/runtime/kernel/authority_port.ts +27 -6
- package/plugins/immune-brain/runtime/kernel/backend_claim.ts +43 -16
- package/plugins/immune-brain/runtime/kernel/batch_authority.ts +10 -6
- package/plugins/immune-brain/runtime/kernel/canary_application.ts +50 -63
- package/plugins/immune-brain/runtime/kernel/canary_eligibility.ts +13 -4
- package/plugins/immune-brain/runtime/kernel/completion.ts +5 -14
- package/plugins/immune-brain/runtime/kernel/enrollment.ts +124 -34
- package/plugins/immune-brain/runtime/kernel/enrollment_authority.ts +13 -5
- package/plugins/immune-brain/runtime/kernel/index.ts +3 -1
- package/plugins/immune-brain/runtime/kernel/intent.ts +7 -11
- package/plugins/immune-brain/runtime/kernel/legacy_audit.ts +4 -1
- package/plugins/immune-brain/runtime/kernel/legacy_task_record.ts +323 -0
- package/plugins/immune-brain/runtime/kernel/pi_canary_prepare.ts +10 -1
- package/plugins/immune-brain/runtime/kernel/reducer.ts +32 -31
- package/plugins/immune-brain/runtime/kernel/run_identity.ts +121 -0
- package/plugins/immune-brain/runtime/kernel/spec_binding.ts +100 -0
- package/plugins/immune-brain/runtime/kernel/sqlite_migration.ts +950 -0
- package/plugins/immune-brain/runtime/kernel/sqlite_store.ts +1193 -0
- package/plugins/immune-brain/runtime/kernel/storage.ts +1254 -1206
- package/plugins/immune-brain/runtime/kernel/storage_layout_migration.ts +129 -755
- package/plugins/immune-brain/runtime/kernel/storage_paths.ts +419 -46
- package/plugins/immune-brain/runtime/kernel/types.ts +12 -43
- package/plugins/immune-brain/runtime/kernel/validation.ts +60 -274
- package/plugins/immune-brain/runtime/managed_task_routing_policy.ts +0 -1
- package/plugins/immune-brain/runtime/plan_core.ts +27 -65
- package/plugins/immune-brain/runtime/plugin_version.ts +1 -1
- package/plugins/immune-brain/runtime/prompts/code-review.md +3 -1
- package/plugins/immune-brain/runtime/prompts/executor.md +4 -4
- package/plugins/immune-brain/runtime/staged_intent.ts +58 -0
- package/plugins/immune-brain/runtime/unattended/batch_git.ts +37 -7
- package/plugins/immune-brain/runtime/unattended/batch_plan.ts +42 -2
- package/plugins/immune-brain/runtime/unattended/batch_preflight.ts +771 -0
- package/plugins/immune-brain/runtime/unattended/batch_reasons.ts +189 -0
- package/plugins/immune-brain/runtime/unattended/batch_runner.ts +35 -0
- package/plugins/immune-brain/runtime/unattended/confirmation_deadline.ts +33 -0
- package/plugins/immune-brain/runtime/unattended/types.ts +14 -1
- package/plugins/immune-brain/runtime/v4_runtime.ts +19 -23
- package/plugins/immune-brain/runtime/verification_descriptor.ts +92 -136
- package/plugins/immune-brain/runtime/workspace_scope.ts +98 -13
- package/plugins/immune-brain/skills/imm-planner/SKILL.md +3 -3
- package/plugins/immune-brain/skills/imm-review-retro/SKILL.md +23 -0
- package/plugins/immune-brain/skills/imm-review-retro/scripts/review_retro.ts +355 -0
- package/plugins/immune-brain/skills/registry.yaml +9 -0
- package/plugins/immune-brain/bin/imm-retire-stale-wrapper +0 -4
- package/plugins/immune-brain/bin/imm-retired +0 -4
- package/plugins/immune-brain/runtime/authority_commit_receipts.ts +0 -716
- package/plugins/immune-brain/runtime/kernel/automatic_observations.ts +0 -451
- package/plugins/immune-brain/runtime/kernel/legacy.ts +0 -299
- package/plugins/immune-brain/runtime/kernel/observation.ts +0 -397
- package/plugins/immune-brain/runtime/kernel/readiness.ts +0 -282
- package/plugins/immune-brain/runtime/kernel/readiness_evidence.ts +0 -132
|
@@ -1,299 +0,0 @@
|
|
|
1
|
-
import type { LegacyMapping } from "./types";
|
|
2
|
-
|
|
3
|
-
const STEP_STATES = new Set([
|
|
4
|
-
"pending",
|
|
5
|
-
"active",
|
|
6
|
-
"probing",
|
|
7
|
-
"executing",
|
|
8
|
-
"ready_for_review",
|
|
9
|
-
"rework_needed",
|
|
10
|
-
"closed",
|
|
11
|
-
"replanning",
|
|
12
|
-
]);
|
|
13
|
-
const FOLLOW_UP_STATES = new Set([
|
|
14
|
-
"pending",
|
|
15
|
-
"executing",
|
|
16
|
-
"ready_for_review",
|
|
17
|
-
"rework_needed",
|
|
18
|
-
"closed",
|
|
19
|
-
"replanning",
|
|
20
|
-
]);
|
|
21
|
-
const FOLLOW_UP_GATES = new Set(["imm-code-review", "imm-ui-review"]);
|
|
22
|
-
|
|
23
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
24
|
-
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function asRecord(value: unknown): Record<string, unknown> {
|
|
28
|
-
return isRecord(value) ? value : {};
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function nonEmptyString(value: unknown): value is string {
|
|
32
|
-
return typeof value === "string" && Boolean(value.trim());
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function latestFinishResetPlanPath(state: Record<string, unknown>): string | null {
|
|
36
|
-
if (!Array.isArray(state.history)) return null;
|
|
37
|
-
for (let index = state.history.length - 1; index >= 0; index -= 1) {
|
|
38
|
-
const entry = asRecord(state.history[index]);
|
|
39
|
-
if (entry.action !== "finish_reset") continue;
|
|
40
|
-
const planPath = asRecord(entry.details).plan_path;
|
|
41
|
-
return nonEmptyString(planPath) ? planPath : null;
|
|
42
|
-
}
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function isFinishedPlan(
|
|
47
|
-
state: Record<string, unknown>,
|
|
48
|
-
planPath: string,
|
|
49
|
-
): boolean {
|
|
50
|
-
const steps = Object.values(asRecord(state.steps));
|
|
51
|
-
return (
|
|
52
|
-
steps.length > 0 &&
|
|
53
|
-
steps.every((step) => asRecord(step).state === "closed") &&
|
|
54
|
-
state.runtime_status === "idle" &&
|
|
55
|
-
state.reset_reason === "intentional_reset" &&
|
|
56
|
-
state.requires_replan === false &&
|
|
57
|
-
(state.active_step === null || state.active_step === undefined) &&
|
|
58
|
-
(state.pending_follow_up === null ||
|
|
59
|
-
state.pending_follow_up === undefined) &&
|
|
60
|
-
latestFinishResetPlanPath(state) === planPath
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function validFollowUp(value: unknown): value is Record<string, unknown> {
|
|
65
|
-
if (!isRecord(value) || !FOLLOW_UP_STATES.has(String(value.state))) return false;
|
|
66
|
-
const origin = value.origin_review;
|
|
67
|
-
return (
|
|
68
|
-
nonEmptyString(value.id) &&
|
|
69
|
-
Array.isArray(value.scope) &&
|
|
70
|
-
value.scope.length > 0 &&
|
|
71
|
-
value.scope.every(nonEmptyString) &&
|
|
72
|
-
nonEmptyString(value.change_goal) &&
|
|
73
|
-
nonEmptyString(value.verification_hint) &&
|
|
74
|
-
isRecord(origin) &&
|
|
75
|
-
FOLLOW_UP_GATES.has(String(origin.gate)) &&
|
|
76
|
-
nonEmptyString(origin.evidence_ref) &&
|
|
77
|
-
(value.execution_evidence === null || isRecord(value.execution_evidence)) &&
|
|
78
|
-
nonEmptyString(value.opened_at) &&
|
|
79
|
-
Number.isInteger(value.round) &&
|
|
80
|
-
Number(value.round) >= 1 &&
|
|
81
|
-
(value.qa_decision === undefined || isRecord(value.qa_decision)) &&
|
|
82
|
-
(value.closed_at === undefined || nonEmptyString(value.closed_at))
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function inspectTerminal(
|
|
87
|
-
state: Record<string, unknown>,
|
|
88
|
-
): "absent" | "valid" | "invalid" | "conflict" {
|
|
89
|
-
if (state.plan_terminal === null || state.plan_terminal === undefined)
|
|
90
|
-
return "absent";
|
|
91
|
-
if (!isRecord(state.plan_terminal)) return "invalid";
|
|
92
|
-
const terminalPath = state.plan_terminal.plan_path;
|
|
93
|
-
if (
|
|
94
|
-
!nonEmptyString(terminalPath) ||
|
|
95
|
-
!(["cancelled", "superseded"] as unknown[]).includes(
|
|
96
|
-
state.plan_terminal.status,
|
|
97
|
-
)
|
|
98
|
-
)
|
|
99
|
-
return "invalid";
|
|
100
|
-
if (
|
|
101
|
-
nonEmptyString(state.plan_path) &&
|
|
102
|
-
state.plan_path !== terminalPath
|
|
103
|
-
)
|
|
104
|
-
return "conflict";
|
|
105
|
-
return "valid";
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export interface LegacyAggregateInspection {
|
|
109
|
-
step_states: Array<[string, string]>;
|
|
110
|
-
current_steps: Array<[string, string]>;
|
|
111
|
-
source_states: string[];
|
|
112
|
-
follow_up_present: boolean;
|
|
113
|
-
follow_up_state: string | null;
|
|
114
|
-
follow_up_malformed: boolean;
|
|
115
|
-
steps_malformed: boolean;
|
|
116
|
-
active_step_mismatch: boolean;
|
|
117
|
-
ownership_conflict: boolean;
|
|
118
|
-
replan_signal: boolean;
|
|
119
|
-
replan_mismatch: boolean;
|
|
120
|
-
replan_type_invalid: boolean;
|
|
121
|
-
terminal: "absent" | "valid" | "invalid" | "conflict";
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
export function inspectLegacyAggregate(raw: unknown): LegacyAggregateInspection {
|
|
125
|
-
const state = asRecord(raw);
|
|
126
|
-
const rawSteps = state.steps;
|
|
127
|
-
const stepsRecord = isRecord(rawSteps) ? rawSteps : {};
|
|
128
|
-
const stepEntries = Object.entries(stepsRecord);
|
|
129
|
-
const stepStates: Array<[string, string]> = [];
|
|
130
|
-
let stepsMalformed = !isRecord(rawSteps);
|
|
131
|
-
for (const [key, value] of stepEntries) {
|
|
132
|
-
const stepState = asRecord(value).state;
|
|
133
|
-
if (!nonEmptyString(stepState) || !STEP_STATES.has(stepState)) {
|
|
134
|
-
stepsMalformed = true;
|
|
135
|
-
continue;
|
|
136
|
-
}
|
|
137
|
-
stepStates.push([key, stepState]);
|
|
138
|
-
}
|
|
139
|
-
const currentSteps = stepStates.filter(
|
|
140
|
-
([, stepState]) => stepState !== "closed" && stepState !== "pending",
|
|
141
|
-
);
|
|
142
|
-
|
|
143
|
-
const followUpPresent =
|
|
144
|
-
state.pending_follow_up !== null && state.pending_follow_up !== undefined;
|
|
145
|
-
const followUpValid = followUpPresent && validFollowUp(state.pending_follow_up);
|
|
146
|
-
const followUpState = followUpValid
|
|
147
|
-
? String(asRecord(state.pending_follow_up).state)
|
|
148
|
-
: null;
|
|
149
|
-
|
|
150
|
-
let activeStepKey: string | null = null;
|
|
151
|
-
let activeStepMalformed = false;
|
|
152
|
-
if (state.active_step !== null && state.active_step !== undefined) {
|
|
153
|
-
if (
|
|
154
|
-
typeof state.active_step === "number" ||
|
|
155
|
-
nonEmptyString(state.active_step)
|
|
156
|
-
)
|
|
157
|
-
activeStepKey = String(state.active_step);
|
|
158
|
-
else activeStepMalformed = true;
|
|
159
|
-
}
|
|
160
|
-
const activeStepMismatch = activeStepMalformed ||
|
|
161
|
-
(activeStepKey !== null &&
|
|
162
|
-
(currentSteps.length !== 1 || currentSteps[0][0] !== activeStepKey)) ||
|
|
163
|
-
(activeStepKey === null &&
|
|
164
|
-
currentSteps.length === 1 &&
|
|
165
|
-
!followUpPresent &&
|
|
166
|
-
currentSteps[0][1] !== "replanning");
|
|
167
|
-
const ownershipConflict = currentSteps.length > 1 ||
|
|
168
|
-
(currentSteps.length > 0 && followUpValid && followUpState !== "closed");
|
|
169
|
-
const replanSignal =
|
|
170
|
-
currentSteps.some(([, stepState]) => stepState === "replanning") ||
|
|
171
|
-
followUpState === "replanning";
|
|
172
|
-
const replanTypeInvalid =
|
|
173
|
-
state.requires_replan !== undefined &&
|
|
174
|
-
typeof state.requires_replan !== "boolean";
|
|
175
|
-
const replanMismatch =
|
|
176
|
-
!replanTypeInvalid && (state.requires_replan === true) !== replanSignal;
|
|
177
|
-
const sourceStates = Array.from(
|
|
178
|
-
new Set([
|
|
179
|
-
...stepStates.map(([, stepState]) => stepState),
|
|
180
|
-
...(followUpState ? [`follow_up:${followUpState}`] : []),
|
|
181
|
-
]),
|
|
182
|
-
).sort();
|
|
183
|
-
|
|
184
|
-
const terminal = inspectTerminal(state);
|
|
185
|
-
return {
|
|
186
|
-
step_states: stepStates,
|
|
187
|
-
current_steps: currentSteps,
|
|
188
|
-
source_states: sourceStates,
|
|
189
|
-
follow_up_present: followUpPresent,
|
|
190
|
-
follow_up_state: followUpState,
|
|
191
|
-
follow_up_malformed: followUpPresent && !followUpValid,
|
|
192
|
-
steps_malformed: stepsMalformed,
|
|
193
|
-
active_step_mismatch: activeStepMismatch,
|
|
194
|
-
ownership_conflict: ownershipConflict,
|
|
195
|
-
replan_signal: replanSignal,
|
|
196
|
-
replan_mismatch: replanMismatch,
|
|
197
|
-
replan_type_invalid: replanTypeInvalid,
|
|
198
|
-
terminal:
|
|
199
|
-
terminal === "valid" &&
|
|
200
|
-
(currentSteps.length > 0 || followUpPresent)
|
|
201
|
-
? "conflict"
|
|
202
|
-
: terminal,
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
function stopped(
|
|
207
|
-
states: string[],
|
|
208
|
-
reason = "legacy-inconsistent",
|
|
209
|
-
ambiguous = true,
|
|
210
|
-
): LegacyMapping {
|
|
211
|
-
return {
|
|
212
|
-
phase: "stopped",
|
|
213
|
-
reason,
|
|
214
|
-
ambiguous,
|
|
215
|
-
source_states: states,
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
function mapCurrentState(state: string, sourceStates: string[]): LegacyMapping {
|
|
220
|
-
if (state === "ready_for_review")
|
|
221
|
-
return {
|
|
222
|
-
phase: "review",
|
|
223
|
-
reason: "legacy-review",
|
|
224
|
-
ambiguous: false,
|
|
225
|
-
source_states: sourceStates,
|
|
226
|
-
};
|
|
227
|
-
if (["active", "executing", "probing", "rework_needed"].includes(state))
|
|
228
|
-
return {
|
|
229
|
-
phase: "working",
|
|
230
|
-
reason: "legacy-working",
|
|
231
|
-
ambiguous: false,
|
|
232
|
-
source_states: sourceStates,
|
|
233
|
-
};
|
|
234
|
-
return stopped(sourceStates);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
export function mapLegacyState(raw: unknown): LegacyMapping {
|
|
238
|
-
const state = asRecord(raw);
|
|
239
|
-
const aggregate = inspectLegacyAggregate(raw);
|
|
240
|
-
const states = aggregate.source_states;
|
|
241
|
-
|
|
242
|
-
if (
|
|
243
|
-
aggregate.steps_malformed ||
|
|
244
|
-
aggregate.follow_up_malformed ||
|
|
245
|
-
aggregate.active_step_mismatch ||
|
|
246
|
-
aggregate.ownership_conflict ||
|
|
247
|
-
aggregate.replan_mismatch ||
|
|
248
|
-
aggregate.replan_type_invalid ||
|
|
249
|
-
aggregate.terminal === "invalid" ||
|
|
250
|
-
aggregate.terminal === "conflict"
|
|
251
|
-
)
|
|
252
|
-
return stopped(states);
|
|
253
|
-
if (aggregate.terminal === "valid")
|
|
254
|
-
return stopped(states, "legacy-terminal");
|
|
255
|
-
if (aggregate.follow_up_state === "closed") return stopped(states);
|
|
256
|
-
if (aggregate.replan_signal)
|
|
257
|
-
return stopped(states, "legacy-replan", false);
|
|
258
|
-
if (aggregate.follow_up_state) {
|
|
259
|
-
if (aggregate.follow_up_state === "ready_for_review")
|
|
260
|
-
return {
|
|
261
|
-
phase: "review",
|
|
262
|
-
reason: "legacy-review",
|
|
263
|
-
ambiguous: false,
|
|
264
|
-
source_states: states,
|
|
265
|
-
};
|
|
266
|
-
return {
|
|
267
|
-
phase: "working",
|
|
268
|
-
reason: "legacy-working",
|
|
269
|
-
ambiguous: false,
|
|
270
|
-
source_states: states,
|
|
271
|
-
};
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
if (aggregate.step_states.length === 0)
|
|
275
|
-
return stopped(states, "legacy-empty");
|
|
276
|
-
if (aggregate.current_steps.length === 1)
|
|
277
|
-
return mapCurrentState(aggregate.current_steps[0][1], states);
|
|
278
|
-
if (aggregate.step_states.some(([, stepState]) => stepState === "pending"))
|
|
279
|
-
return {
|
|
280
|
-
phase: "working",
|
|
281
|
-
reason: "legacy-working",
|
|
282
|
-
ambiguous: false,
|
|
283
|
-
source_states: states,
|
|
284
|
-
};
|
|
285
|
-
if (!nonEmptyString(state.plan_path)) return stopped(states);
|
|
286
|
-
if (isFinishedPlan(state, state.plan_path))
|
|
287
|
-
return {
|
|
288
|
-
phase: "done",
|
|
289
|
-
reason: "legacy-finished",
|
|
290
|
-
ambiguous: false,
|
|
291
|
-
source_states: states,
|
|
292
|
-
};
|
|
293
|
-
return {
|
|
294
|
-
phase: "review",
|
|
295
|
-
reason: "legacy-review",
|
|
296
|
-
ambiguous: false,
|
|
297
|
-
source_states: states,
|
|
298
|
-
};
|
|
299
|
-
}
|
|
@@ -1,397 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import { basename, dirname, resolve } from "node:path";
|
|
3
|
-
import { stableStringify } from "../canonical_json";
|
|
4
|
-
import { inspectLegacyAggregate, mapLegacyState } from "./legacy";
|
|
5
|
-
import {
|
|
6
|
-
appendObservationJournalEntry,
|
|
7
|
-
type JournalEntry,
|
|
8
|
-
} from "./storage";
|
|
9
|
-
import {
|
|
10
|
-
appendAutomaticObservationV2,
|
|
11
|
-
readAutomaticObservationsV2,
|
|
12
|
-
type V3AuthorityObservationV2,
|
|
13
|
-
} from "./automatic_observations";
|
|
14
|
-
import {
|
|
15
|
-
AUTHORITY_OBSERVATION_GENERATION_V2,
|
|
16
|
-
AUTHORITY_OBSERVER_VERSION_V2,
|
|
17
|
-
readAuthorityCommitReceipts,
|
|
18
|
-
type AuthorityCommitReceipt,
|
|
19
|
-
type AuthorityObservationSeedV2,
|
|
20
|
-
} from "../authority_commit_receipts";
|
|
21
|
-
import type {
|
|
22
|
-
V3AuthorityObservation,
|
|
23
|
-
V3CommitSourceEvent,
|
|
24
|
-
} from "./types";
|
|
25
|
-
|
|
26
|
-
export const V3_OBSERVER_VERSION = "assurance-kernel-p2a-observer/v1";
|
|
27
|
-
|
|
28
|
-
export interface CommittedLedgerReceipt {
|
|
29
|
-
attempt_id: string;
|
|
30
|
-
source_kind: "state_mutation" | "project_migration";
|
|
31
|
-
source_ref: string;
|
|
32
|
-
receipt_status:
|
|
33
|
-
| "committed"
|
|
34
|
-
| "prepared"
|
|
35
|
-
| "recovered_committed";
|
|
36
|
-
previous_state: Record<string, unknown>;
|
|
37
|
-
proposed_state: Record<string, unknown>;
|
|
38
|
-
committed_state: Record<string, unknown>;
|
|
39
|
-
committed_bytes: string;
|
|
40
|
-
ledger_revision: string;
|
|
41
|
-
committed_at: string;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
type ObservationAppender = (
|
|
45
|
-
root: string,
|
|
46
|
-
entry: JournalEntry & { observation: V3AuthorityObservation },
|
|
47
|
-
) => "appended" | "duplicate";
|
|
48
|
-
|
|
49
|
-
type ObservationV2Appender = (
|
|
50
|
-
root: string,
|
|
51
|
-
observation: V3AuthorityObservationV2,
|
|
52
|
-
) => "appended" | "duplicate";
|
|
53
|
-
|
|
54
|
-
let observationAppenderForTest: ObservationAppender | null = null;
|
|
55
|
-
let observationV2AppenderForTest: ObservationV2Appender | null = null;
|
|
56
|
-
|
|
57
|
-
/** Test-only fault seam; production always appends through secure kernel storage. */
|
|
58
|
-
export function setObservationAppenderForTest(
|
|
59
|
-
appender: ObservationAppender | null,
|
|
60
|
-
): void {
|
|
61
|
-
observationAppenderForTest = appender;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/** Test-only fault seam for the additive automatic-observation v2 journal. */
|
|
65
|
-
export function setObservationV2AppenderForTest(
|
|
66
|
-
appender: ObservationV2Appender | null,
|
|
67
|
-
): void {
|
|
68
|
-
observationV2AppenderForTest = appender;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function asRecord(value: unknown): Record<string, unknown> {
|
|
72
|
-
return value && typeof value === "object" && !Array.isArray(value)
|
|
73
|
-
? (value as Record<string, unknown>)
|
|
74
|
-
: {};
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function stringOrNull(value: unknown): string | null {
|
|
78
|
-
return typeof value === "string" && value.trim() ? value : null;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function digest(domain: string, value: string): string {
|
|
82
|
-
return `sha256:${createHash("sha256").update(`${domain}\0${value}`).digest("hex")}`;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function projectRootForLedgerPath(statePath: string): string | null {
|
|
86
|
-
if (basename(statePath) !== "current_iteration.json") return null;
|
|
87
|
-
const memoryDirectory = dirname(statePath);
|
|
88
|
-
if (basename(memoryDirectory) !== "memory") return null;
|
|
89
|
-
const immDirectory = dirname(memoryDirectory);
|
|
90
|
-
if (basename(immDirectory) !== ".imm") return null;
|
|
91
|
-
return resolve(dirname(immDirectory));
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function newHistoryEvents(
|
|
95
|
-
previous: Record<string, unknown>,
|
|
96
|
-
proposed: Record<string, unknown>,
|
|
97
|
-
receipt: Pick<
|
|
98
|
-
CommittedLedgerReceipt,
|
|
99
|
-
| "attempt_id"
|
|
100
|
-
| "source_kind"
|
|
101
|
-
| "source_ref"
|
|
102
|
-
| "receipt_status"
|
|
103
|
-
| "committed_at"
|
|
104
|
-
>,
|
|
105
|
-
): V3CommitSourceEvent[] {
|
|
106
|
-
const before = Array.isArray(previous.history) ? previous.history : [];
|
|
107
|
-
const after = Array.isArray(proposed.history) ? proposed.history : [];
|
|
108
|
-
if (after.length < before.length)
|
|
109
|
-
throw new Error("authority commit removed history events");
|
|
110
|
-
for (let index = 0; index < before.length; index += 1) {
|
|
111
|
-
if (stableStringify(before[index]) !== stableStringify(after[index]))
|
|
112
|
-
throw new Error("authority commit rewrote existing history");
|
|
113
|
-
}
|
|
114
|
-
const appended = after.slice(before.length).map((raw) => {
|
|
115
|
-
const entry = asRecord(raw);
|
|
116
|
-
const canonical = stableStringify(entry);
|
|
117
|
-
return {
|
|
118
|
-
id: digest("assurance-kernel-v3-history-event/v1", canonical),
|
|
119
|
-
action: stringOrNull(entry.action) ?? "unknown",
|
|
120
|
-
at: stringOrNull(entry.at),
|
|
121
|
-
};
|
|
122
|
-
});
|
|
123
|
-
if (appended.length > 0) return appended;
|
|
124
|
-
return [
|
|
125
|
-
{
|
|
126
|
-
id: receipt.attempt_id,
|
|
127
|
-
action:
|
|
128
|
-
receipt.source_kind === "project_migration"
|
|
129
|
-
? `project_migration:${receipt.source_ref}`
|
|
130
|
-
: receipt.receipt_status === "recovered_committed"
|
|
131
|
-
? `recovered_state_mutation:${receipt.source_ref}`
|
|
132
|
-
: `state_mutation:${receipt.source_ref}`,
|
|
133
|
-
at: receipt.committed_at,
|
|
134
|
-
},
|
|
135
|
-
];
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function divergenceFields(state: Record<string, unknown>): string[] {
|
|
139
|
-
const aggregate = inspectLegacyAggregate(state);
|
|
140
|
-
const fields: string[] = [];
|
|
141
|
-
if (aggregate.replan_mismatch || aggregate.replan_type_invalid)
|
|
142
|
-
fields.push("requires_replan");
|
|
143
|
-
if (aggregate.active_step_mismatch) fields.push("active_step");
|
|
144
|
-
if (aggregate.ownership_conflict) fields.push("ownership");
|
|
145
|
-
if (aggregate.follow_up_malformed || aggregate.follow_up_state === "closed")
|
|
146
|
-
fields.push("pending_follow_up");
|
|
147
|
-
if (aggregate.steps_malformed) fields.push("steps");
|
|
148
|
-
if (["invalid", "conflict"].includes(aggregate.terminal))
|
|
149
|
-
fields.push("plan_terminal");
|
|
150
|
-
const nextAction = asRecord(state.next_action);
|
|
151
|
-
if (
|
|
152
|
-
aggregate.replan_signal &&
|
|
153
|
-
(nextAction.action === "activate" ||
|
|
154
|
-
(typeof nextAction.command === "string" &&
|
|
155
|
-
nextAction.command.includes("imm-work activate")))
|
|
156
|
-
)
|
|
157
|
-
fields.push("next_action");
|
|
158
|
-
return fields;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function legacyTaskId(state: Record<string, unknown>): string {
|
|
162
|
-
const identity = [state.plan_path, state.plan_signature]
|
|
163
|
-
.map((value) => (typeof value === "string" ? value : ""))
|
|
164
|
-
.join("\0");
|
|
165
|
-
return `legacy-${createHash("sha256").update(identity).digest("hex").slice(0, 16)}`;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
export function buildV3AuthorityObservation(
|
|
169
|
-
receipt: CommittedLedgerReceipt,
|
|
170
|
-
): V3AuthorityObservation {
|
|
171
|
-
const sourceEvents = newHistoryEvents(
|
|
172
|
-
receipt.previous_state,
|
|
173
|
-
receipt.proposed_state,
|
|
174
|
-
receipt,
|
|
175
|
-
);
|
|
176
|
-
const shadow = mapLegacyState(receipt.committed_state);
|
|
177
|
-
const fields = divergenceFields(receipt.committed_state);
|
|
178
|
-
const core = {
|
|
179
|
-
contract: "assurance_kernel/v3_authority_observation/v1" as const,
|
|
180
|
-
observer_version: V3_OBSERVER_VERSION,
|
|
181
|
-
commit_id: receipt.attempt_id,
|
|
182
|
-
ledger_revision: receipt.ledger_revision,
|
|
183
|
-
committed_at: receipt.committed_at,
|
|
184
|
-
plan_path: stringOrNull(receipt.committed_state.plan_path),
|
|
185
|
-
plan_signature: stringOrNull(receipt.committed_state.plan_signature),
|
|
186
|
-
source_events: sourceEvents,
|
|
187
|
-
shadow,
|
|
188
|
-
divergence: { detected: fields.length > 0, fields },
|
|
189
|
-
};
|
|
190
|
-
return {
|
|
191
|
-
...core,
|
|
192
|
-
observation_id: digest(
|
|
193
|
-
"assurance-kernel-v3-observation/v1",
|
|
194
|
-
stableStringify(core),
|
|
195
|
-
),
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
export function buildAuthorityObservationSeedV2(
|
|
200
|
-
statePathIdentity: string,
|
|
201
|
-
receipt: CommittedLedgerReceipt,
|
|
202
|
-
): AuthorityObservationSeedV2 {
|
|
203
|
-
const sourceEvents = newHistoryEvents(
|
|
204
|
-
receipt.previous_state,
|
|
205
|
-
receipt.proposed_state,
|
|
206
|
-
receipt,
|
|
207
|
-
);
|
|
208
|
-
const shadow = mapLegacyState(receipt.committed_state);
|
|
209
|
-
const fields = divergenceFields(receipt.committed_state);
|
|
210
|
-
return {
|
|
211
|
-
contract: "assurance_kernel/authority_observation_seed/v2",
|
|
212
|
-
observer_version: AUTHORITY_OBSERVER_VERSION_V2,
|
|
213
|
-
source_kind: receipt.source_kind,
|
|
214
|
-
source_ref: receipt.source_ref,
|
|
215
|
-
state_path_identity: statePathIdentity,
|
|
216
|
-
committed_bytes_sha256: `sha256:${createHash("sha256")
|
|
217
|
-
.update(receipt.committed_bytes)
|
|
218
|
-
.digest("hex")}`,
|
|
219
|
-
committed_revision: receipt.ledger_revision,
|
|
220
|
-
plan_path: stringOrNull(receipt.committed_state.plan_path),
|
|
221
|
-
plan_signature: stringOrNull(receipt.committed_state.plan_signature),
|
|
222
|
-
source_events: sourceEvents,
|
|
223
|
-
shadow,
|
|
224
|
-
divergence: { detected: fields.length > 0, fields },
|
|
225
|
-
committed_at: receipt.committed_at,
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function committedReceiptV2(
|
|
230
|
-
receipt: AuthorityCommitReceipt,
|
|
231
|
-
): receipt is AuthorityCommitReceipt & {
|
|
232
|
-
contract: "assurance_kernel/authority_commit_receipt/v2";
|
|
233
|
-
status: "committed" | "recovered_committed";
|
|
234
|
-
observation_seed: AuthorityObservationSeedV2;
|
|
235
|
-
} {
|
|
236
|
-
return (
|
|
237
|
-
receipt.contract === "assurance_kernel/authority_commit_receipt/v2" &&
|
|
238
|
-
(receipt.status === "committed" ||
|
|
239
|
-
receipt.status === "recovered_committed") &&
|
|
240
|
-
receipt.observation_seed !== undefined
|
|
241
|
-
);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
export function buildAutomaticObservationV2(
|
|
245
|
-
receipt: AuthorityCommitReceipt,
|
|
246
|
-
): V3AuthorityObservationV2 {
|
|
247
|
-
if (!committedReceiptV2(receipt))
|
|
248
|
-
throw new Error("automatic observation requires a committed receipt-v2 seed");
|
|
249
|
-
const seed = receipt.observation_seed;
|
|
250
|
-
const core = {
|
|
251
|
-
contract: "assurance_kernel/v3_authority_observation/v2" as const,
|
|
252
|
-
receipt_record_id: receipt.record_id,
|
|
253
|
-
receipt_attempt_id: receipt.attempt_id,
|
|
254
|
-
receipt_protocol: receipt.contract,
|
|
255
|
-
receipt_status: receipt.status,
|
|
256
|
-
source_kind: seed.source_kind,
|
|
257
|
-
source_ref: seed.source_ref,
|
|
258
|
-
state_path_identity: seed.state_path_identity,
|
|
259
|
-
committed_bytes_sha256: seed.committed_bytes_sha256,
|
|
260
|
-
ledger_revision: seed.committed_revision,
|
|
261
|
-
plan_path: seed.plan_path,
|
|
262
|
-
plan_signature: seed.plan_signature,
|
|
263
|
-
source_events: seed.source_events,
|
|
264
|
-
shadow: seed.shadow,
|
|
265
|
-
divergence: seed.divergence,
|
|
266
|
-
observer_generation: AUTHORITY_OBSERVATION_GENERATION_V2,
|
|
267
|
-
observer_version: seed.observer_version,
|
|
268
|
-
committed_at: seed.committed_at,
|
|
269
|
-
observed_at: receipt.recorded_at,
|
|
270
|
-
};
|
|
271
|
-
return {
|
|
272
|
-
...core,
|
|
273
|
-
observation_id: digest(
|
|
274
|
-
"assurance-kernel-v3-observation/v2",
|
|
275
|
-
stableStringify(core),
|
|
276
|
-
),
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
export function setAutomaticObservationV2AppenderForTest(
|
|
281
|
-
appender:
|
|
282
|
-
| ((
|
|
283
|
-
root: string,
|
|
284
|
-
observation: V3AuthorityObservationV2,
|
|
285
|
-
) => "appended" | "duplicate")
|
|
286
|
-
| null,
|
|
287
|
-
): void {
|
|
288
|
-
observationV2AppenderForTest = appender;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
export function observeTerminalReceiptV2(
|
|
292
|
-
statePath: string,
|
|
293
|
-
receipt: AuthorityCommitReceipt,
|
|
294
|
-
): "appended" | "duplicate" | "not_production_ledger" {
|
|
295
|
-
const root = projectRootForLedgerPath(statePath);
|
|
296
|
-
if (!root) return "not_production_ledger";
|
|
297
|
-
return (observationV2AppenderForTest ?? appendAutomaticObservationV2)(
|
|
298
|
-
root,
|
|
299
|
-
buildAutomaticObservationV2(receipt),
|
|
300
|
-
);
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
export function replayMissingAutomaticObservationsV2(statePath: string): number {
|
|
304
|
-
const root = projectRootForLedgerPath(statePath);
|
|
305
|
-
if (!root) return 0;
|
|
306
|
-
const receipts = readAuthorityCommitReceipts(statePath);
|
|
307
|
-
const existing = new Set(
|
|
308
|
-
readAutomaticObservationsV2(root).map((entry) => entry.receipt_record_id),
|
|
309
|
-
);
|
|
310
|
-
let appended = 0;
|
|
311
|
-
for (const receipt of receipts) {
|
|
312
|
-
if (receipt.contract !== "assurance_kernel/authority_commit_receipt/v2")
|
|
313
|
-
continue;
|
|
314
|
-
if (!committedReceiptV2(receipt) || existing.has(receipt.record_id)) continue;
|
|
315
|
-
observeTerminalReceiptV2(statePath, receipt);
|
|
316
|
-
existing.add(receipt.record_id);
|
|
317
|
-
appended += 1;
|
|
318
|
-
}
|
|
319
|
-
return appended;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
export function observeTerminalReceiptV2BestEffort(
|
|
323
|
-
statePath: string,
|
|
324
|
-
receipt: AuthorityCommitReceipt,
|
|
325
|
-
): void {
|
|
326
|
-
try {
|
|
327
|
-
observeTerminalReceiptV2(statePath, receipt);
|
|
328
|
-
} catch (error) {
|
|
329
|
-
console.error(
|
|
330
|
-
`warning: authority receipt v2 persisted but automatic observation failed: ${
|
|
331
|
-
error instanceof Error ? error.message : error
|
|
332
|
-
}`,
|
|
333
|
-
);
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
export function replayMissingAutomaticObservationsV2BestEffort(
|
|
338
|
-
statePath: string,
|
|
339
|
-
): void {
|
|
340
|
-
try {
|
|
341
|
-
replayMissingAutomaticObservationsV2(statePath);
|
|
342
|
-
} catch (error) {
|
|
343
|
-
console.error(
|
|
344
|
-
`warning: automatic observation recovery failed: ${
|
|
345
|
-
error instanceof Error ? error.message : error
|
|
346
|
-
}`,
|
|
347
|
-
);
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
export function observeCommittedLedger(
|
|
352
|
-
statePath: string,
|
|
353
|
-
receipt: CommittedLedgerReceipt,
|
|
354
|
-
): "appended" | "duplicate" | "not_production_ledger" {
|
|
355
|
-
const root = projectRootForLedgerPath(statePath);
|
|
356
|
-
if (!root) return "not_production_ledger";
|
|
357
|
-
const observation = buildV3AuthorityObservation(receipt);
|
|
358
|
-
const entry: JournalEntry & { observation: V3AuthorityObservation } = {
|
|
359
|
-
contract: "assurance_kernel/journal/v1",
|
|
360
|
-
timestamp: new Date().toISOString(),
|
|
361
|
-
task_id: legacyTaskId(receipt.committed_state),
|
|
362
|
-
command: "observe_v3_authority_commit",
|
|
363
|
-
entry_phase: observation.shadow.phase,
|
|
364
|
-
result:
|
|
365
|
-
observation.shadow.ambiguous || observation.divergence.detected
|
|
366
|
-
? "escalated"
|
|
367
|
-
: "ok",
|
|
368
|
-
reason_code: observation.divergence.detected
|
|
369
|
-
? "shadow_divergence"
|
|
370
|
-
: "authority_commit_observed",
|
|
371
|
-
recovery_hint: observation.divergence.detected
|
|
372
|
-
? "Inspect the committed v3 authority fields; the observer never repairs them."
|
|
373
|
-
: null,
|
|
374
|
-
planner_reentry: false,
|
|
375
|
-
user_intervention: false,
|
|
376
|
-
observation,
|
|
377
|
-
};
|
|
378
|
-
return (observationAppenderForTest ?? appendObservationJournalEntry)(
|
|
379
|
-
root,
|
|
380
|
-
entry,
|
|
381
|
-
);
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
export function observeCommittedLedgerBestEffort(
|
|
385
|
-
statePath: string,
|
|
386
|
-
receipt: CommittedLedgerReceipt,
|
|
387
|
-
): void {
|
|
388
|
-
try {
|
|
389
|
-
observeCommittedLedger(statePath, receipt);
|
|
390
|
-
} catch (error) {
|
|
391
|
-
console.error(
|
|
392
|
-
`warning: v3 authority commit was persisted but shadow observation failed: ${
|
|
393
|
-
error instanceof Error ? error.message : error
|
|
394
|
-
}`,
|
|
395
|
-
);
|
|
396
|
-
}
|
|
397
|
-
}
|