immune-brain 3.6.9 → 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/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 +7587 -5049
- 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 +34 -27
- package/plugins/immune-brain/dist/imm-review-retro.md +2 -2
- 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 +1 -1
- 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/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,4 +1,3 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
1
|
import {
|
|
3
2
|
closeSync,
|
|
4
3
|
constants as fsConstants,
|
|
@@ -8,7 +7,7 @@ import {
|
|
|
8
7
|
writeFileSync,
|
|
9
8
|
} from "node:fs";
|
|
10
9
|
import { execFileSync } from "node:child_process";
|
|
11
|
-
import {
|
|
10
|
+
import { resolve } from "node:path";
|
|
12
11
|
import process from "node:process";
|
|
13
12
|
import { fileURLToPath } from "node:url";
|
|
14
13
|
import {
|
|
@@ -27,6 +26,7 @@ import {
|
|
|
27
26
|
parseTaskIntentV1,
|
|
28
27
|
RISK_FLOOR_SCOPE_PREFIXES,
|
|
29
28
|
} from "../kernel/intent";
|
|
29
|
+
import { inspectSpecBinding } from "../kernel/spec_binding";
|
|
30
30
|
import { projectTask, resolveProjectedRisk } from "../kernel/completion";
|
|
31
31
|
import { deriveAssuranceAuthorization } from "../kernel/assurance_projection";
|
|
32
32
|
import { taskDiffIdentity, taskRevisionIdentity } from "../workspace_scope";
|
|
@@ -40,7 +40,7 @@ import { projectLegacyAudit } from "../kernel/legacy_audit";
|
|
|
40
40
|
import { inspectStorageLayout } from "../kernel/storage_paths";
|
|
41
41
|
import { migrateLegacyLayout } from "../kernel/storage_layout_migration";
|
|
42
42
|
import { readBackendClaim } from "../kernel/backend_claim";
|
|
43
|
-
import {
|
|
43
|
+
import { probeKernelStore } from "../kernel";
|
|
44
44
|
|
|
45
45
|
export interface KernelCommandResult {
|
|
46
46
|
stdout: string;
|
|
@@ -65,12 +65,6 @@ function errorResult(code: string, message: string, returncode: number): KernelC
|
|
|
65
65
|
return jsonResult({ error: { code, message } }, returncode);
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
function asRecord(value: unknown): Record<string, unknown> {
|
|
69
|
-
return value && typeof value === "object" && !Array.isArray(value)
|
|
70
|
-
? (value as Record<string, unknown>)
|
|
71
|
-
: {};
|
|
72
|
-
}
|
|
73
|
-
|
|
74
68
|
function journalFor(
|
|
75
69
|
command: string,
|
|
76
70
|
phase: TaskPhase | null,
|
|
@@ -121,16 +115,7 @@ function runStatus(root: string): KernelExecution {
|
|
|
121
115
|
try {
|
|
122
116
|
const layout = inspectStorageLayout(root);
|
|
123
117
|
const claim = readBackendClaim(root);
|
|
124
|
-
const workspace = (
|
|
125
|
-
try {
|
|
126
|
-
const raw = JSON.parse(readSecureProjectFile(root, ".imm/state/workspace.json")) as {
|
|
127
|
-
current_working?: unknown;
|
|
128
|
-
};
|
|
129
|
-
return typeof raw.current_working === "string" ? raw.current_working : null;
|
|
130
|
-
} catch {
|
|
131
|
-
return null;
|
|
132
|
-
}
|
|
133
|
-
})();
|
|
118
|
+
const workspace = readWorkspaceOwner(root);
|
|
134
119
|
return {
|
|
135
120
|
result: jsonResult({
|
|
136
121
|
contract: "assurance_kernel/status/v1",
|
|
@@ -193,10 +178,7 @@ function matchingFloorEntries(intent: TaskIntentV1): string[] {
|
|
|
193
178
|
|
|
194
179
|
function readWorkspaceOwner(root: string): string | null {
|
|
195
180
|
try {
|
|
196
|
-
|
|
197
|
-
current_working?: unknown;
|
|
198
|
-
};
|
|
199
|
-
return typeof raw.current_working === "string" ? raw.current_working : null;
|
|
181
|
+
return readWorkspaceStateRaw(root).state.current_working;
|
|
200
182
|
} catch {
|
|
201
183
|
return null;
|
|
202
184
|
}
|
|
@@ -292,8 +274,8 @@ function runInspect(root: string): KernelExecution {
|
|
|
292
274
|
try {
|
|
293
275
|
identity =
|
|
294
276
|
record.contract === "assurance_kernel/task_record/v4"
|
|
295
|
-
? taskRevisionIdentity(root, intent.scope_hint, record.git_base_head)
|
|
296
|
-
: taskDiffIdentity(root, intent.scope_hint);
|
|
277
|
+
? taskRevisionIdentity(root, intent.scope_hint, record.git_base_head, intent.task_id)
|
|
278
|
+
: taskDiffIdentity(root, intent.scope_hint, intent.task_id);
|
|
297
279
|
} catch (error) {
|
|
298
280
|
return sourceFailure("inspect", error);
|
|
299
281
|
}
|
|
@@ -499,7 +481,7 @@ function runIntentAuthor(args: string[], root: string): KernelExecution {
|
|
|
499
481
|
// its frozen manifest under both locks. The original authoring is retried
|
|
500
482
|
// only after the affected migration diff is committed.
|
|
501
483
|
try {
|
|
502
|
-
|
|
484
|
+
probeKernelStore(root);
|
|
503
485
|
} catch (error) {
|
|
504
486
|
return {
|
|
505
487
|
result: errorResult(
|
|
@@ -536,46 +518,13 @@ function runIntentAuthor(args: string[], root: string): KernelExecution {
|
|
|
536
518
|
};
|
|
537
519
|
}
|
|
538
520
|
if (layout.layout === "recovery_required" || layout.layout === "migration_required") {
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
result: jsonResult({
|
|
543
|
-
contract: "assurance_kernel/migration_completed/v1",
|
|
544
|
-
operation: "intent author",
|
|
545
|
-
affected_paths: migration.affected_paths,
|
|
546
|
-
next_action: "commit the affected migration paths, then retry intent author",
|
|
547
|
-
}),
|
|
548
|
-
journal: journalFor(
|
|
549
|
-
"intent",
|
|
550
|
-
null,
|
|
551
|
-
"escalated",
|
|
552
|
-
"migration_ambiguous",
|
|
553
|
-
null,
|
|
554
|
-
"Legacy evidence was relocated without Git index writes; commit and retry.",
|
|
555
|
-
),
|
|
556
|
-
};
|
|
557
|
-
}
|
|
558
|
-
if (migration.outcome === "migration_uncommitted") {
|
|
559
|
-
return {
|
|
560
|
-
result: errorResult(
|
|
561
|
-
"migration_uncommitted",
|
|
562
|
-
`affected storage paths differ from HEAD: ${migration.affected_paths.join(", ") || "(none)"}`,
|
|
563
|
-
1,
|
|
564
|
-
),
|
|
565
|
-
journal: journalFor(
|
|
566
|
-
"intent",
|
|
567
|
-
null,
|
|
568
|
-
"rejected",
|
|
569
|
-
"migration_ambiguous",
|
|
570
|
-
null,
|
|
571
|
-
"Commit or restore the affected paths before retrying.",
|
|
572
|
-
),
|
|
573
|
-
};
|
|
574
|
-
}
|
|
521
|
+
// Authoring never migrates implicitly: the retired layout is converted by
|
|
522
|
+
// the explicit claimless command only, so creating a draft cannot publish a
|
|
523
|
+
// database or retire historical files behind the operator's back.
|
|
575
524
|
return {
|
|
576
525
|
result: errorResult(
|
|
577
|
-
layout.layout === "recovery_required" ? "layout_recovery_required" : "
|
|
578
|
-
|
|
526
|
+
layout.layout === "recovery_required" ? "layout_recovery_required" : "layout_migration_required",
|
|
527
|
+
`${layout.reason ?? "storage layout is not ready"}; run imm-kernel migrate --storage-layout first`,
|
|
579
528
|
1,
|
|
580
529
|
),
|
|
581
530
|
journal: journalFor(
|
|
@@ -584,7 +533,7 @@ function runIntentAuthor(args: string[], root: string): KernelExecution {
|
|
|
584
533
|
"rejected",
|
|
585
534
|
"source_invalid",
|
|
586
535
|
null,
|
|
587
|
-
"
|
|
536
|
+
"Run imm-kernel migrate --storage-layout, commit the affected paths, then retry intent author.",
|
|
588
537
|
),
|
|
589
538
|
};
|
|
590
539
|
}
|
|
@@ -760,7 +709,7 @@ function runIntentAuthor(args: string[], root: string): KernelExecution {
|
|
|
760
709
|
return {
|
|
761
710
|
result: errorResult(
|
|
762
711
|
"intent_invalid",
|
|
763
|
-
`candidate acceptance verification failed strict verification_descriptor/
|
|
712
|
+
`candidate acceptance verification failed strict verification_descriptor/v2 parsing: ${error instanceof Error ? error.message : error}`,
|
|
764
713
|
1,
|
|
765
714
|
),
|
|
766
715
|
journal: journalFor(
|
|
@@ -769,7 +718,7 @@ function runIntentAuthor(args: string[], root: string): KernelExecution {
|
|
|
769
718
|
"rejected",
|
|
770
719
|
"intent_invalid",
|
|
771
720
|
null,
|
|
772
|
-
"Fix the reported verification_descriptor/
|
|
721
|
+
"Fix the reported verification_descriptor/v2 violations and retry.",
|
|
773
722
|
),
|
|
774
723
|
};
|
|
775
724
|
}
|
|
@@ -1132,6 +1081,29 @@ function runIntentValidate(args: string[], root: string): KernelExecution {
|
|
|
1132
1081
|
),
|
|
1133
1082
|
};
|
|
1134
1083
|
}
|
|
1084
|
+
const specBinding = inspectSpecBinding(intent);
|
|
1085
|
+
if (!specBinding.ok) {
|
|
1086
|
+
return {
|
|
1087
|
+
result: jsonResult(
|
|
1088
|
+
{
|
|
1089
|
+
contract: "assurance_kernel/intent_validation/v1",
|
|
1090
|
+
valid: false,
|
|
1091
|
+
path: relative,
|
|
1092
|
+
task_id: intent.task_id,
|
|
1093
|
+
reason: specBinding.message,
|
|
1094
|
+
},
|
|
1095
|
+
0,
|
|
1096
|
+
),
|
|
1097
|
+
journal: journalFor(
|
|
1098
|
+
"intent",
|
|
1099
|
+
null,
|
|
1100
|
+
"ok",
|
|
1101
|
+
"command_ok",
|
|
1102
|
+
null,
|
|
1103
|
+
null,
|
|
1104
|
+
),
|
|
1105
|
+
};
|
|
1106
|
+
}
|
|
1135
1107
|
|
|
1136
1108
|
const verification: Array<{ id: string; eligible: boolean; reason: string | null }> = [];
|
|
1137
1109
|
let allEligible = true;
|
|
@@ -1244,6 +1216,25 @@ function executeKernelCommand(args: string[], root: string): KernelExecution {
|
|
|
1244
1216
|
};
|
|
1245
1217
|
return runInspect(root);
|
|
1246
1218
|
}
|
|
1219
|
+
if (command === "migrate") {
|
|
1220
|
+
if (flags.length !== 1 || flags[0] !== "--storage-layout")
|
|
1221
|
+
return {
|
|
1222
|
+
result: errorResult(
|
|
1223
|
+
"invalid_command",
|
|
1224
|
+
"migrate accepts only --storage-layout",
|
|
1225
|
+
2,
|
|
1226
|
+
),
|
|
1227
|
+
journal: journalFor(
|
|
1228
|
+
command,
|
|
1229
|
+
null,
|
|
1230
|
+
"rejected",
|
|
1231
|
+
"invalid_command",
|
|
1232
|
+
null,
|
|
1233
|
+
"Run imm-kernel migrate --storage-layout.",
|
|
1234
|
+
),
|
|
1235
|
+
};
|
|
1236
|
+
return runStorageLayoutMigration(root);
|
|
1237
|
+
}
|
|
1247
1238
|
if (command === "audit") {
|
|
1248
1239
|
if (args.length !== 2 || args[1] !== "--legacy")
|
|
1249
1240
|
return {
|
|
@@ -1341,6 +1332,106 @@ function executeKernelCommand(args: string[], root: string): KernelExecution {
|
|
|
1341
1332
|
};
|
|
1342
1333
|
}
|
|
1343
1334
|
|
|
1335
|
+
/**
|
|
1336
|
+
* Explicit claimless storage-layout migration.
|
|
1337
|
+
*
|
|
1338
|
+
* The retired file store is converted here and nowhere else: this command never
|
|
1339
|
+
* authors an intent, never opens a task, and never needs an active claim, so a
|
|
1340
|
+
* worktree that is still on the retired layout has one reachable entry point.
|
|
1341
|
+
* Mutating commands keep their fail-closed gate; this is the command that
|
|
1342
|
+
* resolves it.
|
|
1343
|
+
*/
|
|
1344
|
+
function runStorageLayoutMigration(root: string): KernelExecution {
|
|
1345
|
+
const inspection = inspectStorageLayout(root);
|
|
1346
|
+
// Only a live owner blocks the command. Every other diagnosis is handed to
|
|
1347
|
+
// the migrator, which owns the recovery decisions: an interrupted
|
|
1348
|
+
// publication shows both stores and must stay reachable through this CLI.
|
|
1349
|
+
if (inspection.layout === "migration_blocked_active")
|
|
1350
|
+
return {
|
|
1351
|
+
result: errorResult(
|
|
1352
|
+
"layout_migration_blocked",
|
|
1353
|
+
inspection.reason ?? "a live legacy task still owns work; settle or stop it on the prior runtime first",
|
|
1354
|
+
1,
|
|
1355
|
+
),
|
|
1356
|
+
journal: journalFor(
|
|
1357
|
+
"migrate",
|
|
1358
|
+
null,
|
|
1359
|
+
"rejected",
|
|
1360
|
+
"source_invalid",
|
|
1361
|
+
null,
|
|
1362
|
+
"Resolve the reported layout condition, then rerun imm-kernel migrate --storage-layout.",
|
|
1363
|
+
),
|
|
1364
|
+
};
|
|
1365
|
+
const migration = migrateLegacyLayout(root);
|
|
1366
|
+
if (migration.outcome === "migrated")
|
|
1367
|
+
return {
|
|
1368
|
+
result: jsonResult({
|
|
1369
|
+
contract: "assurance_kernel/migration_completed/v1",
|
|
1370
|
+
operation: "storage layout migration",
|
|
1371
|
+
affected_paths: migration.affected_paths,
|
|
1372
|
+
next_action: "commit the affected migration paths, then retry the original command",
|
|
1373
|
+
}),
|
|
1374
|
+
journal: journalFor(
|
|
1375
|
+
"migrate",
|
|
1376
|
+
null,
|
|
1377
|
+
"escalated",
|
|
1378
|
+
"migration_ambiguous",
|
|
1379
|
+
null,
|
|
1380
|
+
"Commit the affected migration paths, then retry the original command.",
|
|
1381
|
+
),
|
|
1382
|
+
};
|
|
1383
|
+
if (migration.outcome === "migration_uncommitted")
|
|
1384
|
+
return {
|
|
1385
|
+
result: jsonResult({
|
|
1386
|
+
contract: "assurance_kernel/migration_pending_commit/v1",
|
|
1387
|
+
operation: "storage layout migration",
|
|
1388
|
+
affected_paths: migration.affected_paths,
|
|
1389
|
+
reason: migration.reason,
|
|
1390
|
+
next_action: "commit the preserved audit evidence, then rerun imm-kernel migrate --storage-layout",
|
|
1391
|
+
}),
|
|
1392
|
+
journal: journalFor(
|
|
1393
|
+
"migrate",
|
|
1394
|
+
null,
|
|
1395
|
+
"escalated",
|
|
1396
|
+
"migration_ambiguous",
|
|
1397
|
+
null,
|
|
1398
|
+
"Commit the preserved audit evidence, then rerun imm-kernel migrate --storage-layout.",
|
|
1399
|
+
),
|
|
1400
|
+
};
|
|
1401
|
+
if (migration.outcome === "already_migrated")
|
|
1402
|
+
return {
|
|
1403
|
+
result: jsonResult({
|
|
1404
|
+
contract: "assurance_kernel/migration_completed/v1",
|
|
1405
|
+
operation: "storage layout migration",
|
|
1406
|
+
affected_paths: [],
|
|
1407
|
+
next_action: "retry the original command",
|
|
1408
|
+
}),
|
|
1409
|
+
journal: journalFor(
|
|
1410
|
+
"migrate",
|
|
1411
|
+
null,
|
|
1412
|
+
"ok",
|
|
1413
|
+
"command_ok",
|
|
1414
|
+
null,
|
|
1415
|
+
"Retry the original command.",
|
|
1416
|
+
),
|
|
1417
|
+
};
|
|
1418
|
+
return {
|
|
1419
|
+
result: errorResult(
|
|
1420
|
+
"layout_migration_blocked",
|
|
1421
|
+
migration.reason ?? "the storage layout migration did not complete",
|
|
1422
|
+
1,
|
|
1423
|
+
),
|
|
1424
|
+
journal: journalFor(
|
|
1425
|
+
"migrate",
|
|
1426
|
+
null,
|
|
1427
|
+
"rejected",
|
|
1428
|
+
"source_invalid",
|
|
1429
|
+
null,
|
|
1430
|
+
"Resolve the reported layout condition, then rerun imm-kernel migrate --storage-layout.",
|
|
1431
|
+
),
|
|
1432
|
+
};
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1344
1435
|
export function runKernelCommand(
|
|
1345
1436
|
args: string[],
|
|
1346
1437
|
root = process.cwd(),
|
|
@@ -2360,7 +2360,7 @@ export async function runGithubInitiativePublication(
|
|
|
2360
2360
|
);
|
|
2361
2361
|
if (!("get" in topology)) return publicationResult(topology.status, topology.message, topology);
|
|
2362
2362
|
const historicalRelations = new Map<string, { blocked_by: number[]; state_reason: string | null }>();
|
|
2363
|
-
for (const [taskId
|
|
2363
|
+
for (const [taskId] of amendment.historical) {
|
|
2364
2364
|
const issue = topology.get(taskId)!;
|
|
2365
2365
|
const observed = await readBlockedByIds(absoluteRoot, gh, "upsert-task", initial.repository, issue.number);
|
|
2366
2366
|
if (!Array.isArray(observed)) return publicationResult(observed.status, observed.message, observed);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// The literal-user authority identity, spelled once.
|
|
2
|
+
//
|
|
3
|
+
// Audit survey before the convergence (122 settled records under .imm/audit/,
|
|
4
|
+
// counting every `actor_id` in each record): `literal-user` appears 220 times
|
|
5
|
+
// across 54 records — the spelling Pi has recorded since the first managed
|
|
6
|
+
// task. The shortened `user` appears 4 times, all of them in three
|
|
7
|
+
// Claude-Host-era records, and the Kernel's own batch validation already
|
|
8
|
+
// demanded "a literal-user actor_id" while accepting the string "user". The
|
|
9
|
+
// converged spelling is therefore `literal-user`.
|
|
10
|
+
//
|
|
11
|
+
// Settled records are never rewritten: the reader keeps accepting both
|
|
12
|
+
// spellings, so a historical `user` audit or a batch capability issued before
|
|
13
|
+
// the convergence still validates and stays byte-identical. Every newly written
|
|
14
|
+
// record carries the canonical spelling.
|
|
15
|
+
|
|
16
|
+
export const LITERAL_USER_ACTOR_ID = "literal-user";
|
|
17
|
+
|
|
18
|
+
/** Spellings that mean the literal user. Both are read; only one is written. */
|
|
19
|
+
const LITERAL_USER_SPELLINGS: ReadonlySet<string> = new Set([LITERAL_USER_ACTOR_ID, "user"]);
|
|
20
|
+
|
|
21
|
+
/** True for a historical or current literal-user actor. */
|
|
22
|
+
export function isLiteralUserActor(actorId: string): boolean {
|
|
23
|
+
return LITERAL_USER_SPELLINGS.has(actorId);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The spelling to record. A historical `user` is read as what it meant and
|
|
28
|
+
* recorded as the canonical identity; every other actor is passed through
|
|
29
|
+
* unchanged.
|
|
30
|
+
*/
|
|
31
|
+
export function canonicalActorId(actorId: string): string {
|
|
32
|
+
return actorId === "user" ? LITERAL_USER_ACTOR_ID : actorId;
|
|
33
|
+
}
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
// the dedicated recoverable workspace transaction. Consumed by the Pi canary
|
|
5
5
|
// host adapter via canary_application.
|
|
6
6
|
|
|
7
|
+
import { terminalRequestDigest } from "./run_identity";
|
|
7
8
|
import {
|
|
8
9
|
type MutationAuthorityRegistry,
|
|
9
|
-
type ValidatedAuthorityV2,
|
|
10
10
|
} from "./authority_port";
|
|
11
11
|
import { asTaskDiffSnapshot } from "./completion";
|
|
12
12
|
import { canonicalIntentHash, readTaskIntent } from "./intent";
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
commitTerminalLocked,
|
|
27
27
|
readTaskRecordRaw,
|
|
28
28
|
readWorkspaceStateRaw,
|
|
29
|
+
currentRunId,
|
|
29
30
|
revisionForContent,
|
|
30
31
|
serializeWorkspace,
|
|
31
32
|
withKernelStoreLock,
|
|
@@ -37,8 +38,6 @@ import type {
|
|
|
37
38
|
AuthorityAuditDescriptor,
|
|
38
39
|
MutationAuthorityCapabilityV2,
|
|
39
40
|
StoredTaskMutationV3,
|
|
40
|
-
TaskAction,
|
|
41
|
-
TaskIntentV1,
|
|
42
41
|
TaskRecord,
|
|
43
42
|
} from "./types";
|
|
44
43
|
import { TASK_TOMBSTONE_CONTRACT, type TaskTombstone } from "./backend_claim";
|
|
@@ -164,9 +163,15 @@ export function applyTaskAction(
|
|
|
164
163
|
action.type === "stop" ||
|
|
165
164
|
action.type === "resolve_user_decision";
|
|
166
165
|
|
|
166
|
+
// A capability is opaque: its binding lives in the issuing registry, so
|
|
167
|
+
// the run this worktree holds is read here and handed to the registry as
|
|
168
|
+
// the expected identity. Authority issued for another run is refused
|
|
169
|
+
// before any write, which is what keeps it inside the worktree that
|
|
170
|
+
// issued it even when task, record, intent and diff content are identical.
|
|
167
171
|
const expectedAuthority = privileged
|
|
168
172
|
? {
|
|
169
173
|
task_id,
|
|
174
|
+
run_id: currentRunId(root, task_id) ?? undefined,
|
|
170
175
|
action,
|
|
171
176
|
expected_record_hash: current.revision,
|
|
172
177
|
intent_revision: isRevisionAction
|
|
@@ -189,6 +194,7 @@ export function applyTaskAction(
|
|
|
189
194
|
const inspectedAudit = expectedAuthority
|
|
190
195
|
? registry.inspect(capability, expectedAuthority, now)
|
|
191
196
|
: null;
|
|
197
|
+
const authorityRunId = inspectedAudit?.run_id;
|
|
192
198
|
const mutation = reduceTask(
|
|
193
199
|
current.record,
|
|
194
200
|
action,
|
|
@@ -227,9 +233,8 @@ export function applyTaskAction(
|
|
|
227
233
|
// All preflight passed: consume tokens and authority, then commit.
|
|
228
234
|
consumeIntentToken(prior_intent_token);
|
|
229
235
|
consumeIntentToken(freshRead.token);
|
|
230
|
-
let consumedAudit: ValidatedAuthorityV2 | null = null;
|
|
231
236
|
if (expectedAuthority) {
|
|
232
|
-
|
|
237
|
+
registry.consume(
|
|
233
238
|
capability as MutationAuthorityCapabilityV2,
|
|
234
239
|
expectedAuthority,
|
|
235
240
|
now,
|
|
@@ -284,7 +289,17 @@ export function applyTaskAction(
|
|
|
284
289
|
next_workspace_content: serializeWorkspace(nextWorkspaceState),
|
|
285
290
|
...(input.artifact_transition ? { artifact_relocations: input.artifact_transition.relocations } : {}),
|
|
286
291
|
};
|
|
287
|
-
commitTerminalLocked(
|
|
292
|
+
commitTerminalLocked(
|
|
293
|
+
root,
|
|
294
|
+
task_id,
|
|
295
|
+
transaction,
|
|
296
|
+
tombstone,
|
|
297
|
+
authorityRunId,
|
|
298
|
+
// The request this settlement answers: a retry with a different
|
|
299
|
+
// reason, actor or event time is a different decision and must be
|
|
300
|
+
// authorized again rather than replayed.
|
|
301
|
+
terminalRequestDigest(action),
|
|
302
|
+
);
|
|
288
303
|
return {
|
|
289
304
|
revision: canonicalRecordHash(nextRecord),
|
|
290
305
|
record: nextRecord,
|
|
@@ -303,6 +318,7 @@ export function applyTaskAction(
|
|
|
303
318
|
workspace.revision,
|
|
304
319
|
nextWorkspaceState,
|
|
305
320
|
input.artifact_transition?.relocations,
|
|
321
|
+
authorityRunId,
|
|
306
322
|
);
|
|
307
323
|
});
|
|
308
324
|
}
|
|
@@ -12,9 +12,16 @@
|
|
|
12
12
|
// "start QA" or "start Review"; it returns Kernel-owned facts only.
|
|
13
13
|
|
|
14
14
|
import { readBackendClaim, readTaskTombstone } from "./backend_claim";
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
readTaskRecord,
|
|
17
|
+
readAuditTaskPair,
|
|
18
|
+
readCommittedRecord,
|
|
19
|
+
readWorkspaceStateRaw,
|
|
20
|
+
reconcileKernelAuthority,
|
|
21
|
+
} from "./storage";
|
|
16
22
|
import { projectTask, resolveProjectedRisk } from "./completion";
|
|
17
|
-
import type { AssuranceObligation, TaskIntentV1, TaskRecord,
|
|
23
|
+
import type { AssuranceObligation, TaskIntentV1, TaskRecord, TaskRecordV3 } from "./types";
|
|
24
|
+
import type { TaskRecordV2 } from "./legacy_task_record";
|
|
18
25
|
|
|
19
26
|
export interface TaskDiffSnapshot {
|
|
20
27
|
diff_hash: string;
|
|
@@ -34,6 +41,12 @@ export interface AssuranceAuthorizationReadiness {
|
|
|
34
41
|
}
|
|
35
42
|
|
|
36
43
|
export interface AssuranceProjection {
|
|
44
|
+
/**
|
|
45
|
+
* The exact run this projection describes: one worktree owns one store and
|
|
46
|
+
* one active run, so the run id is the authority identity a capability must
|
|
47
|
+
* bind. Null when no run is committed.
|
|
48
|
+
*/
|
|
49
|
+
run_id: string | null;
|
|
37
50
|
record_revision: string;
|
|
38
51
|
workspace_revision: string;
|
|
39
52
|
intent_revision: number;
|
|
@@ -83,6 +96,7 @@ export function deriveAssuranceAuthorization(input: {
|
|
|
83
96
|
|
|
84
97
|
function emptyProjection(): AssuranceProjection {
|
|
85
98
|
return {
|
|
99
|
+
run_id: null,
|
|
86
100
|
record_revision: "",
|
|
87
101
|
workspace_revision: "",
|
|
88
102
|
intent_revision: 0,
|
|
@@ -128,6 +142,36 @@ function projectHistoricalTerminal(
|
|
|
128
142
|
};
|
|
129
143
|
}
|
|
130
144
|
|
|
145
|
+
function projectHistoricalTerminalV3(
|
|
146
|
+
record: TaskRecordV3,
|
|
147
|
+
recordRevision: string,
|
|
148
|
+
workspaceRevision: string,
|
|
149
|
+
): AssuranceProjection {
|
|
150
|
+
const freshAcceptanceIds = [
|
|
151
|
+
...new Set(
|
|
152
|
+
record.attestations
|
|
153
|
+
.filter((item) => item.kind === "qa")
|
|
154
|
+
.flatMap((item) => item.acceptance_results)
|
|
155
|
+
.filter((item) => item.status === "passed")
|
|
156
|
+
.map((item) => item.acceptance_id),
|
|
157
|
+
),
|
|
158
|
+
];
|
|
159
|
+
const approvalKinds = [...new Set(record.attestations.map((item) => item.kind))];
|
|
160
|
+
return {
|
|
161
|
+
...emptyProjection(),
|
|
162
|
+
record_revision: recordRevision,
|
|
163
|
+
workspace_revision: workspaceRevision,
|
|
164
|
+
intent_revision: record.intent_snapshot.revision,
|
|
165
|
+
intent_content_hash: record.intent_ref.content_hash,
|
|
166
|
+
lifecycle: record.lifecycle,
|
|
167
|
+
artifact_state: "frozen",
|
|
168
|
+
risk: record.intent_snapshot.risk,
|
|
169
|
+
fresh_acceptance_ids: freshAcceptanceIds,
|
|
170
|
+
fresh_approval_kinds: approvalKinds,
|
|
171
|
+
completion_ready: record.lifecycle === "done",
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
131
175
|
function freshApprovalKinds(
|
|
132
176
|
record: TaskRecord,
|
|
133
177
|
currentIntentContentHash: string,
|
|
@@ -168,6 +212,8 @@ function projectFromRecord(
|
|
|
168
212
|
(finding) => finding.kind === "unresolved_user_decision" && finding.status === "open",
|
|
169
213
|
).length;
|
|
170
214
|
return {
|
|
215
|
+
// Filled by the caller from the committed run row.
|
|
216
|
+
run_id: null,
|
|
171
217
|
record_revision: recordRevision,
|
|
172
218
|
workspace_revision: workspaceRevision,
|
|
173
219
|
intent_revision: record.intent_snapshot.revision,
|
|
@@ -232,9 +278,19 @@ export async function projectAssurance(
|
|
|
232
278
|
const tombstone = readTaskTombstone(root, taskId);
|
|
233
279
|
if (tombstone) {
|
|
234
280
|
const authority = reconcileKernelAuthority(root, taskId);
|
|
235
|
-
|
|
281
|
+
// A terminal proof keyed by task id may have been exported by
|
|
282
|
+
// another worktree's run of this same task: it is that run's
|
|
283
|
+
// evidence, not a conflict with this worktree's own run. The local
|
|
284
|
+
// store decides, and an active local run keeps its authority.
|
|
285
|
+
if (authority.state === "active_owner") {
|
|
286
|
+
// fall through: the local run owns this task here
|
|
287
|
+
} else if (authority.state === "repairable_stale_claim")
|
|
236
288
|
return fail(`task ${taskId} has a repairable stale backend claim`, claim);
|
|
237
|
-
|
|
289
|
+
else
|
|
290
|
+
return fail(
|
|
291
|
+
authority.diagnostic ?? `authority state conflicts for ${taskId}`,
|
|
292
|
+
claim,
|
|
293
|
+
);
|
|
238
294
|
}
|
|
239
295
|
} else {
|
|
240
296
|
const authority = reconcileKernelAuthority(root, taskId);
|
|
@@ -254,6 +310,28 @@ export async function projectAssurance(
|
|
|
254
310
|
}
|
|
255
311
|
if (!read.record) {
|
|
256
312
|
if (!terminalOwner) return fail(`task ${taskId} has no TaskRecord v3`, claim);
|
|
313
|
+
const committedRunId = reconcileKernelAuthority(root, taskId).owner_run_id;
|
|
314
|
+
// The store keeps the terminal record itself, so a settled task projects
|
|
315
|
+
// from committed facts even while its audit export is still in flight.
|
|
316
|
+
const committed = await readCommittedRecord(root, taskId);
|
|
317
|
+
if (committed) {
|
|
318
|
+
const workspace = await readWorkspaceStateRaw(root);
|
|
319
|
+
return {
|
|
320
|
+
contract: "assurance_kernel/assurance_projection/v1",
|
|
321
|
+
task_id: taskId,
|
|
322
|
+
error: null,
|
|
323
|
+
claim: null,
|
|
324
|
+
projection: {
|
|
325
|
+
...projectFromRecord(
|
|
326
|
+
committed.record,
|
|
327
|
+
committed.revision,
|
|
328
|
+
workspace.revision,
|
|
329
|
+
diffProvider(root, committed.record),
|
|
330
|
+
),
|
|
331
|
+
run_id: committedRunId,
|
|
332
|
+
},
|
|
333
|
+
};
|
|
334
|
+
}
|
|
257
335
|
const auditPair = await readAuditTaskPair(root, taskId);
|
|
258
336
|
if (!auditPair) return fail(`task ${taskId} has no terminal audit pair`, claim);
|
|
259
337
|
const workspace = await readWorkspaceStateRaw(root);
|
|
@@ -265,6 +343,14 @@ export async function projectAssurance(
|
|
|
265
343
|
claim: null,
|
|
266
344
|
projection: projectHistoricalTerminal(auditPair.record, auditPair.recordRevision, workspace.revision),
|
|
267
345
|
};
|
|
346
|
+
if (auditPair.record.contract === "assurance_kernel/task_record/v3")
|
|
347
|
+
return {
|
|
348
|
+
contract: "assurance_kernel/assurance_projection/v1",
|
|
349
|
+
task_id: taskId,
|
|
350
|
+
error: null,
|
|
351
|
+
claim: null,
|
|
352
|
+
projection: projectHistoricalTerminalV3(auditPair.record, auditPair.recordRevision, workspace.revision),
|
|
353
|
+
};
|
|
268
354
|
return {
|
|
269
355
|
contract: "assurance_kernel/assurance_projection/v1",
|
|
270
356
|
task_id: taskId,
|
|
@@ -284,7 +370,10 @@ export async function projectAssurance(
|
|
|
284
370
|
task_id: taskId,
|
|
285
371
|
error: null,
|
|
286
372
|
claim,
|
|
287
|
-
projection:
|
|
373
|
+
projection: {
|
|
374
|
+
...projectFromRecord(read.record, read.revision, workspace.revision, snapshot),
|
|
375
|
+
run_id: reconcileKernelAuthority(root, taskId).owner_run_id,
|
|
376
|
+
},
|
|
288
377
|
};
|
|
289
378
|
} catch (error) {
|
|
290
379
|
return fail(error instanceof Error ? error.message : String(error));
|