owlrunkit 0.18.3 → 0.19.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.
Files changed (59) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/README.md +223 -52
  3. package/docs/architecture/OWLCODA_RUNKIT_0184_EXTERNAL_DELIVERY_INTAKE_PRODUCT_DECISION_20260812.md +209 -0
  4. package/docs/architecture/OWLCODA_RUNKIT_0190_CONSOLIDATED_DAILY_USE_PRODUCT_DECISION_20260812.md +97 -0
  5. package/docs/architecture/OWLCODA_RUNKIT_USAGE_MODE_SELECTION_PRODUCT_DECISION_20260811.md +8 -7
  6. package/docs/architecture/OWLCODA_RUN_KIT_CONTRACT_V0_2.md +57 -0
  7. package/integrations/codex/skills/owlcoda-runkit/SKILL.md +133 -77
  8. package/integrations/codex/skills/owlcoda-runkit/assets/templates/config.json +3 -3
  9. package/integrations/codex/skills/owlcoda-runkit/references/contract-v0.2.md +57 -0
  10. package/package.json +1 -1
  11. package/packages/attest/README.md +9 -5
  12. package/packages/attest/consumer-surface-v2.json +263 -0
  13. package/packages/attest/distribution-manifest-v1.json +14 -1
  14. package/packages/attest/schemas/quick-verification-receipt-v3.schema.json +244 -0
  15. package/packages/attest/schemas/workspace-snapshot-v2.schema.json +114 -0
  16. package/packages/attest/src/dependency-environment.mjs +128 -0
  17. package/packages/attest/src/index.mjs +1 -0
  18. package/packages/attest/src/quick-receipt-contract.mjs +205 -12
  19. package/packages/attest/src/quick.mjs +45 -4
  20. package/packages/attest/src/workspace-snapshot.mjs +143 -7
  21. package/registry-release-policy-v1.json +5 -5
  22. package/sbom.spdx.json +5 -5
  23. package/scripts/runkit-contract/assurance-router.mjs +25 -6
  24. package/scripts/runkit-contract/core-contract.mjs +29 -1
  25. package/scripts/runkit-contract/efficiency-metrics.mjs +559 -0
  26. package/scripts/runkit-contract/evidence-store.mjs +130 -0
  27. package/scripts/runkit-contract/onboarding-bootstrap.mjs +685 -0
  28. package/scripts/runkit-contract/onboarding-doctor.mjs +3 -2
  29. package/scripts/runkit-contract/outcome-presentation.mjs +171 -0
  30. package/scripts/runkit-contract/profile-onboarding.mjs +285 -17
  31. package/scripts/runkit-contract/project-cli-resolver.mjs +29 -5
  32. package/scripts/runkit-contract/project-external-delivery.mjs +707 -0
  33. package/scripts/runkit-contract/quick-evidence-reuse.mjs +590 -0
  34. package/scripts/runkit-contract/quick-isolation.mjs +314 -0
  35. package/scripts/runkit-contract/quick-metrics.mjs +51 -3
  36. package/scripts/runkit-contract/quick-receipt.mjs +31 -1
  37. package/scripts/runkit-contract/quick-verify.mjs +217 -26
  38. package/scripts/runkit-contract/quick-workspace-snapshot.mjs +1 -0
  39. package/scripts/runkit-contract/runkit-bootstrap.mjs +7 -1
  40. package/scripts/runkit-contract/runkit-cli.mjs +602 -152
  41. package/scripts/runkit-contract/runtime-transition.mjs +478 -0
  42. package/scripts/runkit-contract/schemas/assurance-route-v2.schema.json +26 -0
  43. package/scripts/runkit-contract/schemas/efficiency-event-v1.schema.json +56 -0
  44. package/scripts/runkit-contract/schemas/efficiency-metrics-v1.schema.json +220 -0
  45. package/scripts/runkit-contract/schemas/external-runtime-report-v1.schema.json +125 -0
  46. package/scripts/runkit-contract/schemas/onboarding-bootstrap-plan-v1.schema.json +168 -0
  47. package/scripts/runkit-contract/schemas/onboarding-bootstrap-receipt-v1.schema.json +130 -0
  48. package/scripts/runkit-contract/schemas/outcome-summary-v1.schema.json +83 -0
  49. package/scripts/runkit-contract/schemas/profile-detection-v3.schema.json +170 -0
  50. package/scripts/runkit-contract/schemas/quick-evidence-reuse-plan-v1.schema.json +59 -0
  51. package/scripts/runkit-contract/schemas/quick-evidence-reuse-receipt-v1.schema.json +79 -0
  52. package/scripts/runkit-contract/schemas/runtime-transition-receipt-v1.schema.json +185 -0
  53. package/scripts/runkit-contract/schemas/team-project-event-v3.schema.json +146 -0
  54. package/scripts/runkit-contract/schemas/team-project-event-v4.schema.json +142 -0
  55. package/scripts/runkit-contract/schemas/team-project-status-v2.schema.json +85 -0
  56. package/scripts/runkit-contract/schemas/usage-mode-recommendation-v2.schema.json +136 -0
  57. package/scripts/runkit-contract/team-project.mjs +1172 -22
  58. package/scripts/runkit-contract/usage-mode.mjs +28 -7
  59. package/scripts/runkit-contract/verification-envelope.mjs +15 -18
@@ -12,6 +12,11 @@ import path from "node:path";
12
12
 
13
13
  import { validateLeaseOwnedPaths } from "./core-contract.mjs";
14
14
  import { inspectProjectControlState } from "./project-control-state.mjs";
15
+ import { canonicalSourceFingerprint } from "./source-fingerprint.mjs";
16
+ import {
17
+ quickSnapshotFingerprintValid,
18
+ validWorkspaceSnapshotShape,
19
+ } from "../../packages/attest/src/quick-receipt-contract.mjs";
15
20
  import {
16
21
  assertAllowedKeys,
17
22
  isRecord,
@@ -24,7 +29,10 @@ const PROJECT_ROOT = ".owlcoda/runkit/project";
24
29
  const DEFINITION_SCHEMA = "OwlCodaRunKitTeamProjectDefinitionV1";
25
30
  const EVENT_SCHEMA = "OwlCodaRunKitTeamProjectEventV1";
26
31
  const EVENT_SCHEMA_V2 = "OwlCodaRunKitTeamProjectEventV2";
27
- const STATUS_SCHEMA = "OwlCodaRunKitTeamProjectStatusV1";
32
+ const EVENT_SCHEMA_V3 = "OwlCodaRunKitTeamProjectEventV3";
33
+ const EVENT_SCHEMA_V4 = "OwlCodaRunKitTeamProjectEventV4";
34
+ const STATUS_SCHEMA_V1 = "OwlCodaRunKitTeamProjectStatusV1";
35
+ const STATUS_SCHEMA_V2 = "OwlCodaRunKitTeamProjectStatusV2";
28
36
 
29
37
  const DEFINITION_KEYS = [
30
38
  "schemaVersion",
@@ -92,6 +100,30 @@ const EVENT_KEYS = Object.freeze({
92
100
  "sourceRef", "rights", "inputRef", "outputRef", "decisionRef",
93
101
  "verificationRefs", "outcomeRef", "version",
94
102
  ],
103
+ source_delivery_imported: [
104
+ "schemaVersion", "eventId", "type", "occurredAt", "deliveryId",
105
+ "projectId", "projectDefinitionSha256", "assignmentId", "workItemId",
106
+ "producerId", "targetWorkspaceRef",
107
+ "targetHead", "entrySnapshotRef", "entrySnapshotSha256",
108
+ "deliveryPacketSha256", "deliveryCandidateFingerprint",
109
+ "workspaceSnapshotFingerprint", "overlayStatus", "candidateFiles",
110
+ "changedPaths", "toolCachePaths", "dependencyEnvironmentPaths",
111
+ "unchangedPacketPaths", "outOfScopeSourcePaths", "targetSnapshot",
112
+ "proven", "notProven", "targetWritePerformed", "authorizationGranted",
113
+ ],
114
+ upstream_reopened: [
115
+ "schemaVersion", "eventId", "type", "occurredAt", "reopenId",
116
+ "assignmentId", "previousAssignmentId", "previousCheckpointEventId",
117
+ "workItemId", "triggerWorkItemId", "fromAgentId", "reviewerAgentId",
118
+ "toAgentId", "reason", "evidenceRefs", "nextAction",
119
+ ],
120
+ responsibility_transferred: [
121
+ "schemaVersion", "eventId", "type", "occurredAt", "transferId",
122
+ "assignmentId", "previousAssignmentId", "checkpointEventId",
123
+ "handoffEventId", "assignmentEventId", "workItemId", "fromAgentId",
124
+ "toAgentId", "state", "summary", "completedUnits", "evidenceRefs",
125
+ "blockerRefs", "decisionRefs", "nextAction", "sourceFingerprint",
126
+ ],
95
127
  });
96
128
 
97
129
  function canonical(value) {
@@ -122,6 +154,26 @@ function nullableString(value, label) {
122
154
  return nonEmptyString(value, label);
123
155
  }
124
156
 
157
+ function sha256Ref(value, label) {
158
+ if (typeof value !== "string" || !/^sha256:[a-f0-9]{64}$/u.test(value)) {
159
+ throw new Error(`${label} must be a canonical SHA-256 reference.`);
160
+ }
161
+ return value;
162
+ }
163
+
164
+ function safeSourcePath(value, label) {
165
+ if (typeof value !== "string"
166
+ || value.length === 0
167
+ || path.isAbsolute(value)
168
+ || path.win32.parse(value).root
169
+ || value.includes("\\")
170
+ || value.includes("\0")
171
+ || value.split("/").some(segment => !segment || segment === "." || segment === "..")) {
172
+ throw new Error(`${label} must be a safe repository-relative path.`);
173
+ }
174
+ return value;
175
+ }
176
+
125
177
  function stringArray(value, label, { allowEmpty = true } = {}) {
126
178
  if (!Array.isArray(value)
127
179
  || (!allowEmpty && value.length === 0)
@@ -167,6 +219,30 @@ function withProjectLock(root, operation) {
167
219
  }
168
220
  }
169
221
 
222
+ export function teamProjectDefinitionBindingV1(definition) {
223
+ const normalized = validateDefinition(definition);
224
+ return {
225
+ projectId: normalized.projectId,
226
+ projectDefinitionSha256: `sha256:${sha256(canonical(normalized))}`,
227
+ };
228
+ }
229
+
230
+ export function withTeamProjectControlLockV1({ workspaceRoot, operation }) {
231
+ if (typeof operation !== "function") {
232
+ throw new Error("Team project control operation must be a function.");
233
+ }
234
+ const loaded = loadProject(workspaceRoot);
235
+ return withProjectLock(loaded.projectRoot, () => {
236
+ const refreshed = loadProject(workspaceRoot);
237
+ return operation({
238
+ workspaceRoot: refreshed.root,
239
+ projectRoot: refreshed.projectRoot,
240
+ definition: structuredClone(refreshed.definition),
241
+ definitionBinding: teamProjectDefinitionBindingV1(refreshed.definition),
242
+ });
243
+ });
244
+ }
245
+
170
246
  function uniqueIds(rows, label) {
171
247
  const ids = rows.map(row => safeIdentifier(row.id, `${label} id`));
172
248
  if (new Set(ids).size !== ids.length) throw new Error(`${label} ids must be unique.`);
@@ -381,6 +457,22 @@ function latestEventFact(left, right) {
381
457
  return compareEventFacts(left, right) >= 0 ? left : right;
382
458
  }
383
459
 
460
+ function downstreamWorkItemIds(definition, workItemId, { includeRoot = false } = {}) {
461
+ const children = new Map(definition.workItems.map(row => [row.id, []]));
462
+ for (const row of definition.workItems) {
463
+ for (const dependency of row.dependencies) children.get(dependency).push(row.id);
464
+ }
465
+ const visited = new Set(includeRoot ? [workItemId] : []);
466
+ const queue = [...children.get(workItemId)];
467
+ while (queue.length > 0) {
468
+ const current = queue.shift();
469
+ if (visited.has(current)) continue;
470
+ visited.add(current);
471
+ queue.push(...children.get(current));
472
+ }
473
+ return [...visited].sort(compareIdentifiers);
474
+ }
475
+
384
476
  function pendingHandoffProjection(handoff) {
385
477
  return handoff
386
478
  ? {
@@ -420,6 +512,140 @@ function reworkProjection(rework, attempt) {
420
512
  : null;
421
513
  }
422
514
 
515
+ function sourceDeliveryProjection(delivery) {
516
+ return delivery
517
+ ? {
518
+ projectId: delivery.projectId,
519
+ projectDefinitionSha256: delivery.projectDefinitionSha256,
520
+ deliveryId: delivery.deliveryId,
521
+ workItemId: delivery.workItemId,
522
+ assignmentId: delivery.assignmentId,
523
+ producerId: delivery.producerId,
524
+ targetWorkspaceRef: delivery.targetWorkspaceRef,
525
+ targetHead: delivery.targetHead,
526
+ entrySnapshotRef: delivery.entrySnapshotRef,
527
+ entrySnapshotSha256: delivery.entrySnapshotSha256,
528
+ deliveryPacketSha256: delivery.deliveryPacketSha256,
529
+ deliveryCandidateFingerprint: delivery.deliveryCandidateFingerprint,
530
+ workspaceSnapshotFingerprint: delivery.workspaceSnapshotFingerprint,
531
+ overlayStatus: delivery.overlayStatus,
532
+ candidateFiles: delivery.candidateFiles.map(row => ({ ...row })),
533
+ candidatePaths: delivery.candidateFiles.map(row => row.path),
534
+ changedPaths: [...delivery.changedPaths],
535
+ toolCachePaths: [...delivery.toolCachePaths],
536
+ dependencyEnvironmentPaths: [...delivery.dependencyEnvironmentPaths],
537
+ unchangedPacketPaths: [...delivery.unchangedPacketPaths],
538
+ proven: [...delivery.proven],
539
+ notProven: [...delivery.notProven],
540
+ occurredAt: delivery.occurredAt,
541
+ truthRef: delivery.ref,
542
+ targetWritePerformed: false,
543
+ authorizationGranted: false,
544
+ }
545
+ : null;
546
+ }
547
+
548
+ function sourceDeliveryDelta(deliveries) {
549
+ const current = deliveries.at(-1) ?? null;
550
+ if (!current) return null;
551
+ const previous = deliveries.at(-2) ?? null;
552
+ if (!previous) {
553
+ return {
554
+ status: "first_delivery",
555
+ currentDeliveryId: current.deliveryId,
556
+ previousDeliveryId: null,
557
+ addedPaths: current.candidateFiles.map(row => row.path),
558
+ changedPaths: [],
559
+ removedPaths: [],
560
+ unchangedPaths: [],
561
+ };
562
+ }
563
+ const currentFiles = new Map(current.candidateFiles.map(row => [row.path, row.sha256]));
564
+ const previousFiles = new Map(previous.candidateFiles.map(row => [row.path, row.sha256]));
565
+ const allPaths = uniqueSorted([...currentFiles.keys(), ...previousFiles.keys()]);
566
+ const addedPaths = allPaths.filter(filePath => (
567
+ currentFiles.has(filePath) && !previousFiles.has(filePath)
568
+ ));
569
+ const removedPaths = allPaths.filter(filePath => (
570
+ !currentFiles.has(filePath) && previousFiles.has(filePath)
571
+ ));
572
+ const changedPaths = allPaths.filter(filePath => (
573
+ currentFiles.has(filePath)
574
+ && previousFiles.has(filePath)
575
+ && currentFiles.get(filePath) !== previousFiles.get(filePath)
576
+ ));
577
+ const unchangedPaths = allPaths.filter(filePath => (
578
+ currentFiles.has(filePath)
579
+ && previousFiles.has(filePath)
580
+ && currentFiles.get(filePath) === previousFiles.get(filePath)
581
+ ));
582
+ return {
583
+ status: addedPaths.length || removedPaths.length || changedPaths.length
584
+ ? "changed"
585
+ : "unchanged",
586
+ currentDeliveryId: current.deliveryId,
587
+ previousDeliveryId: previous.deliveryId,
588
+ addedPaths,
589
+ changedPaths,
590
+ removedPaths,
591
+ unchangedPaths,
592
+ };
593
+ }
594
+
595
+ function staleHandoffsForTakeover({ loaded, status, agentId, responsibilities }) {
596
+ const responsibilityIds = new Set(responsibilities.map(row => row.workItemId));
597
+ const pendingEventIds = new Set(status.workItems
598
+ .map(row => row.pendingHandoff?.eventId)
599
+ .filter(Boolean));
600
+ return loaded.events
601
+ .filter(({ event }) => (
602
+ event.type === "handoff_recorded"
603
+ && !pendingEventIds.has(event.eventId)
604
+ && (responsibilityIds.has(event.workItemId)
605
+ || event.fromAgentId === agentId
606
+ || event.toAgentId === agentId)
607
+ ))
608
+ .map(({ event, ref }) => {
609
+ const laterFacts = loaded.events.filter(row => (
610
+ row.event.workItemId === event.workItemId
611
+ && compareEventFacts(row.event, event) > 0
612
+ && [
613
+ "agent_assigned",
614
+ "checkpoint_recorded",
615
+ "handoff_recorded",
616
+ "rework_returned",
617
+ "source_delivery_imported",
618
+ "upstream_reopened",
619
+ "responsibility_transferred",
620
+ ].includes(row.event.type)
621
+ ));
622
+ let reason = "superseded_by_current_truth";
623
+ if (laterFacts.some(row => row.event.type === "handoff_recorded")) {
624
+ reason = "redirected_by_later_handoff";
625
+ } else if (laterFacts.some(row => (
626
+ row.event.type === "agent_assigned"
627
+ || row.event.type === "rework_returned"
628
+ || row.event.type === "upstream_reopened"
629
+ || row.event.type === "responsibility_transferred"
630
+ ))) {
631
+ reason = "superseded_by_assignment";
632
+ } else if (laterFacts.length > 0) {
633
+ reason = "superseded_by_later_work_fact";
634
+ }
635
+ return {
636
+ eventId: event.eventId,
637
+ truthRef: ref,
638
+ assignmentId: event.assignmentId,
639
+ workItemId: event.workItemId,
640
+ fromAgentId: event.fromAgentId,
641
+ toAgentId: event.toAgentId,
642
+ occurredAt: event.occurredAt,
643
+ reason,
644
+ };
645
+ })
646
+ .sort((left, right) => compareIdentifiers(left.eventId, right.eventId));
647
+ }
648
+
423
649
  function validateTimestamp(value) {
424
650
  if (typeof value !== "string"
425
651
  || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/u.test(value)
@@ -434,14 +660,30 @@ function validateEventShape(value, definition) {
434
660
  throw new Error("Team project event type is invalid.");
435
661
  }
436
662
  assertAllowedKeys(value, `Team project event ${value.eventId ?? "unknown"}`, EVENT_KEYS[value.type]);
437
- if (![EVENT_SCHEMA, EVENT_SCHEMA_V2].includes(value.schemaVersion)) {
663
+ if (![EVENT_SCHEMA, EVENT_SCHEMA_V2, EVENT_SCHEMA_V3, EVENT_SCHEMA_V4]
664
+ .includes(value.schemaVersion)) {
438
665
  throw new Error(
439
- `Team project event schemaVersion must be ${EVENT_SCHEMA} or ${EVENT_SCHEMA_V2}.`,
666
+ `Team project event schemaVersion must be ${EVENT_SCHEMA}, ${EVENT_SCHEMA_V2}, ${EVENT_SCHEMA_V3}, or ${EVENT_SCHEMA_V4}.`,
440
667
  );
441
668
  }
442
669
  if (value.type === "rework_returned" && value.schemaVersion !== EVENT_SCHEMA_V2) {
443
670
  throw new Error(`rework_returned requires schemaVersion ${EVENT_SCHEMA_V2}.`);
444
671
  }
672
+ if (value.type === "source_delivery_imported" && value.schemaVersion !== EVENT_SCHEMA_V3) {
673
+ throw new Error(`source_delivery_imported requires schemaVersion ${EVENT_SCHEMA_V3}.`);
674
+ }
675
+ if (value.schemaVersion === EVENT_SCHEMA_V3 && value.type !== "source_delivery_imported") {
676
+ throw new Error(`${EVENT_SCHEMA_V3} is reserved for source_delivery_imported events.`);
677
+ }
678
+ const eventV4Types = new Set(["upstream_reopened", "responsibility_transferred"]);
679
+ if (eventV4Types.has(value.type) && value.schemaVersion !== EVENT_SCHEMA_V4) {
680
+ throw new Error(`${value.type} requires schemaVersion ${EVENT_SCHEMA_V4}.`);
681
+ }
682
+ if (value.schemaVersion === EVENT_SCHEMA_V4 && !eventV4Types.has(value.type)) {
683
+ throw new Error(
684
+ `${EVENT_SCHEMA_V4} is reserved for upstream_reopened and responsibility_transferred events.`,
685
+ );
686
+ }
445
687
  safeIdentifier(value.eventId, "eventId");
446
688
  validateTimestamp(value.occurredAt);
447
689
  const workItemIds = new Set(definition.workItems.map(row => row.id));
@@ -455,6 +697,9 @@ function validateEventShape(value, definition) {
455
697
  "checkpoint_recorded",
456
698
  "handoff_recorded",
457
699
  "rework_returned",
700
+ "source_delivery_imported",
701
+ "upstream_reopened",
702
+ "responsibility_transferred",
458
703
  "verification_deferred",
459
704
  ].includes(value.type)) {
460
705
  assertWorkItem(value.workItemId);
@@ -464,6 +709,9 @@ function validateEventShape(value, definition) {
464
709
  "checkpoint_recorded",
465
710
  "handoff_recorded",
466
711
  "rework_returned",
712
+ "source_delivery_imported",
713
+ "upstream_reopened",
714
+ "responsibility_transferred",
467
715
  ].includes(value.type)) {
468
716
  safeIdentifier(value.assignmentId, "assignmentId");
469
717
  }
@@ -582,6 +830,138 @@ function validateEventShape(value, definition) {
582
830
  stringArray(value.verificationRefs, "Data candidate verificationRefs");
583
831
  nullableString(value.outcomeRef, "Data candidate outcomeRef");
584
832
  nonEmptyString(value.version, "Data candidate version");
833
+ } else if (value.type === "source_delivery_imported") {
834
+ safeIdentifier(value.projectId, "External delivery projectId");
835
+ sha256Ref(value.projectDefinitionSha256, "External delivery projectDefinitionSha256");
836
+ const definitionBinding = teamProjectDefinitionBindingV1(definition);
837
+ if (value.projectId !== definitionBinding.projectId
838
+ || value.projectDefinitionSha256 !== definitionBinding.projectDefinitionSha256) {
839
+ throw new Error("External delivery project identity does not match current project truth.");
840
+ }
841
+ safeIdentifier(value.deliveryId, "deliveryId");
842
+ safeIdentifier(value.producerId, "producerId");
843
+ nonEmptyString(value.targetWorkspaceRef, "External delivery targetWorkspaceRef");
844
+ if (!path.isAbsolute(value.targetWorkspaceRef)) {
845
+ throw new Error("External delivery targetWorkspaceRef must be absolute.");
846
+ }
847
+ if (typeof value.targetHead !== "string"
848
+ || !/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/u.test(value.targetHead)) {
849
+ throw new Error("External delivery targetHead is invalid.");
850
+ }
851
+ if ((value.entrySnapshotRef === null) !== (value.entrySnapshotSha256 === null)) {
852
+ throw new Error("External delivery entry snapshot ref and hash must be supplied together.");
853
+ }
854
+ if (value.entrySnapshotRef !== null) {
855
+ nonEmptyString(value.entrySnapshotRef, "External delivery entrySnapshotRef");
856
+ sha256Ref(value.entrySnapshotSha256, "External delivery entrySnapshotSha256");
857
+ }
858
+ sha256Ref(value.deliveryPacketSha256, "External delivery packet hash");
859
+ if (typeof value.deliveryCandidateFingerprint !== "string"
860
+ || !/^[a-f0-9]{64}$/u.test(value.deliveryCandidateFingerprint)) {
861
+ throw new Error("External delivery candidate fingerprint is invalid.");
862
+ }
863
+ sha256Ref(value.workspaceSnapshotFingerprint, "External delivery workspace snapshot fingerprint");
864
+ if (!["candidate_only", "attested", "attested_with_non_source_changes"].includes(value.overlayStatus)) {
865
+ throw new Error("External delivery overlayStatus is invalid.");
866
+ }
867
+ if (!Array.isArray(value.candidateFiles) || value.candidateFiles.length === 0) {
868
+ throw new Error("External delivery candidateFiles must contain at least one file.");
869
+ }
870
+ const candidateFiles = value.candidateFiles.map((row) => {
871
+ assertAllowedKeys(row, "External delivery candidate file", ["path", "sha256"]);
872
+ const filePath = safeSourcePath(row.path, "External delivery candidate file path");
873
+ if (typeof row.sha256 !== "string" || !/^[a-f0-9]{64}$/u.test(row.sha256)) {
874
+ throw new Error(`External delivery candidate file hash is invalid: ${filePath}.`);
875
+ }
876
+ return { path: filePath, sha256: row.sha256 };
877
+ });
878
+ if (new Set(candidateFiles.map(row => row.path)).size !== candidateFiles.length
879
+ || canonical([...candidateFiles].sort((left, right) => (
880
+ compareIdentifiers(left.path, right.path)
881
+ )))
882
+ !== canonical(candidateFiles)) {
883
+ throw new Error("External delivery candidateFiles must be uniquely sorted by path.");
884
+ }
885
+ if (canonicalSourceFingerprint(Object.fromEntries(
886
+ candidateFiles.map(row => [row.path, row.sha256]),
887
+ )) !== value.deliveryCandidateFingerprint) {
888
+ throw new Error("External delivery candidate fingerprint does not match its file map.");
889
+ }
890
+ for (const [field, label] of [
891
+ ["changedPaths", "External delivery changedPaths"],
892
+ ["toolCachePaths", "External delivery toolCachePaths"],
893
+ ["dependencyEnvironmentPaths", "External delivery dependencyEnvironmentPaths"],
894
+ ["unchangedPacketPaths", "External delivery unchangedPacketPaths"],
895
+ ["outOfScopeSourcePaths", "External delivery outOfScopeSourcePaths"],
896
+ ]) {
897
+ const paths = stringArray(value[field], label);
898
+ for (const filePath of paths) safeSourcePath(filePath, label);
899
+ if (canonical([...paths].sort()) !== canonical(paths)) {
900
+ throw new Error(`${label} must be sorted.`);
901
+ }
902
+ }
903
+ if (value.outOfScopeSourcePaths.length !== 0) {
904
+ throw new Error("An imported external delivery cannot contain out-of-scope source paths.");
905
+ }
906
+ stringArray(value.proven, "External delivery proven statements", { allowEmpty: false });
907
+ stringArray(value.notProven, "External delivery notProven statements", { allowEmpty: false });
908
+ if (!validWorkspaceSnapshotShape(value.targetSnapshot)
909
+ || !quickSnapshotFingerprintValid(value.targetSnapshot)
910
+ || value.targetSnapshot.sourceFingerprint !== value.workspaceSnapshotFingerprint) {
911
+ throw new Error("External delivery targetSnapshot identity is invalid.");
912
+ }
913
+ if (value.targetWritePerformed !== false || value.authorizationGranted !== false) {
914
+ throw new Error("External delivery cannot grant authority or write the target workspace.");
915
+ }
916
+ } else if (value.type === "upstream_reopened") {
917
+ safeIdentifier(value.reopenId, "reopenId");
918
+ safeIdentifier(value.previousAssignmentId, "previousAssignmentId");
919
+ safeIdentifier(value.previousCheckpointEventId, "previousCheckpointEventId");
920
+ assertWorkItem(value.triggerWorkItemId);
921
+ safeIdentifier(value.fromAgentId, "fromAgentId");
922
+ safeIdentifier(value.reviewerAgentId, "reviewerAgentId");
923
+ safeIdentifier(value.toAgentId, "toAgentId");
924
+ nonEmptyString(value.reason, "Upstream reopen reason");
925
+ stringArray(value.evidenceRefs, "Upstream reopen evidenceRefs", { allowEmpty: false });
926
+ nonEmptyString(value.nextAction, "Upstream reopen nextAction");
927
+ } else if (value.type === "responsibility_transferred") {
928
+ safeIdentifier(value.transferId, "transferId");
929
+ safeIdentifier(value.previousAssignmentId, "previousAssignmentId");
930
+ for (const [field, label] of [
931
+ ["checkpointEventId", "checkpointEventId"],
932
+ ["handoffEventId", "handoffEventId"],
933
+ ["assignmentEventId", "assignmentEventId"],
934
+ ]) safeIdentifier(value[field], label);
935
+ if (new Set([
936
+ value.eventId,
937
+ value.checkpointEventId,
938
+ value.handoffEventId,
939
+ value.assignmentEventId,
940
+ ]).size !== 4) {
941
+ throw new Error("Responsibility transfer fact IDs must be distinct.");
942
+ }
943
+ safeIdentifier(value.fromAgentId, "fromAgentId");
944
+ safeIdentifier(value.toAgentId, "toAgentId");
945
+ if (value.fromAgentId === value.toAgentId) {
946
+ throw new Error("Responsibility transfer Agents must differ.");
947
+ }
948
+ if (!["active", "waiting_dependency", "waiting_decision", "verifying", "ready_to_integrate"]
949
+ .includes(value.state)) {
950
+ throw new Error("Responsibility transfer state must be non-terminal.");
951
+ }
952
+ nonEmptyString(value.summary, "Responsibility transfer summary");
953
+ stringArray(value.evidenceRefs, "Responsibility transfer evidenceRefs", { allowEmpty: false });
954
+ stringArray(value.blockerRefs, "Responsibility transfer blockerRefs");
955
+ stringArray(value.decisionRefs, "Responsibility transfer decisionRefs");
956
+ nonEmptyString(value.nextAction, "Responsibility transfer nextAction");
957
+ if (value.completedUnits !== undefined
958
+ && (!Number.isSafeInteger(value.completedUnits) || value.completedUnits < 0)) {
959
+ throw new Error("Responsibility transfer completedUnits must be a non-negative integer.");
960
+ }
961
+ if (value.sourceFingerprint !== undefined
962
+ && !/^(?:sha256:)?[a-f0-9]{64}$/u.test(value.sourceFingerprint)) {
963
+ throw new Error("Responsibility transfer sourceFingerprint is invalid.");
964
+ }
585
965
  }
586
966
  return structuredClone(value);
587
967
  }
@@ -592,13 +972,20 @@ function projectEvents(definition, records) {
592
972
  const checkpoints = new Map();
593
973
  const handoffs = new Map();
594
974
  const reworks = new Map();
975
+ const reopens = new Map();
976
+ const transfers = new Map();
595
977
  const decisions = new Map();
596
978
  const gatePasses = new Map();
597
979
  const verifications = new Map();
598
980
  const dataCandidates = [];
981
+ const sourceDeliveries = [];
599
982
  const seenAssignments = new Set();
600
983
  const seenReworks = new Set();
984
+ const seenReopens = new Set();
985
+ const seenTransfers = new Set();
601
986
  const seenDecisions = new Set();
987
+ const seenDeliveries = new Set();
988
+ const seenFactIds = new Set(records.map(row => row.event.eventId));
602
989
 
603
990
  for (const { event, ref } of records) {
604
991
  validateEventShape(event, definition);
@@ -692,6 +1079,185 @@ function projectEvents(definition, records) {
692
1079
  throw new Error(`Duplicate data candidate ${event.candidateId}.`);
693
1080
  }
694
1081
  dataCandidates.push({ ...event, ref });
1082
+ } else if (event.type === "source_delivery_imported") {
1083
+ if (seenDeliveries.has(event.deliveryId)) {
1084
+ throw new Error(`Duplicate external source delivery ${event.deliveryId}.`);
1085
+ }
1086
+ const current = assignments.get(event.workItemId) ?? null;
1087
+ if (current?.assignmentId !== event.assignmentId) {
1088
+ throw new Error(
1089
+ `External source delivery ${event.deliveryId} does not bind the current assignment.`,
1090
+ );
1091
+ }
1092
+ const currentCheckpoint = checkpoints.get(event.workItemId) ?? null;
1093
+ if (currentCheckpoint?.assignmentId === current.assignmentId
1094
+ && ["completed", "failed"].includes(currentCheckpoint.state)) {
1095
+ throw new Error(
1096
+ `External source delivery ${event.deliveryId} cannot attach to terminal WorkItem truth.`,
1097
+ );
1098
+ }
1099
+ seenDeliveries.add(event.deliveryId);
1100
+ sourceDeliveries.push({ ...event, ref });
1101
+ } else if (event.type === "upstream_reopened") {
1102
+ if (seenReopens.has(event.reopenId)) {
1103
+ throw new Error(`Duplicate upstream reopen ${event.reopenId}.`);
1104
+ }
1105
+ if (seenAssignments.has(event.assignmentId)) {
1106
+ throw new Error(`Duplicate assignmentId ${event.assignmentId}.`);
1107
+ }
1108
+ const current = assignments.get(event.workItemId) ?? null;
1109
+ const checkpoint = checkpoints.get(event.workItemId) ?? null;
1110
+ const affectedIds = downstreamWorkItemIds(
1111
+ definition,
1112
+ event.workItemId,
1113
+ { includeRoot: true },
1114
+ );
1115
+ if (!current
1116
+ || current.assignmentId !== event.previousAssignmentId
1117
+ || current.agentId !== event.fromAgentId
1118
+ || checkpoint?.eventId !== event.previousCheckpointEventId
1119
+ || checkpoint.assignmentId !== current.assignmentId
1120
+ || checkpoint.state !== "completed") {
1121
+ throw new Error(
1122
+ `Upstream reopen ${event.reopenId} does not bind the current completed WorkItem.`,
1123
+ );
1124
+ }
1125
+ if (!downstreamWorkItemIds(definition, event.workItemId)
1126
+ .includes(event.triggerWorkItemId)) {
1127
+ throw new Error(
1128
+ `Upstream reopen ${event.reopenId} trigger must be a transitive downstream WorkItem.`,
1129
+ );
1130
+ }
1131
+ const triggerAssignment = assignments.get(event.triggerWorkItemId) ?? null;
1132
+ if (triggerAssignment?.agentId !== event.reviewerAgentId) {
1133
+ throw new Error(
1134
+ `Upstream reopen ${event.reopenId} reviewer must own the trigger WorkItem.`,
1135
+ );
1136
+ }
1137
+ const invalidatedGate = definition.integrationGates.find(gate => (
1138
+ gatePasses.has(gate.id)
1139
+ && gate.requiredWorkItemIds.some(id => affectedIds.includes(id))
1140
+ ));
1141
+ if (invalidatedGate) {
1142
+ throw new Error(
1143
+ `Upstream reopen cannot invalidate passed integration gate ${invalidatedGate.id}.`,
1144
+ );
1145
+ }
1146
+ seenReopens.add(event.reopenId);
1147
+ seenAssignments.add(event.assignmentId);
1148
+ const assignment = {
1149
+ schemaVersion: EVENT_SCHEMA,
1150
+ eventId: `assignment-${event.assignmentId}`,
1151
+ type: "agent_assigned",
1152
+ occurredAt: event.occurredAt,
1153
+ assignmentId: event.assignmentId,
1154
+ supersedesAssignmentId: event.previousAssignmentId,
1155
+ workItemId: event.workItemId,
1156
+ agentId: event.toAgentId,
1157
+ ref: `${ref}#assignment`,
1158
+ };
1159
+ assignments.set(event.workItemId, assignment);
1160
+ assignmentHistory.get(event.workItemId).push(assignment);
1161
+ reopens.set(event.workItemId, { ...event, affectedWorkItemIds: affectedIds, ref });
1162
+ } else if (event.type === "responsibility_transferred") {
1163
+ if (seenTransfers.has(event.transferId)) {
1164
+ throw new Error(`Duplicate responsibility transfer ${event.transferId}.`);
1165
+ }
1166
+ if (seenAssignments.has(event.assignmentId)) {
1167
+ throw new Error(`Duplicate assignmentId ${event.assignmentId}.`);
1168
+ }
1169
+ for (const factId of [
1170
+ event.checkpointEventId,
1171
+ event.handoffEventId,
1172
+ event.assignmentEventId,
1173
+ ]) {
1174
+ if (seenFactIds.has(factId)) {
1175
+ throw new Error(`Duplicate responsibility transfer fact ID ${factId}.`);
1176
+ }
1177
+ }
1178
+ const current = assignments.get(event.workItemId) ?? null;
1179
+ const currentCheckpoint = checkpoints.get(event.workItemId) ?? null;
1180
+ const currentHandoff = handoffs.get(event.workItemId) ?? null;
1181
+ const latestCurrentFact = [
1182
+ currentCheckpoint,
1183
+ currentHandoff,
1184
+ reworks.get(event.workItemId) ?? null,
1185
+ reopens.get(event.workItemId) ?? null,
1186
+ transfers.get(event.workItemId) ?? null,
1187
+ sourceDeliveries.filter(row => row.workItemId === event.workItemId).at(-1) ?? null,
1188
+ ].reduce((latest, fact) => latestEventFact(latest, fact), null);
1189
+ if (!current
1190
+ || current.assignmentId !== event.previousAssignmentId
1191
+ || current.agentId !== event.fromAgentId) {
1192
+ throw new Error(
1193
+ `Responsibility transfer ${event.transferId} does not bind the current assignment.`,
1194
+ );
1195
+ }
1196
+ if (currentCheckpoint?.assignmentId === current.assignmentId
1197
+ && ["completed", "failed"].includes(currentCheckpoint.state)) {
1198
+ throw new Error("Responsibility transfer cannot target a terminal WorkItem.");
1199
+ }
1200
+ if (currentHandoff?.assignmentId === current.assignmentId
1201
+ && currentHandoff.fromAgentId === current.agentId
1202
+ && latestCurrentFact?.eventId === currentHandoff.eventId) {
1203
+ throw new Error("Responsibility transfer cannot replace a pending handoff.");
1204
+ }
1205
+ const checkpoint = {
1206
+ schemaVersion: EVENT_SCHEMA,
1207
+ eventId: event.checkpointEventId,
1208
+ type: "checkpoint_recorded",
1209
+ occurredAt: event.occurredAt,
1210
+ assignmentId: event.previousAssignmentId,
1211
+ workItemId: event.workItemId,
1212
+ state: event.state,
1213
+ summary: event.summary,
1214
+ ...(event.completedUnits === undefined ? {} : { completedUnits: event.completedUnits }),
1215
+ evidenceRefs: [...event.evidenceRefs],
1216
+ blockerRefs: [...event.blockerRefs],
1217
+ decisionRefs: [...event.decisionRefs],
1218
+ nextAction: event.nextAction,
1219
+ ...(event.sourceFingerprint === undefined
1220
+ ? {}
1221
+ : { sourceFingerprint: event.sourceFingerprint }),
1222
+ ref: `${ref}#checkpoint`,
1223
+ };
1224
+ const handoff = {
1225
+ schemaVersion: EVENT_SCHEMA,
1226
+ eventId: event.handoffEventId,
1227
+ type: "handoff_recorded",
1228
+ occurredAt: event.occurredAt,
1229
+ assignmentId: event.previousAssignmentId,
1230
+ workItemId: event.workItemId,
1231
+ fromAgentId: event.fromAgentId,
1232
+ toAgentId: event.toAgentId,
1233
+ summary: event.summary,
1234
+ evidenceRefs: [...event.evidenceRefs],
1235
+ nextAction: event.nextAction,
1236
+ ref: `${ref}#handoff`,
1237
+ };
1238
+ const assignment = {
1239
+ schemaVersion: EVENT_SCHEMA,
1240
+ eventId: event.assignmentEventId,
1241
+ type: "agent_assigned",
1242
+ occurredAt: event.occurredAt,
1243
+ assignmentId: event.assignmentId,
1244
+ supersedesAssignmentId: event.previousAssignmentId,
1245
+ workItemId: event.workItemId,
1246
+ agentId: event.toAgentId,
1247
+ ref: `${ref}#assignment`,
1248
+ };
1249
+ for (const factId of [
1250
+ event.checkpointEventId,
1251
+ event.handoffEventId,
1252
+ event.assignmentEventId,
1253
+ ]) seenFactIds.add(factId);
1254
+ seenTransfers.add(event.transferId);
1255
+ seenAssignments.add(event.assignmentId);
1256
+ checkpoints.set(event.workItemId, checkpoint);
1257
+ handoffs.set(event.workItemId, handoff);
1258
+ assignments.set(event.workItemId, assignment);
1259
+ assignmentHistory.get(event.workItemId).push(assignment);
1260
+ transfers.set(event.workItemId, { ...event, ref });
695
1261
  }
696
1262
  }
697
1263
  return {
@@ -700,10 +1266,13 @@ function projectEvents(definition, records) {
700
1266
  checkpoints,
701
1267
  handoffs,
702
1268
  reworks,
1269
+ reopens,
1270
+ transfers,
703
1271
  decisions,
704
1272
  gatePasses,
705
1273
  verifications,
706
1274
  dataCandidates,
1275
+ sourceDeliveries,
707
1276
  };
708
1277
  }
709
1278
 
@@ -1020,25 +1589,79 @@ function deriveProjectDriverSummary(status) {
1020
1589
  };
1021
1590
  }
1022
1591
 
1023
- function projectStatus(definition, records, workspaceRoot = null) {
1024
- const state = projectEvents(definition, records);
1592
+ function affectingReopensForWorkItem(state, workItemId) {
1593
+ return [...state.reopens.values()]
1594
+ .filter(reopen => reopen.affectedWorkItemIds.includes(workItemId))
1595
+ .sort(compareEventFacts);
1596
+ }
1597
+
1598
+ function invalidatingReopensForWorkItem(state, workItemId) {
1599
+ const checkpoint = state.checkpoints.get(workItemId) ?? null;
1600
+ return affectingReopensForWorkItem(state, workItemId).filter(reopen => (
1601
+ checkpoint?.state !== "completed" || compareEventFacts(checkpoint, reopen) <= 0
1602
+ ));
1603
+ }
1604
+
1605
+ function completedWorkItemIds(definition, state) {
1025
1606
  const completed = new Set();
1026
- for (const [id, checkpoint] of state.checkpoints) {
1027
- if (checkpoint.state === "completed") completed.add(id);
1607
+ let changed = true;
1608
+ while (changed) {
1609
+ changed = false;
1610
+ for (const item of definition.workItems) {
1611
+ if (completed.has(item.id)) continue;
1612
+ const checkpoint = state.checkpoints.get(item.id) ?? null;
1613
+ if (checkpoint?.state !== "completed"
1614
+ || invalidatingReopensForWorkItem(state, item.id).length > 0
1615
+ || item.dependencies.some(id => !completed.has(id))) continue;
1616
+ completed.add(item.id);
1617
+ changed = true;
1618
+ }
1028
1619
  }
1620
+ return completed;
1621
+ }
1622
+
1623
+ function projectStatus(definition, records, workspaceRoot = null) {
1624
+ const state = projectEvents(definition, records);
1625
+ const completed = completedWorkItemIds(definition, state);
1029
1626
  let workItems = definition.workItems.map((item) => {
1030
1627
  const assignment = state.assignments.get(item.id) ?? null;
1031
1628
  const checkpoint = state.checkpoints.get(item.id) ?? null;
1032
1629
  const handoff = state.handoffs.get(item.id) ?? null;
1033
1630
  const rework = state.reworks.get(item.id) ?? null;
1631
+ const sourceDeliveries = state.sourceDeliveries.filter(row => row.workItemId === item.id);
1632
+ const latestSourceDeliveryFact = sourceDeliveries.at(-1) ?? null;
1633
+ const latestSourceDelivery = sourceDeliveryProjection(sourceDeliveries.at(-1) ?? null);
1034
1634
  const currentRework = rework?.assignmentId === assignment?.assignmentId ? rework : null;
1035
- const latestFact = latestEventFact(latestEventFact(checkpoint, handoff), currentRework);
1635
+ const reopenFacts = affectingReopensForWorkItem(state, item.id);
1636
+ const invalidatingReopens = invalidatingReopensForWorkItem(state, item.id);
1637
+ const currentReopen = state.reopens.get(item.id)?.assignmentId === assignment?.assignmentId
1638
+ ? state.reopens.get(item.id)
1639
+ : null;
1640
+ const currentTransfer = state.transfers.get(item.id)?.assignmentId === assignment?.assignmentId
1641
+ ? state.transfers.get(item.id)
1642
+ : null;
1643
+ const effectiveCheckpoint = invalidatingReopens.length === 0 ? checkpoint : null;
1644
+ let latestFact = [effectiveCheckpoint, handoff, currentRework, currentReopen, currentTransfer]
1645
+ .reduce((latest, fact) => latestEventFact(latest, fact), null);
1646
+ if (currentTransfer && latestFact?.occurredAt === currentTransfer.occurredAt) {
1647
+ latestFact = currentTransfer;
1648
+ }
1036
1649
  const activeHandoff = handoff
1037
1650
  && handoff.assignmentId === assignment?.assignmentId
1038
1651
  && handoff.fromAgentId === assignment?.agentId
1039
1652
  ? handoff
1040
1653
  : null;
1041
- const pendingHandoff = activeHandoff && latestFact?.eventId === activeHandoff.eventId
1654
+ const latestHandoffFact = latestEventFact(activeHandoff, latestSourceDeliveryFact);
1655
+ const handoffSupersededByDelivery = activeHandoff
1656
+ && latestFact?.eventId === activeHandoff.eventId
1657
+ && latestHandoffFact?.eventId !== activeHandoff.eventId;
1658
+ const projectedFact = handoffSupersededByDelivery
1659
+ ? [effectiveCheckpoint, currentRework, currentReopen, currentTransfer]
1660
+ .reduce((latest, fact) => latestEventFact(latest, fact), null)
1661
+ : latestFact;
1662
+ const pendingHandoff = activeHandoff
1663
+ && latestFact?.eventId === activeHandoff.eventId
1664
+ && latestHandoffFact?.eventId === activeHandoff.eventId
1042
1665
  ? pendingHandoffProjection(activeHandoff)
1043
1666
  : null;
1044
1667
  const executionTruth = executionTruthFor(workspaceRoot, assignment);
@@ -1046,7 +1669,11 @@ function projectStatus(definition, records, workspaceRoot = null) {
1046
1669
  let status = "planned";
1047
1670
  if (assignment) status = unresolvedDependencies.length > 0 ? "waiting_dependency" : "active";
1048
1671
  if (pendingHandoff) status = "planned";
1049
- if (latestFact?.type === "checkpoint_recorded") status = checkpoint.state;
1672
+ if (projectedFact?.type === "checkpoint_recorded") status = effectiveCheckpoint.state;
1673
+ if (projectedFact?.type === "responsibility_transferred") status = projectedFact.state;
1674
+ if (projectedFact?.type === "upstream_reopened") status = "active";
1675
+ if (assignment && unresolvedDependencies.length > 0
1676
+ && !["completed", "failed"].includes(status)) status = "waiting_dependency";
1050
1677
  if (executionTruth && (executionTruth.status !== "bound"
1051
1678
  || (executionTruth.lifecycle === "closed"
1052
1679
  && executionTruth.closeoutDecision !== "accepted"))) {
@@ -1056,7 +1683,7 @@ function projectStatus(definition, records, workspaceRoot = null) {
1056
1683
  ? {
1057
1684
  kind: "measured_units",
1058
1685
  unit: item.measurable.unit,
1059
- completed: checkpoint?.completedUnits ?? 0,
1686
+ completed: effectiveCheckpoint?.completedUnits ?? 0,
1060
1687
  total: item.measurable.total,
1061
1688
  }
1062
1689
  : { kind: "state_only" };
@@ -1075,6 +1702,9 @@ function projectStatus(definition, records, workspaceRoot = null) {
1075
1702
  ...(checkpoint ? [checkpoint.ref] : []),
1076
1703
  ...(handoff ? [handoff.ref] : []),
1077
1704
  ...(rework ? [rework.ref] : []),
1705
+ ...reopenFacts.map(row => row.ref),
1706
+ ...(currentTransfer ? [currentTransfer.ref] : []),
1707
+ ...sourceDeliveries.map(row => row.ref),
1078
1708
  ];
1079
1709
  const projectedRework = reworkProjection(
1080
1710
  currentRework,
@@ -1082,9 +1712,13 @@ function projectStatus(definition, records, workspaceRoot = null) {
1082
1712
  ? assignmentEvents.findIndex(row => row.assignmentId === currentRework.assignmentId) + 1
1083
1713
  : null,
1084
1714
  );
1085
- const latestSummary = latestFact?.type === "rework_returned"
1086
- ? latestFact.reason
1087
- : latestFact?.summary ?? null;
1715
+ const latestSummary = ["rework_returned", "upstream_reopened"].includes(projectedFact?.type)
1716
+ ? projectedFact.reason
1717
+ : projectedFact?.summary ?? null;
1718
+ const checkpointLikeFact = ["checkpoint_recorded", "responsibility_transferred"]
1719
+ .includes(projectedFact?.type)
1720
+ ? projectedFact
1721
+ : null;
1088
1722
  return {
1089
1723
  workItemId: item.id,
1090
1724
  title: item.title,
@@ -1098,19 +1732,23 @@ function projectStatus(definition, records, workspaceRoot = null) {
1098
1732
  executionTruth,
1099
1733
  dependencies: [...item.dependencies],
1100
1734
  unresolvedDependencies,
1101
- blockers: latestFact?.type === "checkpoint_recorded"
1102
- ? checkpoint.blockerRefs
1735
+ blockers: checkpointLikeFact
1736
+ ? checkpointLikeFact.blockerRefs
1103
1737
  : unresolvedDependencies.map(id => `work-item:${id}`),
1104
- decisionRefs: latestFact?.type === "checkpoint_recorded" ? checkpoint.decisionRefs : [],
1738
+ decisionRefs: checkpointLikeFact ? checkpointLikeFact.decisionRefs : [],
1105
1739
  progress: measured,
1106
1740
  summary: latestSummary,
1107
- nextAction: latestFact?.nextAction ?? null,
1741
+ nextAction: projectedFact?.nextAction ?? null,
1108
1742
  pendingHandoff,
1109
1743
  rework: projectedRework,
1744
+ latestSourceDelivery,
1110
1745
  lastCheckpointAt: checkpoint?.occurredAt ?? null,
1111
- evidenceRefs: latestFact?.evidenceRefs ?? [],
1746
+ evidenceRefs: projectedFact?.evidenceRefs ?? [],
1112
1747
  assignmentHistory: history,
1113
1748
  truthRefs,
1749
+ ...(reopenFacts.length === 0
1750
+ ? {}
1751
+ : { invalidatedByReopenIds: invalidatingReopens.map(row => row.reopenId) }),
1114
1752
  };
1115
1753
  });
1116
1754
  const openDecisions = [...state.decisions.values()]
@@ -1311,6 +1949,7 @@ function projectStatus(definition, records, workspaceRoot = null) {
1311
1949
  truthRefs: [row.ref],
1312
1950
  };
1313
1951
  });
1952
+ const sourceDeliveries = state.sourceDeliveries.map(sourceDeliveryProjection);
1314
1953
  let overall = "planned";
1315
1954
  if (workItems.every(row => row.status === "completed")
1316
1955
  && integrationGates.every(row => row.status === "passed")
@@ -1318,7 +1957,7 @@ function projectStatus(definition, records, workspaceRoot = null) {
1318
1957
  else if (openDecisions.length > 0 || workItems.some(row => row.status === "failed")) overall = "active_with_blockers";
1319
1958
  else if (workItems.some(row => row.status !== "planned" || row.pendingHandoff !== null)) overall = "active";
1320
1959
  const status = {
1321
- schemaVersion: STATUS_SCHEMA,
1960
+ schemaVersion: STATUS_SCHEMA_V2,
1322
1961
  status: "team_project_status",
1323
1962
  projectId: definition.projectId,
1324
1963
  objective: definition.objective,
@@ -1336,6 +1975,7 @@ function projectStatus(definition, records, workspaceRoot = null) {
1336
1975
  openDeferredVerificationIds,
1337
1976
  integrationGates,
1338
1977
  dataCandidates,
1978
+ sourceDeliveries,
1339
1979
  projectTruthHash: sha256(canonical({ definition, events: records.map(row => row.event) })),
1340
1980
  authorizationGranted: false,
1341
1981
  };
@@ -1474,6 +2114,101 @@ function validateEventAgainstState(event, definition, records, workspaceRoot) {
1474
2114
  );
1475
2115
  }
1476
2116
  }
2117
+ } else if (normalized.type === "upstream_reopened") {
2118
+ if ([...state.reopens.values()].some(row => row.reopenId === normalized.reopenId)) {
2119
+ throw new Error(`Upstream reopen ${normalized.reopenId} already exists.`);
2120
+ }
2121
+ if (state.assignmentHistory.get(normalized.workItemId).some(
2122
+ row => row.assignmentId === normalized.assignmentId,
2123
+ )) {
2124
+ throw new Error(`Assignment ${normalized.assignmentId} already exists.`);
2125
+ }
2126
+ const current = state.assignments.get(normalized.workItemId) ?? null;
2127
+ const checkpoint = state.checkpoints.get(normalized.workItemId) ?? null;
2128
+ const projected = status.workItems.find(row => row.workItemId === normalized.workItemId);
2129
+ if (projected?.status !== "completed"
2130
+ || !current
2131
+ || current.assignmentId !== normalized.previousAssignmentId
2132
+ || current.agentId !== normalized.fromAgentId
2133
+ || checkpoint?.eventId !== normalized.previousCheckpointEventId
2134
+ || checkpoint.assignmentId !== current.assignmentId
2135
+ || checkpoint.state !== "completed") {
2136
+ throw new Error("reopen-upstream requires the current completed upstream WorkItem.");
2137
+ }
2138
+ const downstreamIds = downstreamWorkItemIds(definition, normalized.workItemId);
2139
+ if (!downstreamIds.includes(normalized.triggerWorkItemId)) {
2140
+ throw new Error("reopen-upstream trigger must be a transitive downstream WorkItem.");
2141
+ }
2142
+ const triggerAssignment = state.assignments.get(normalized.triggerWorkItemId) ?? null;
2143
+ if (triggerAssignment?.agentId !== normalized.reviewerAgentId) {
2144
+ throw new Error("reopen-upstream reviewer must own the trigger WorkItem.");
2145
+ }
2146
+ const affectedIds = [normalized.workItemId, ...downstreamIds];
2147
+ const passedGate = definition.integrationGates.find(gate => (
2148
+ state.gatePasses.has(gate.id)
2149
+ && gate.requiredWorkItemIds.some(id => affectedIds.includes(id))
2150
+ ));
2151
+ if (passedGate) {
2152
+ throw new Error(`reopen-upstream cannot invalidate passed integration gate ${passedGate.id}.`);
2153
+ }
2154
+ for (const otherProjection of status.workItems) {
2155
+ if (otherProjection.workItemId === normalized.workItemId
2156
+ || otherProjection.assignmentId === null
2157
+ || ["completed", "failed"].includes(otherProjection.status)) continue;
2158
+ const other = definition.workItems.find(row => row.id === otherProjection.workItemId);
2159
+ if (pathsOverlap(workItem.ownedPaths, other.ownedPaths)) {
2160
+ throw new Error(
2161
+ `Owned paths overlap active assignment ${otherProjection.assignmentId}: ${normalized.workItemId} <> ${otherProjection.workItemId}.`,
2162
+ );
2163
+ }
2164
+ }
2165
+ } else if (normalized.type === "responsibility_transferred") {
2166
+ if ([...state.transfers.values()].some(row => row.transferId === normalized.transferId)) {
2167
+ throw new Error(`Responsibility transfer ${normalized.transferId} already exists.`);
2168
+ }
2169
+ if (state.assignmentHistory.get(normalized.workItemId).some(
2170
+ row => row.assignmentId === normalized.assignmentId,
2171
+ )) {
2172
+ throw new Error(`Assignment ${normalized.assignmentId} already exists.`);
2173
+ }
2174
+ const current = state.assignments.get(normalized.workItemId) ?? null;
2175
+ const projected = status.workItems.find(row => row.workItemId === normalized.workItemId);
2176
+ if (!current
2177
+ || current.assignmentId !== normalized.previousAssignmentId
2178
+ || current.agentId !== normalized.fromAgentId) {
2179
+ throw new Error("Responsibility transfer must bind the current assignment and Agent.");
2180
+ }
2181
+ if (["completed", "failed"].includes(projected?.status)) {
2182
+ throw new Error("Responsibility transfer cannot target a terminal WorkItem.");
2183
+ }
2184
+ if (projected?.pendingHandoff !== null) {
2185
+ throw new Error("Responsibility transfer cannot replace a pending handoff.");
2186
+ }
2187
+ const existingFactIds = new Set(records.flatMap(({ event }) => [
2188
+ event.eventId,
2189
+ ...(event.type === "responsibility_transferred"
2190
+ ? [event.checkpointEventId, event.handoffEventId, event.assignmentEventId]
2191
+ : []),
2192
+ ]));
2193
+ for (const factId of [
2194
+ normalized.checkpointEventId,
2195
+ normalized.handoffEventId,
2196
+ normalized.assignmentEventId,
2197
+ ]) {
2198
+ if (existingFactIds.has(factId)) {
2199
+ throw new Error(`Responsibility transfer fact ID ${factId} already exists.`);
2200
+ }
2201
+ }
2202
+ if (workItem.measurable) {
2203
+ if (normalized.completedUnits === undefined) {
2204
+ throw new Error("Measured WorkItem transfer requires completedUnits.");
2205
+ }
2206
+ if (normalized.completedUnits > workItem.measurable.total) {
2207
+ throw new Error("Responsibility transfer completedUnits exceeds measurable total.");
2208
+ }
2209
+ } else if (normalized.completedUnits !== undefined) {
2210
+ throw new Error("Unmeasured WorkItem transfer cannot claim completedUnits.");
2211
+ }
1477
2212
  } else if (normalized.type === "decision_opened") {
1478
2213
  if (state.decisions.has(normalized.decisionId)) throw new Error(`Decision ${normalized.decisionId} already exists.`);
1479
2214
  } else if (normalized.type === "decision_resolved") {
@@ -1513,6 +2248,18 @@ function validateEventAgainstState(event, definition, records, workspaceRoot) {
1513
2248
  if (state.dataCandidates.some(row => row.candidateId === normalized.candidateId)) {
1514
2249
  throw new Error(`Data candidate ${normalized.candidateId} already exists.`);
1515
2250
  }
2251
+ } else if (normalized.type === "source_delivery_imported") {
2252
+ if (state.sourceDeliveries.some(row => row.deliveryId === normalized.deliveryId)) {
2253
+ throw new Error(`External source delivery ${normalized.deliveryId} already exists.`);
2254
+ }
2255
+ const current = state.assignments.get(normalized.workItemId) ?? null;
2256
+ if (current?.assignmentId !== normalized.assignmentId) {
2257
+ throw new Error("External source delivery must bind the current assignment.");
2258
+ }
2259
+ const projected = status.workItems.find(row => row.workItemId === normalized.workItemId);
2260
+ if (["completed", "failed"].includes(projected?.status)) {
2261
+ throw new Error("External source delivery requires a non-terminal WorkItem.");
2262
+ }
1516
2263
  }
1517
2264
  return normalized;
1518
2265
  }
@@ -1849,6 +2596,205 @@ export function rejectAndReturnTeamProjectV1({
1849
2596
  });
1850
2597
  }
1851
2598
 
2599
+ export function reopenUpstreamTeamProjectV1({
2600
+ workspaceRoot,
2601
+ reopenId,
2602
+ occurredAt,
2603
+ upstreamWorkItemId,
2604
+ triggerWorkItemId,
2605
+ reviewerAgentId,
2606
+ toAgentId,
2607
+ reason,
2608
+ nextAction,
2609
+ evidenceRefs,
2610
+ }) {
2611
+ safeIdentifier(reopenId, "reopenId");
2612
+ const eventId = `upstream-reopened-${reopenId}`;
2613
+ const assignmentId = `reopen-${reopenId}`;
2614
+ const loaded = loadProject(workspaceRoot);
2615
+ return withProjectLock(loaded.projectRoot, () => {
2616
+ const refreshed = loadProject(workspaceRoot);
2617
+ const existing = refreshed.events.find(row => row.event.eventId === eventId)?.event ?? null;
2618
+ let event;
2619
+ if (existing) {
2620
+ event = {
2621
+ schemaVersion: EVENT_SCHEMA_V4,
2622
+ eventId,
2623
+ type: "upstream_reopened",
2624
+ occurredAt,
2625
+ reopenId,
2626
+ assignmentId,
2627
+ previousAssignmentId: existing.previousAssignmentId,
2628
+ previousCheckpointEventId: existing.previousCheckpointEventId,
2629
+ workItemId: upstreamWorkItemId,
2630
+ triggerWorkItemId,
2631
+ fromAgentId: existing.fromAgentId,
2632
+ reviewerAgentId,
2633
+ toAgentId,
2634
+ reason,
2635
+ evidenceRefs,
2636
+ nextAction,
2637
+ };
2638
+ } else {
2639
+ const state = projectEvents(refreshed.definition, refreshed.events);
2640
+ const current = state.assignments.get(upstreamWorkItemId) ?? null;
2641
+ const checkpoint = state.checkpoints.get(upstreamWorkItemId) ?? null;
2642
+ if (!current || !checkpoint) {
2643
+ throw new Error("reopen-upstream requires an assigned, completed upstream WorkItem.");
2644
+ }
2645
+ event = {
2646
+ schemaVersion: EVENT_SCHEMA_V4,
2647
+ eventId,
2648
+ type: "upstream_reopened",
2649
+ occurredAt,
2650
+ reopenId,
2651
+ assignmentId,
2652
+ previousAssignmentId: current.assignmentId,
2653
+ previousCheckpointEventId: checkpoint.eventId,
2654
+ workItemId: upstreamWorkItemId,
2655
+ triggerWorkItemId,
2656
+ fromAgentId: current.agentId,
2657
+ reviewerAgentId,
2658
+ toAgentId,
2659
+ reason,
2660
+ evidenceRefs,
2661
+ nextAction,
2662
+ };
2663
+ }
2664
+ const recorded = appendTeamProjectEventWithinLock(refreshed, event);
2665
+ const projectedEvents = recorded.resumed
2666
+ ? refreshed.events
2667
+ : [...refreshed.events, { event, ref: `project/events/${event.eventId}.json` }];
2668
+ const status = projectStatus(refreshed.definition, projectedEvents, refreshed.root);
2669
+ const projectedState = projectEvents(refreshed.definition, projectedEvents);
2670
+ const current = status.workItems.find(row => row.workItemId === upstreamWorkItemId) ?? null;
2671
+ const reopen = projectedState.reopens.get(upstreamWorkItemId) ?? null;
2672
+ if (current?.assignmentId !== assignmentId || reopen?.reopenId !== reopenId) {
2673
+ throw new Error(
2674
+ `Cannot resume historical/superseded upstream reopen ${reopenId}; `
2675
+ + `current assignment=${current?.assignmentId ?? "none"}.`,
2676
+ );
2677
+ }
2678
+ return {
2679
+ status: "team_project_upstream_reopened",
2680
+ reopenId,
2681
+ assignmentId,
2682
+ previousAssignmentId: event.previousAssignmentId,
2683
+ previousCheckpointEventId: event.previousCheckpointEventId,
2684
+ upstreamWorkItemId,
2685
+ triggerWorkItemId,
2686
+ fromAgentId: event.fromAgentId,
2687
+ reviewerAgentId,
2688
+ toAgentId,
2689
+ invalidatedWorkItemIds: downstreamWorkItemIds(
2690
+ refreshed.definition,
2691
+ upstreamWorkItemId,
2692
+ { includeRoot: true },
2693
+ ),
2694
+ eventId: recorded.eventId,
2695
+ eventPath: recorded.eventPath,
2696
+ eventSha256: recorded.eventSha256,
2697
+ resumed: recorded.resumed,
2698
+ projectTruthHash: status.projectTruthHash,
2699
+ overall: status.overall,
2700
+ nextAction: status.nextAction,
2701
+ nextActorId: status.nextActorId,
2702
+ gitWritePerformed: false,
2703
+ releaseWritePerformed: false,
2704
+ authorizationGranted: false,
2705
+ exitCode: 0,
2706
+ };
2707
+ });
2708
+ }
2709
+
2710
+ export function transferTeamProjectV1({
2711
+ workspaceRoot,
2712
+ transferId,
2713
+ occurredAt,
2714
+ assignmentId: previousAssignmentId,
2715
+ workItemId,
2716
+ fromAgentId,
2717
+ toAgentId,
2718
+ state,
2719
+ summary,
2720
+ completedUnits,
2721
+ evidenceRefs = [],
2722
+ blockerRefs = [],
2723
+ decisionRefs = [],
2724
+ nextAction,
2725
+ sourceFingerprint,
2726
+ }) {
2727
+ safeIdentifier(transferId, "transferId");
2728
+ const eventId = `responsibility-transferred-${transferId}`;
2729
+ const assignmentId = `transfer-${transferId}`;
2730
+ const event = {
2731
+ schemaVersion: EVENT_SCHEMA_V4,
2732
+ eventId,
2733
+ type: "responsibility_transferred",
2734
+ occurredAt,
2735
+ transferId,
2736
+ assignmentId,
2737
+ previousAssignmentId,
2738
+ checkpointEventId: `transfer-checkpoint-${transferId}`,
2739
+ handoffEventId: `transfer-handoff-${transferId}`,
2740
+ assignmentEventId: `transfer-assignment-${transferId}`,
2741
+ workItemId,
2742
+ fromAgentId,
2743
+ toAgentId,
2744
+ state,
2745
+ summary,
2746
+ ...(completedUnits === undefined ? {} : { completedUnits }),
2747
+ evidenceRefs,
2748
+ blockerRefs,
2749
+ decisionRefs,
2750
+ nextAction,
2751
+ ...(sourceFingerprint === undefined ? {} : { sourceFingerprint }),
2752
+ };
2753
+ const loaded = loadProject(workspaceRoot);
2754
+ return withProjectLock(loaded.projectRoot, () => {
2755
+ const refreshed = loadProject(workspaceRoot);
2756
+ const recorded = appendTeamProjectEventWithinLock(refreshed, event);
2757
+ const projectedEvents = recorded.resumed
2758
+ ? refreshed.events
2759
+ : [...refreshed.events, { event, ref: `project/events/${event.eventId}.json` }];
2760
+ const status = projectStatus(refreshed.definition, projectedEvents, refreshed.root);
2761
+ const projectedState = projectEvents(refreshed.definition, projectedEvents);
2762
+ const current = status.workItems.find(row => row.workItemId === workItemId) ?? null;
2763
+ const transfer = projectedState.transfers.get(workItemId) ?? null;
2764
+ if (current?.assignmentId !== assignmentId || transfer?.transferId !== transferId) {
2765
+ throw new Error(
2766
+ `Cannot resume historical/superseded responsibility transfer ${transferId}; `
2767
+ + `current assignment=${current?.assignmentId ?? "none"}.`,
2768
+ );
2769
+ }
2770
+ const factRefs = ["checkpoint", "handoff", "assignment"]
2771
+ .map(fragment => `project/events/${eventId}.json#${fragment}`);
2772
+ return {
2773
+ status: "team_project_transfer",
2774
+ transferId,
2775
+ assignmentId,
2776
+ previousAssignmentId,
2777
+ workItemId,
2778
+ fromAgentId,
2779
+ toAgentId,
2780
+ state,
2781
+ factRefs,
2782
+ eventId: recorded.eventId,
2783
+ eventPath: recorded.eventPath,
2784
+ eventSha256: recorded.eventSha256,
2785
+ resumed: recorded.resumed,
2786
+ projectTruthHash: status.projectTruthHash,
2787
+ overall: status.overall,
2788
+ nextAction: status.nextAction,
2789
+ nextActorId: status.nextActorId,
2790
+ gitWritePerformed: false,
2791
+ releaseWritePerformed: false,
2792
+ authorizationGranted: false,
2793
+ exitCode: 0,
2794
+ };
2795
+ });
2796
+ }
2797
+
1852
2798
  export function openTeamProjectDecisionV1({
1853
2799
  workspaceRoot,
1854
2800
  decisionId,
@@ -2155,7 +3101,32 @@ export function checkpointTeamProjectV1({
2155
3101
  };
2156
3102
  }
2157
3103
 
3104
+ function legacyWorkItemProjection(workItem) {
3105
+ if (workItem === null) return null;
3106
+ const {
3107
+ latestSourceDelivery: _latestSourceDelivery,
3108
+ invalidatedByReopenIds: _invalidatedByReopenIds,
3109
+ ...legacy
3110
+ } = workItem;
3111
+ return legacy;
3112
+ }
3113
+
2158
3114
  export function readTeamProjectStatusV1({ workspaceRoot }) {
3115
+ const loaded = loadProject(workspaceRoot);
3116
+ const { sourceDeliveries: _sourceDeliveries, ...legacy } = projectStatus(
3117
+ loaded.definition,
3118
+ loaded.events,
3119
+ loaded.root,
3120
+ );
3121
+ return {
3122
+ ...legacy,
3123
+ schemaVersion: STATUS_SCHEMA_V1,
3124
+ workItems: legacy.workItems.map(legacyWorkItemProjection),
3125
+ exitCode: 0,
3126
+ };
3127
+ }
3128
+
3129
+ export function readTeamProjectStatusV2({ workspaceRoot }) {
2159
3130
  const loaded = loadProject(workspaceRoot);
2160
3131
  return {
2161
3132
  ...projectStatus(loaded.definition, loaded.events, loaded.root),
@@ -2163,7 +3134,77 @@ export function readTeamProjectStatusV1({ workspaceRoot }) {
2163
3134
  };
2164
3135
  }
2165
3136
 
2166
- export function buildTeamProjectTakeoverV1({ workspaceRoot, agentId }) {
3137
+ export function readTeamProjectHandoffWaitV1({ workspaceRoot }) {
3138
+ const { root, projectRoot: rootPath } = projectRoot(workspaceRoot);
3139
+ if (!existsSync(path.join(rootPath, "definition.json"))) {
3140
+ return {
3141
+ completedHandoffs: 0,
3142
+ unresolvedHandoffs: 0,
3143
+ staleHandoffs: 0,
3144
+ totalMs: 0,
3145
+ averageMs: 0,
3146
+ maxMs: 0,
3147
+ samples: [],
3148
+ inputRefs: [],
3149
+ };
3150
+ }
3151
+ const loaded = loadProject(root);
3152
+ const workFactTypes = new Set([
3153
+ "agent_assigned",
3154
+ "checkpoint_recorded",
3155
+ "handoff_recorded",
3156
+ "rework_returned",
3157
+ "source_delivery_imported",
3158
+ "upstream_reopened",
3159
+ "responsibility_transferred",
3160
+ ]);
3161
+ const samples = [];
3162
+ let unresolvedHandoffs = 0;
3163
+ let staleHandoffs = 0;
3164
+ const handoffs = loaded.events.filter(({ event }) => event.type === "handoff_recorded");
3165
+ for (const { event, ref } of handoffs) {
3166
+ const laterFacts = loaded.events.filter(({ event: later }) => (
3167
+ later.workItemId === event.workItemId
3168
+ && compareEventFacts(later, event) > 0
3169
+ && workFactTypes.has(later.type)
3170
+ ));
3171
+ const accepted = laterFacts.find(({ event: later }) => (
3172
+ later.type === "agent_assigned"
3173
+ && later.supersedesAssignmentId === event.assignmentId
3174
+ && later.agentId === event.toAgentId
3175
+ ));
3176
+ if (accepted) {
3177
+ const durationMs = Date.parse(accepted.event.occurredAt) - Date.parse(event.occurredAt);
3178
+ samples.push({
3179
+ handoffEventId: event.eventId,
3180
+ acceptedEventId: accepted.event.eventId,
3181
+ workItemId: event.workItemId,
3182
+ durationMs,
3183
+ handoffRef: ref,
3184
+ acceptedRef: accepted.ref,
3185
+ });
3186
+ } else if (laterFacts.length > 0) {
3187
+ staleHandoffs += 1;
3188
+ } else {
3189
+ unresolvedHandoffs += 1;
3190
+ }
3191
+ }
3192
+ const totalMs = samples.reduce((total, sample) => total + sample.durationMs, 0);
3193
+ return {
3194
+ completedHandoffs: samples.length,
3195
+ unresolvedHandoffs,
3196
+ staleHandoffs,
3197
+ totalMs,
3198
+ averageMs: samples.length === 0 ? 0 : Math.round(totalMs / samples.length),
3199
+ maxMs: samples.length === 0 ? 0 : Math.max(...samples.map(sample => sample.durationMs)),
3200
+ samples,
3201
+ inputRefs: loaded.events
3202
+ .filter(({ event }) => event.type === "handoff_recorded" || event.type === "agent_assigned")
3203
+ .map(({ ref }) => ref),
3204
+ };
3205
+ }
3206
+
3207
+ export function buildTeamProjectTakeoverV2({ workspaceRoot, agentId }) {
2167
3208
  safeIdentifier(agentId, "agentId");
2168
3209
  const loaded = loadProject(workspaceRoot);
2169
3210
  const status = projectStatus(loaded.definition, loaded.events, loaded.root);
@@ -2180,6 +3221,29 @@ export function buildTeamProjectTakeoverV1({ workspaceRoot, agentId }) {
2180
3221
  row.pendingHandoff?.toAgentId === agentId
2181
3222
  ))?.pendingHandoff ?? null;
2182
3223
  const pendingHandoff = pendingForCurrent ?? pendingForAgent;
3224
+ const staleHandoffs = staleHandoffsForTakeover({
3225
+ loaded,
3226
+ status,
3227
+ agentId,
3228
+ responsibilities,
3229
+ });
3230
+ const latestStaleHandoff = [...staleHandoffs]
3231
+ .sort(compareEventFacts)
3232
+ .at(-1) ?? null;
3233
+ const focusResponsibility = current
3234
+ ?? (pendingHandoff
3235
+ ? status.workItems.find(row => row.workItemId === pendingHandoff.workItemId) ?? null
3236
+ : null)
3237
+ ?? (latestStaleHandoff
3238
+ ? status.workItems.find(row => row.workItemId === latestStaleHandoff.workItemId) ?? null
3239
+ : null);
3240
+ const focusedDeliveries = focusResponsibility
3241
+ ? status.sourceDeliveries.filter(row => (
3242
+ row.workItemId === focusResponsibility.workItemId
3243
+ ))
3244
+ : [];
3245
+ const currentSourceDelivery = focusedDeliveries.at(-1) ?? null;
3246
+ const deliveryDelta = sourceDeliveryDelta(focusedDeliveries);
2183
3247
  const checkpoints = loaded.events
2184
3248
  .filter(row => row.event.type === "checkpoint_recorded"
2185
3249
  && responsibilities.some(item => item.workItemId === row.event.workItemId))
@@ -2189,6 +3253,9 @@ export function buildTeamProjectTakeoverV1({ workspaceRoot, agentId }) {
2189
3253
  ...(lastCheckpoint?.evidenceRefs ?? []),
2190
3254
  ...(current?.evidenceRefs ?? []),
2191
3255
  ...(pendingHandoff?.evidenceRefs ?? []),
3256
+ ...(currentSourceDelivery ? [currentSourceDelivery.truthRef] : []),
3257
+ ...(deliveryDelta?.previousDeliveryId ? [focusedDeliveries.at(-2).truthRef] : []),
3258
+ ...staleHandoffs.map(row => row.truthRef),
2192
3259
  ])].sort();
2193
3260
  const unresolvedDecisionsById = new Map();
2194
3261
  for (const decision of status.openDecisions) {
@@ -2230,7 +3297,7 @@ export function buildTeamProjectTakeoverV1({ workspaceRoot, agentId }) {
2230
3297
  : current ? null : status.readyQueue[0] ? `Take ${status.readyQueue[0]}.` : null;
2231
3298
  }
2232
3299
  return {
2233
- schemaVersion: "OwlCodaRunKitTeamProjectTakeoverV1",
3300
+ schemaVersion: "OwlCodaRunKitTeamProjectTakeoverV2",
2234
3301
  status: "team_project_takeover",
2235
3302
  projectId: status.projectId,
2236
3303
  objective: status.objective,
@@ -2243,6 +3310,9 @@ export function buildTeamProjectTakeoverV1({ workspaceRoot, agentId }) {
2243
3310
  currentResponsibility: current,
2244
3311
  allResponsibilities: responsibilities,
2245
3312
  pendingHandoff,
3313
+ staleHandoffs,
3314
+ currentSourceDelivery,
3315
+ sourceDeliveryDelta: deliveryDelta,
2246
3316
  lastAcceptedCheckpoint: lastCheckpoint
2247
3317
  ? {
2248
3318
  workItemId: lastCheckpoint.workItemId,
@@ -2265,6 +3335,27 @@ export function buildTeamProjectTakeoverV1({ workspaceRoot, agentId }) {
2265
3335
  };
2266
3336
  }
2267
3337
 
3338
+ export function buildTeamProjectTakeoverV1(input) {
3339
+ const {
3340
+ staleHandoffs: _staleHandoffs,
3341
+ currentSourceDelivery: _currentSourceDelivery,
3342
+ sourceDeliveryDelta: _sourceDeliveryDelta,
3343
+ ...legacy
3344
+ } = buildTeamProjectTakeoverV2(input);
3345
+ const v2OnlyEvidenceRefs = new Set([
3346
+ ..._staleHandoffs.map(row => row.truthRef),
3347
+ ...readTeamProjectStatusV2({ workspaceRoot: input.workspaceRoot })
3348
+ .sourceDeliveries.map(row => row.truthRef),
3349
+ ]);
3350
+ return {
3351
+ ...legacy,
3352
+ schemaVersion: "OwlCodaRunKitTeamProjectTakeoverV1",
3353
+ currentResponsibility: legacyWorkItemProjection(legacy.currentResponsibility),
3354
+ allResponsibilities: legacy.allResponsibilities.map(legacyWorkItemProjection),
3355
+ evidenceRefs: legacy.evidenceRefs.filter(ref => !v2OnlyEvidenceRefs.has(ref)),
3356
+ };
3357
+ }
3358
+
2268
3359
  export function formatTeamProjectStatusHumanV1(status) {
2269
3360
  const nextAction = status.nextAction === null ? "none" : singleLine(status.nextAction);
2270
3361
  return [
@@ -2285,6 +3376,65 @@ export function formatTeamProjectStatusHumanV1(status) {
2285
3376
  ].join("\n");
2286
3377
  }
2287
3378
 
3379
+ export function formatTeamProjectStatusHumanV2(status) {
3380
+ const nextAction = status.nextAction === null ? "none" : singleLine(status.nextAction);
3381
+ const latestSourceDelivery = status.sourceDeliveries.at(-1) ?? null;
3382
+ return [
3383
+ status.headline,
3384
+ `Project: ${status.projectId} — ${singleLine(status.objective)}`,
3385
+ `State: ${status.overall}`,
3386
+ `Work: ${status.counts.completed}/${status.counts.total} completed; ${status.counts.active} active; ${status.counts.waiting_dependency + status.counts.waiting_decision} waiting`,
3387
+ `Agents: ${status.agents.map(agent => agent.agentId).join(", ") || "none assigned"}`,
3388
+ `Open decisions: ${status.openDecisions.map(row => row.decisionId).join(", ") || "none"}`,
3389
+ `Deferred verification: ${status.openDeferredVerificationIds.length} open${
3390
+ status.openDeferredVerificationIds.length > 0
3391
+ ? ` (${status.openDeferredVerificationIds.join(", ")})`
3392
+ : ""
3393
+ }`,
3394
+ `Ready queue: ${status.readyQueue.join(", ") || "none"}`,
3395
+ `Source deliveries: ${status.sourceDeliveries.length}${latestSourceDelivery
3396
+ ? `; latest ${latestSourceDelivery.deliveryId} (${latestSourceDelivery.overlayStatus}, ${latestSourceDelivery.deliveryCandidateFingerprint})`
3397
+ : ""}`,
3398
+ `Next: ${nextAction}${status.nextActorId ? ` (owner ${status.nextActorId})` : ""}`,
3399
+ "",
3400
+ ].join("\n");
3401
+ }
3402
+
3403
+ export function formatTeamProjectTakeoverHumanV2(takeover) {
3404
+ const responsibility = takeover.currentResponsibility;
3405
+ const agentNextAction = takeover.nextAction === null
3406
+ ? "none"
3407
+ : singleLine(takeover.nextAction);
3408
+ const projectNextAction = takeover.projectNextAction === null
3409
+ ? "none"
3410
+ : singleLine(takeover.projectNextAction);
3411
+ return [
3412
+ takeover.projectHeadline,
3413
+ `Project: ${takeover.projectId} — ${singleLine(takeover.objective)}`,
3414
+ `Agent: ${takeover.agentId}`,
3415
+ `Responsibility: ${responsibility?.workItemId ?? "none"}`,
3416
+ `Open decisions: ${takeover.unresolvedDecisions.map(row => row.decisionId).join(", ") || "none"}`,
3417
+ `Deferred verification: ${takeover.deferredVerifications
3418
+ .map(row => row.verificationId).join(", ") || "none"}`,
3419
+ `Handoffs: pending ${takeover.pendingHandoff
3420
+ ? `${takeover.pendingHandoff.fromAgentId} -> ${takeover.pendingHandoff.toAgentId} for ${takeover.pendingHandoff.workItemId}`
3421
+ : "none"}; stale ${takeover.staleHandoffs.length > 0
3422
+ ? takeover.staleHandoffs.map(row => `${row.eventId} (${row.reason})`).join(", ")
3423
+ : "none"}`,
3424
+ `Candidate: ${takeover.currentSourceDelivery
3425
+ ? `${takeover.currentSourceDelivery.deliveryId} (${takeover.currentSourceDelivery.overlayStatus}, ${takeover.currentSourceDelivery.deliveryCandidateFingerprint})`
3426
+ : "none"}; delta ${takeover.sourceDeliveryDelta === null
3427
+ ? "none"
3428
+ : takeover.sourceDeliveryDelta.previousDeliveryId === null
3429
+ ? `first delivery; added ${takeover.sourceDeliveryDelta.addedPaths.length}`
3430
+ : `vs ${takeover.sourceDeliveryDelta.previousDeliveryId}; added ${takeover.sourceDeliveryDelta.addedPaths.length}, changed ${takeover.sourceDeliveryDelta.changedPaths.length}, removed ${takeover.sourceDeliveryDelta.removedPaths.length}, unchanged ${takeover.sourceDeliveryDelta.unchangedPaths.length}`}`,
3431
+ `Agent next: ${agentNextAction}`,
3432
+ `Project next: ${projectNextAction}${takeover.projectNextActorId ? ` (owner ${takeover.projectNextActorId})` : ""}`,
3433
+ `State: ${takeover.overall}`,
3434
+ "",
3435
+ ].join("\n");
3436
+ }
3437
+
2288
3438
  export function formatTeamProjectTakeoverHumanV1(takeover) {
2289
3439
  const responsibility = takeover.currentResponsibility;
2290
3440
  const agentNextAction = takeover.nextAction === null