immune-brain 3.6.4 → 3.6.5

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.
@@ -27,6 +27,7 @@ import {
27
27
  import {
28
28
  mintToken,
29
29
  type TaskIntentIdentityToken,
30
+ type TokenIdentity,
30
31
  } from "./intent_token_registry";
31
32
 
32
33
  export const INTENT_MAX_BYTES = 64 * 1024;
@@ -406,6 +407,26 @@ export interface ReadTaskIntentResult {
406
407
  token: TaskIntentIdentityToken;
407
408
  }
408
409
 
410
+ export interface ObservedTaskIntent {
411
+ intent: TaskIntentV1;
412
+ content_hash: string;
413
+ intent_ref: TaskIntentRefV1;
414
+ }
415
+
416
+ export class TaskIntentObservationError extends Error {
417
+ readonly code: "missing" | "invalid";
418
+
419
+ constructor(code: "missing" | "invalid", message: string) {
420
+ super(message);
421
+ this.name = "TaskIntentObservationError";
422
+ this.code = code;
423
+ }
424
+ }
425
+
426
+ interface TaskIntentReadSource extends ObservedTaskIntent {
427
+ identity: TokenIdentity;
428
+ }
429
+
409
430
  export function setIntentReaderTestHook(
410
431
  hook: { onBeforeDescriptorRead?: () => void } | null,
411
432
  ): void {
@@ -523,11 +544,11 @@ function assertIdentitiesUnchanged(
523
544
  }
524
545
  }
525
546
 
526
- export function readTaskIntent(
547
+ function readTaskIntentSource(
527
548
  root: string,
528
549
  taskId: string,
529
550
  requestedPath?: string,
530
- ): ReadTaskIntentResult {
551
+ ): TaskIntentReadSource {
531
552
  validateTaskId(taskId);
532
553
 
533
554
  const canonicalRoot = resolveCanonicalRoot(root);
@@ -545,7 +566,7 @@ export function readTaskIntent(
545
566
  if (!target.startsWith(canonicalRoot + sep))
546
567
  throw new Error("intent sidecar escapes project root");
547
568
  if (!sidecarPresent(canonicalRoot, sidecarPath))
548
- throw new Error(`TaskIntent sidecar is missing at ${sidecarPath}`);
569
+ throw new TaskIntentObservationError("missing", `TaskIntent sidecar is missing at ${sidecarPath}`);
549
570
 
550
571
  const pathIdentities = collectPathIdentities(canonicalRoot, sidecarPath);
551
572
  const fileIdentity = pathIdentities[pathIdentities.length - 1];
@@ -557,13 +578,19 @@ export function readTaskIntent(
557
578
  ["ls-files", "--error-unmatch", "--", sidecarPath],
558
579
  { cwd: canonicalRoot, stdio: ["ignore", "pipe", "pipe"] },
559
580
  );
560
- } catch {
561
- throw new Error("TaskIntent sidecar is not Git-tracked");
581
+ } catch (error) {
582
+ if (
583
+ typeof error === "object"
584
+ && error !== null
585
+ && "status" in error
586
+ && (error as { status?: unknown }).status === 1
587
+ ) throw new TaskIntentObservationError("invalid", "TaskIntent sidecar is not Git-tracked");
588
+ throw error;
562
589
  }
563
590
 
564
591
  const before = lstatSync(target);
565
592
  if (!before.isFile() || before.size > INTENT_MAX_BYTES)
566
- throw new Error("TaskIntent sidecar must be a regular file no larger than 64 KiB");
593
+ throw new TaskIntentObservationError("invalid", "TaskIntent sidecar must be a regular file no larger than 64 KiB");
567
594
 
568
595
  const fd = openSync(target, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW);
569
596
  let bytes: Buffer;
@@ -576,7 +603,7 @@ export function readTaskIntent(
576
603
  closeSync(fd);
577
604
  }
578
605
  if (bytes.byteLength > INTENT_MAX_BYTES)
579
- throw new Error("TaskIntent sidecar exceeds 64 KiB");
606
+ throw new TaskIntentObservationError("invalid", "TaskIntent sidecar exceeds 64 KiB");
580
607
 
581
608
  // Post-read identity re-verification without a second path read as the
582
609
  // source of bytes.
@@ -594,27 +621,15 @@ export function readTaskIntent(
594
621
  try {
595
622
  intent = parseTaskIntentV1(JSON.parse(bytes.toString("utf8")));
596
623
  } catch (error) {
597
- throw new Error(
624
+ throw new TaskIntentObservationError(
625
+ "invalid",
598
626
  `TaskIntent sidecar is invalid: ${error instanceof Error ? error.message : String(error)}`,
599
627
  );
600
628
  }
601
629
  if (intent.task_id !== taskId)
602
- throw new Error("intent.task_id does not match the sidecar filename task id");
630
+ throw new TaskIntentObservationError("invalid", "intent.task_id does not match the sidecar filename task id");
603
631
 
604
632
  const contentHash = canonicalIntentHash(intent);
605
- const token = mintToken({
606
- canonical_root: canonicalRoot,
607
- sidecar_path: sidecarPath,
608
- path_dev: fileIdentity.dev,
609
- path_ino: fileIdentity.ino,
610
- fd_dev: before.dev,
611
- fd_ino: before.ino,
612
- fd_size: before.size,
613
- fd_mtime_ms: before.mtimeMs,
614
- source_bytes_sha256: sourceBytesSha256,
615
- intent_content_hash: contentHash,
616
- });
617
-
618
633
  return {
619
634
  intent,
620
635
  content_hash: contentHash,
@@ -623,6 +638,35 @@ export function readTaskIntent(
623
638
  revision: intent.revision,
624
639
  content_hash: contentHash,
625
640
  },
626
- token,
641
+ identity: {
642
+ canonical_root: canonicalRoot,
643
+ sidecar_path: sidecarPath,
644
+ path_dev: fileIdentity.dev,
645
+ path_ino: fileIdentity.ino,
646
+ fd_dev: before.dev,
647
+ fd_ino: before.ino,
648
+ fd_size: before.size,
649
+ fd_mtime_ms: before.mtimeMs,
650
+ source_bytes_sha256: sourceBytesSha256,
651
+ intent_content_hash: contentHash,
652
+ },
627
653
  };
628
654
  }
655
+
656
+ export function observeTaskIntent(
657
+ root: string,
658
+ taskId: string,
659
+ requestedPath?: string,
660
+ ): ObservedTaskIntent {
661
+ const { identity: _identity, ...observed } = readTaskIntentSource(root, taskId, requestedPath);
662
+ return observed;
663
+ }
664
+
665
+ export function readTaskIntent(
666
+ root: string,
667
+ taskId: string,
668
+ requestedPath?: string,
669
+ ): ReadTaskIntentResult {
670
+ const { identity, ...observed } = readTaskIntentSource(root, taskId, requestedPath);
671
+ return { ...observed, token: mintToken(identity) };
672
+ }
@@ -178,6 +178,7 @@ function hasPrivilegedKind(action: TaskAction): boolean {
178
178
  action.type === "record_approval" ||
179
179
  action.type === "approve_breaking_intent_revision" ||
180
180
  action.type === "request_rework" ||
181
+ action.type === "authorize_rework" ||
181
182
  action.type === "stop" ||
182
183
  action.type === "resolve_user_decision"
183
184
  );
@@ -449,13 +450,16 @@ export function reduceTask(
449
450
  "request_rework requires review, qa, or user authority",
450
451
  ]);
451
452
  const round = reviewRound(record);
452
- const reviewAuthorityReworks = record.history.filter(
453
- (entry) =>
454
- entry.type === "request_rework" &&
455
- entry.authority?.authority_kind === "review",
456
- ).length;
453
+ const hasPriorBlockingReviewRework = record.findings.some(
454
+ (finding) =>
455
+ finding.source === "review" &&
456
+ finding.kind === "blocking" &&
457
+ finding.review_round !== null,
458
+ );
457
459
  const parkForReplan =
458
- authorityAudit.authority_kind === "review" && reviewAuthorityReworks >= 1;
460
+ authorityAudit.authority_kind === "review" &&
461
+ hasPriorBlockingReviewRework &&
462
+ action.findings.some((finding) => finding.kind === "blocking");
459
463
  if (!parkForReplan) {
460
464
  record.artifact_state = "active";
461
465
  record.intent_ref.path = `docs/plans/${record.task_id}.intent.json`;
@@ -470,8 +474,8 @@ export function reduceTask(
470
474
  record.findings.push({
471
475
  ...finding,
472
476
  status: "open",
473
- source: "review",
474
- review_round: round,
477
+ source: authorityAudit.authority_kind === "review" ? "review" : "execution",
478
+ review_round: authorityAudit.authority_kind === "review" ? round : null,
475
479
  });
476
480
  }
477
481
  if (
@@ -502,6 +506,28 @@ export function reduceTask(
502
506
  appendHistory(record, action, from, `review_round_${round}`, authorityAudit);
503
507
  break;
504
508
  }
509
+ case "authorize_rework": {
510
+ if (record.lifecycle !== "active")
511
+ throw new KernelInvariantError([
512
+ `cannot authorize rework while lifecycle is ${record.lifecycle}`,
513
+ ]);
514
+ if (authorityAudit?.authority_kind !== "user")
515
+ throw new KernelInvariantError([
516
+ "authorize_rework requires literal-user authority",
517
+ ]);
518
+ const open = record.findings.filter(
519
+ (finding) => finding.kind === "replan_required" && finding.status === "open",
520
+ );
521
+ if (open.length === 0)
522
+ throw new KernelInvariantError([
523
+ "authorize_rework requires an open replan boundary",
524
+ ]);
525
+ for (const finding of open) finding.status = "resolved";
526
+ record.artifact_state = "active";
527
+ record.intent_ref.path = `docs/plans/${record.task_id}.intent.json`;
528
+ appendHistory(record, action, from, open.map((finding) => finding.id).join(","), authorityAudit);
529
+ break;
530
+ }
505
531
  case "complete": {
506
532
  if (record.lifecycle !== "active" || record.artifact_state !== "frozen")
507
533
  throw new KernelInvariantError([
@@ -297,6 +297,7 @@ export type TaskAction =
297
297
  | (TaskActionBase & { type: "request_rework"; findings: TaskFinding[] })
298
298
  | (TaskActionBase & { type: "complete" })
299
299
  | (TaskActionBase & { type: "stop"; reason: string })
300
+ | (TaskActionBase & { type: "authorize_rework" })
300
301
  | (TaskActionBase & { type: "resolve_user_decision"; finding_id: string; resolution: string });
301
302
 
302
303
 
@@ -735,6 +735,7 @@ const ACTION_V2_TYPES = [
735
735
  "request_rework",
736
736
  "complete",
737
737
  "stop",
738
+ "authorize_rework",
738
739
  "resolve_user_decision",
739
740
  ] as const;
740
741
 
@@ -897,7 +898,8 @@ export function parseTaskAction(raw: unknown): TaskAction {
897
898
  };
898
899
  break;
899
900
  }
900
- case "complete": {
901
+ case "complete":
902
+ case "authorize_rework": {
901
903
  rejectUnknown(value, [...ACTION_BASE_FIELDS], "action", violations);
902
904
  action = { ...base, type: base.type };
903
905
  break;
@@ -1002,6 +1004,7 @@ export function assertTaskRecordUpdateV3(
1002
1004
  if (
1003
1005
  next.intent_ref.path !== previous.intent_ref.path &&
1004
1006
  action.type !== "request_rework" &&
1007
+ action.type !== "authorize_rework" &&
1005
1008
  action.type !== "stop"
1006
1009
  )
1007
1010
  violations.push("only artifact transitions may change intent_ref path");
@@ -1023,11 +1026,12 @@ export function assertTaskRecordUpdateV3(
1023
1026
  ? [action.finding_id]
1024
1027
  : action.type === "resolve_user_decision"
1025
1028
  ? [action.finding_id]
1026
- : action.type === "approve_breaking_intent_revision"
1027
- ? previous.findings
1028
- .filter((item) => item.kind === "replan_required" && item.status === "open")
1029
- .map((item) => item.id)
1030
- : [];
1029
+ : action.type === "authorize_rework" ||
1030
+ action.type === "approve_breaking_intent_revision"
1031
+ ? previous.findings
1032
+ .filter((item) => item.kind === "replan_required" && item.status === "open")
1033
+ .map((item) => item.id)
1034
+ : [];
1031
1035
  const reworkFindingIds =
1032
1036
  action.type === "request_rework"
1033
1037
  ? new Set(action.findings.map((item) => item.id))
@@ -1,2 +1,2 @@
1
1
  // Generated by scripts/plugin_versioning.ts from the root package.json.
2
- export const PLUGIN_VERSION = "3.6.4" as const;
2
+ export const PLUGIN_VERSION = "3.6.5" as const;