owlrunkit 0.18.2 → 0.18.4
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/CHANGELOG.md +63 -0
- package/README.md +159 -52
- package/docs/architecture/OWLCODA_RUNKIT_0184_EXTERNAL_DELIVERY_INTAKE_PRODUCT_DECISION_20260812.md +209 -0
- package/docs/architecture/OWLCODA_RUNKIT_018_PROJECT_DRIVER_PRODUCT_DEFINITION.md +16 -5
- package/docs/architecture/OWLCODA_RUNKIT_USAGE_MODE_SELECTION_PRODUCT_DECISION_20260811.md +54 -3
- package/docs/architecture/OWLCODA_RUN_KIT_CONTRACT_V0_2.md +7 -0
- package/integrations/codex/skills/owlcoda-runkit/SKILL.md +125 -74
- package/integrations/codex/skills/owlcoda-runkit/assets/templates/config.json +3 -3
- package/integrations/codex/skills/owlcoda-runkit/references/contract-v0.1.md +160 -0
- package/integrations/codex/skills/owlcoda-runkit/references/contract-v0.2.md +545 -0
- package/package.json +1 -1
- package/packages/attest/README.md +4 -3
- package/packages/attest/consumer-surface-v1.json +1 -0
- package/packages/attest/distribution-manifest-v1.json +12 -1
- package/packages/attest/schemas/quick-verification-receipt-v2.schema.json +232 -0
- package/packages/attest/src/index.mjs +4 -1
- package/packages/attest/src/quick-receipt-contract.mjs +16 -6
- package/packages/attest/src/quick.mjs +21 -2
- package/packages/attest/src/workspace-snapshot.mjs +10 -1
- package/registry-release-policy-v1.json +5 -5
- package/sbom.spdx.json +5 -5
- package/scripts/runkit-contract/assurance-router.mjs +25 -6
- package/scripts/runkit-contract/core-contract.mjs +10 -1
- package/scripts/runkit-contract/onboarding-doctor.mjs +1 -1
- package/scripts/runkit-contract/profile-onboarding.mjs +263 -15
- package/scripts/runkit-contract/project-cli-resolver.mjs +6 -0
- package/scripts/runkit-contract/project-external-delivery.mjs +707 -0
- package/scripts/runkit-contract/quick-metrics.mjs +3 -3
- package/scripts/runkit-contract/quick-receipt.mjs +79 -12
- package/scripts/runkit-contract/quick-verify.mjs +89 -12
- package/scripts/runkit-contract/runkit-bootstrap.mjs +6 -1
- package/scripts/runkit-contract/runkit-cli.mjs +363 -143
- package/scripts/runkit-contract/schemas/assurance-route-v2.schema.json +26 -0
- package/scripts/runkit-contract/schemas/profile-detection-v3.schema.json +170 -0
- package/scripts/runkit-contract/schemas/team-project-event-v2.schema.json +128 -0
- package/scripts/runkit-contract/schemas/team-project-event-v3.schema.json +146 -0
- package/scripts/runkit-contract/schemas/team-project-status-v2.schema.json +85 -0
- package/scripts/runkit-contract/schemas/usage-mode-recommendation-v2.schema.json +136 -0
- package/scripts/runkit-contract/team-project.mjs +782 -55
- package/scripts/runkit-contract/usage-mode.mjs +28 -7
|
@@ -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,
|
|
@@ -23,7 +28,10 @@ import {
|
|
|
23
28
|
const PROJECT_ROOT = ".owlcoda/runkit/project";
|
|
24
29
|
const DEFINITION_SCHEMA = "OwlCodaRunKitTeamProjectDefinitionV1";
|
|
25
30
|
const EVENT_SCHEMA = "OwlCodaRunKitTeamProjectEventV1";
|
|
26
|
-
const
|
|
31
|
+
const EVENT_SCHEMA_V2 = "OwlCodaRunKitTeamProjectEventV2";
|
|
32
|
+
const EVENT_SCHEMA_V3 = "OwlCodaRunKitTeamProjectEventV3";
|
|
33
|
+
const STATUS_SCHEMA_V1 = "OwlCodaRunKitTeamProjectStatusV1";
|
|
34
|
+
const STATUS_SCHEMA_V2 = "OwlCodaRunKitTeamProjectStatusV2";
|
|
27
35
|
|
|
28
36
|
const DEFINITION_KEYS = [
|
|
29
37
|
"schemaVersion",
|
|
@@ -60,6 +68,12 @@ const EVENT_KEYS = Object.freeze({
|
|
|
60
68
|
"workItemId", "fromAgentId", "toAgentId", "summary", "evidenceRefs",
|
|
61
69
|
"nextAction",
|
|
62
70
|
],
|
|
71
|
+
rework_returned: [
|
|
72
|
+
"schemaVersion", "eventId", "type", "occurredAt", "reworkId",
|
|
73
|
+
"assignmentId", "failedAssignmentId", "failedCheckpointEventId",
|
|
74
|
+
"workItemId", "fromAgentId", "reviewerAgentId", "toAgentId", "reason",
|
|
75
|
+
"evidenceRefs", "nextAction",
|
|
76
|
+
],
|
|
63
77
|
decision_opened: [
|
|
64
78
|
"schemaVersion", "eventId", "type", "occurredAt", "decisionId",
|
|
65
79
|
"title", "question", "ownerAgentId", "blockingWorkItemIds", "options",
|
|
@@ -85,6 +99,17 @@ const EVENT_KEYS = Object.freeze({
|
|
|
85
99
|
"sourceRef", "rights", "inputRef", "outputRef", "decisionRef",
|
|
86
100
|
"verificationRefs", "outcomeRef", "version",
|
|
87
101
|
],
|
|
102
|
+
source_delivery_imported: [
|
|
103
|
+
"schemaVersion", "eventId", "type", "occurredAt", "deliveryId",
|
|
104
|
+
"projectId", "projectDefinitionSha256", "assignmentId", "workItemId",
|
|
105
|
+
"producerId", "targetWorkspaceRef",
|
|
106
|
+
"targetHead", "entrySnapshotRef", "entrySnapshotSha256",
|
|
107
|
+
"deliveryPacketSha256", "deliveryCandidateFingerprint",
|
|
108
|
+
"workspaceSnapshotFingerprint", "overlayStatus", "candidateFiles",
|
|
109
|
+
"changedPaths", "toolCachePaths", "dependencyEnvironmentPaths",
|
|
110
|
+
"unchangedPacketPaths", "outOfScopeSourcePaths", "targetSnapshot",
|
|
111
|
+
"proven", "notProven", "targetWritePerformed", "authorizationGranted",
|
|
112
|
+
],
|
|
88
113
|
});
|
|
89
114
|
|
|
90
115
|
function canonical(value) {
|
|
@@ -115,6 +140,26 @@ function nullableString(value, label) {
|
|
|
115
140
|
return nonEmptyString(value, label);
|
|
116
141
|
}
|
|
117
142
|
|
|
143
|
+
function sha256Ref(value, label) {
|
|
144
|
+
if (typeof value !== "string" || !/^sha256:[a-f0-9]{64}$/u.test(value)) {
|
|
145
|
+
throw new Error(`${label} must be a canonical SHA-256 reference.`);
|
|
146
|
+
}
|
|
147
|
+
return value;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function safeSourcePath(value, label) {
|
|
151
|
+
if (typeof value !== "string"
|
|
152
|
+
|| value.length === 0
|
|
153
|
+
|| path.isAbsolute(value)
|
|
154
|
+
|| path.win32.parse(value).root
|
|
155
|
+
|| value.includes("\\")
|
|
156
|
+
|| value.includes("\0")
|
|
157
|
+
|| value.split("/").some(segment => !segment || segment === "." || segment === "..")) {
|
|
158
|
+
throw new Error(`${label} must be a safe repository-relative path.`);
|
|
159
|
+
}
|
|
160
|
+
return value;
|
|
161
|
+
}
|
|
162
|
+
|
|
118
163
|
function stringArray(value, label, { allowEmpty = true } = {}) {
|
|
119
164
|
if (!Array.isArray(value)
|
|
120
165
|
|| (!allowEmpty && value.length === 0)
|
|
@@ -160,6 +205,30 @@ function withProjectLock(root, operation) {
|
|
|
160
205
|
}
|
|
161
206
|
}
|
|
162
207
|
|
|
208
|
+
export function teamProjectDefinitionBindingV1(definition) {
|
|
209
|
+
const normalized = validateDefinition(definition);
|
|
210
|
+
return {
|
|
211
|
+
projectId: normalized.projectId,
|
|
212
|
+
projectDefinitionSha256: `sha256:${sha256(canonical(normalized))}`,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function withTeamProjectControlLockV1({ workspaceRoot, operation }) {
|
|
217
|
+
if (typeof operation !== "function") {
|
|
218
|
+
throw new Error("Team project control operation must be a function.");
|
|
219
|
+
}
|
|
220
|
+
const loaded = loadProject(workspaceRoot);
|
|
221
|
+
return withProjectLock(loaded.projectRoot, () => {
|
|
222
|
+
const refreshed = loadProject(workspaceRoot);
|
|
223
|
+
return operation({
|
|
224
|
+
workspaceRoot: refreshed.root,
|
|
225
|
+
projectRoot: refreshed.projectRoot,
|
|
226
|
+
definition: structuredClone(refreshed.definition),
|
|
227
|
+
definitionBinding: teamProjectDefinitionBindingV1(refreshed.definition),
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
163
232
|
function uniqueIds(rows, label) {
|
|
164
233
|
const ids = rows.map(row => safeIdentifier(row.id, `${label} id`));
|
|
165
234
|
if (new Set(ids).size !== ids.length) throw new Error(`${label} ids must be unique.`);
|
|
@@ -391,6 +460,157 @@ function pendingHandoffProjection(handoff) {
|
|
|
391
460
|
: null;
|
|
392
461
|
}
|
|
393
462
|
|
|
463
|
+
function reworkProjection(rework, attempt) {
|
|
464
|
+
return rework
|
|
465
|
+
? {
|
|
466
|
+
eventId: rework.eventId,
|
|
467
|
+
truthRef: rework.ref,
|
|
468
|
+
reworkId: rework.reworkId,
|
|
469
|
+
attempt,
|
|
470
|
+
failedCheckpointEventId: rework.failedCheckpointEventId,
|
|
471
|
+
failedTruthRef: `project/events/${rework.failedCheckpointEventId}.json`,
|
|
472
|
+
previousAssignmentId: rework.failedAssignmentId,
|
|
473
|
+
previousAgentId: rework.fromAgentId,
|
|
474
|
+
reviewerAgentId: rework.reviewerAgentId,
|
|
475
|
+
assignmentId: rework.assignmentId,
|
|
476
|
+
agentId: rework.toAgentId,
|
|
477
|
+
reason: rework.reason,
|
|
478
|
+
evidenceRefs: [...rework.evidenceRefs],
|
|
479
|
+
nextAction: rework.nextAction,
|
|
480
|
+
occurredAt: rework.occurredAt,
|
|
481
|
+
}
|
|
482
|
+
: null;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function sourceDeliveryProjection(delivery) {
|
|
486
|
+
return delivery
|
|
487
|
+
? {
|
|
488
|
+
projectId: delivery.projectId,
|
|
489
|
+
projectDefinitionSha256: delivery.projectDefinitionSha256,
|
|
490
|
+
deliveryId: delivery.deliveryId,
|
|
491
|
+
workItemId: delivery.workItemId,
|
|
492
|
+
assignmentId: delivery.assignmentId,
|
|
493
|
+
producerId: delivery.producerId,
|
|
494
|
+
targetWorkspaceRef: delivery.targetWorkspaceRef,
|
|
495
|
+
targetHead: delivery.targetHead,
|
|
496
|
+
entrySnapshotRef: delivery.entrySnapshotRef,
|
|
497
|
+
entrySnapshotSha256: delivery.entrySnapshotSha256,
|
|
498
|
+
deliveryPacketSha256: delivery.deliveryPacketSha256,
|
|
499
|
+
deliveryCandidateFingerprint: delivery.deliveryCandidateFingerprint,
|
|
500
|
+
workspaceSnapshotFingerprint: delivery.workspaceSnapshotFingerprint,
|
|
501
|
+
overlayStatus: delivery.overlayStatus,
|
|
502
|
+
candidateFiles: delivery.candidateFiles.map(row => ({ ...row })),
|
|
503
|
+
candidatePaths: delivery.candidateFiles.map(row => row.path),
|
|
504
|
+
changedPaths: [...delivery.changedPaths],
|
|
505
|
+
toolCachePaths: [...delivery.toolCachePaths],
|
|
506
|
+
dependencyEnvironmentPaths: [...delivery.dependencyEnvironmentPaths],
|
|
507
|
+
unchangedPacketPaths: [...delivery.unchangedPacketPaths],
|
|
508
|
+
proven: [...delivery.proven],
|
|
509
|
+
notProven: [...delivery.notProven],
|
|
510
|
+
occurredAt: delivery.occurredAt,
|
|
511
|
+
truthRef: delivery.ref,
|
|
512
|
+
targetWritePerformed: false,
|
|
513
|
+
authorizationGranted: false,
|
|
514
|
+
}
|
|
515
|
+
: null;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function sourceDeliveryDelta(deliveries) {
|
|
519
|
+
const current = deliveries.at(-1) ?? null;
|
|
520
|
+
if (!current) return null;
|
|
521
|
+
const previous = deliveries.at(-2) ?? null;
|
|
522
|
+
if (!previous) {
|
|
523
|
+
return {
|
|
524
|
+
status: "first_delivery",
|
|
525
|
+
currentDeliveryId: current.deliveryId,
|
|
526
|
+
previousDeliveryId: null,
|
|
527
|
+
addedPaths: current.candidateFiles.map(row => row.path),
|
|
528
|
+
changedPaths: [],
|
|
529
|
+
removedPaths: [],
|
|
530
|
+
unchangedPaths: [],
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
const currentFiles = new Map(current.candidateFiles.map(row => [row.path, row.sha256]));
|
|
534
|
+
const previousFiles = new Map(previous.candidateFiles.map(row => [row.path, row.sha256]));
|
|
535
|
+
const allPaths = uniqueSorted([...currentFiles.keys(), ...previousFiles.keys()]);
|
|
536
|
+
const addedPaths = allPaths.filter(filePath => (
|
|
537
|
+
currentFiles.has(filePath) && !previousFiles.has(filePath)
|
|
538
|
+
));
|
|
539
|
+
const removedPaths = allPaths.filter(filePath => (
|
|
540
|
+
!currentFiles.has(filePath) && previousFiles.has(filePath)
|
|
541
|
+
));
|
|
542
|
+
const changedPaths = allPaths.filter(filePath => (
|
|
543
|
+
currentFiles.has(filePath)
|
|
544
|
+
&& previousFiles.has(filePath)
|
|
545
|
+
&& currentFiles.get(filePath) !== previousFiles.get(filePath)
|
|
546
|
+
));
|
|
547
|
+
const unchangedPaths = allPaths.filter(filePath => (
|
|
548
|
+
currentFiles.has(filePath)
|
|
549
|
+
&& previousFiles.has(filePath)
|
|
550
|
+
&& currentFiles.get(filePath) === previousFiles.get(filePath)
|
|
551
|
+
));
|
|
552
|
+
return {
|
|
553
|
+
status: addedPaths.length || removedPaths.length || changedPaths.length
|
|
554
|
+
? "changed"
|
|
555
|
+
: "unchanged",
|
|
556
|
+
currentDeliveryId: current.deliveryId,
|
|
557
|
+
previousDeliveryId: previous.deliveryId,
|
|
558
|
+
addedPaths,
|
|
559
|
+
changedPaths,
|
|
560
|
+
removedPaths,
|
|
561
|
+
unchangedPaths,
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
function staleHandoffsForTakeover({ loaded, status, agentId, responsibilities }) {
|
|
566
|
+
const responsibilityIds = new Set(responsibilities.map(row => row.workItemId));
|
|
567
|
+
const pendingEventIds = new Set(status.workItems
|
|
568
|
+
.map(row => row.pendingHandoff?.eventId)
|
|
569
|
+
.filter(Boolean));
|
|
570
|
+
return loaded.events
|
|
571
|
+
.filter(({ event }) => (
|
|
572
|
+
event.type === "handoff_recorded"
|
|
573
|
+
&& !pendingEventIds.has(event.eventId)
|
|
574
|
+
&& (responsibilityIds.has(event.workItemId)
|
|
575
|
+
|| event.fromAgentId === agentId
|
|
576
|
+
|| event.toAgentId === agentId)
|
|
577
|
+
))
|
|
578
|
+
.map(({ event, ref }) => {
|
|
579
|
+
const laterFacts = loaded.events.filter(row => (
|
|
580
|
+
row.event.workItemId === event.workItemId
|
|
581
|
+
&& compareEventFacts(row.event, event) > 0
|
|
582
|
+
&& [
|
|
583
|
+
"agent_assigned",
|
|
584
|
+
"checkpoint_recorded",
|
|
585
|
+
"handoff_recorded",
|
|
586
|
+
"rework_returned",
|
|
587
|
+
"source_delivery_imported",
|
|
588
|
+
].includes(row.event.type)
|
|
589
|
+
));
|
|
590
|
+
let reason = "superseded_by_current_truth";
|
|
591
|
+
if (laterFacts.some(row => row.event.type === "handoff_recorded")) {
|
|
592
|
+
reason = "redirected_by_later_handoff";
|
|
593
|
+
} else if (laterFacts.some(row => (
|
|
594
|
+
row.event.type === "agent_assigned" || row.event.type === "rework_returned"
|
|
595
|
+
))) {
|
|
596
|
+
reason = "superseded_by_assignment";
|
|
597
|
+
} else if (laterFacts.length > 0) {
|
|
598
|
+
reason = "superseded_by_later_work_fact";
|
|
599
|
+
}
|
|
600
|
+
return {
|
|
601
|
+
eventId: event.eventId,
|
|
602
|
+
truthRef: ref,
|
|
603
|
+
assignmentId: event.assignmentId,
|
|
604
|
+
workItemId: event.workItemId,
|
|
605
|
+
fromAgentId: event.fromAgentId,
|
|
606
|
+
toAgentId: event.toAgentId,
|
|
607
|
+
occurredAt: event.occurredAt,
|
|
608
|
+
reason,
|
|
609
|
+
};
|
|
610
|
+
})
|
|
611
|
+
.sort((left, right) => compareIdentifiers(left.eventId, right.eventId));
|
|
612
|
+
}
|
|
613
|
+
|
|
394
614
|
function validateTimestamp(value) {
|
|
395
615
|
if (typeof value !== "string"
|
|
396
616
|
|| !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/u.test(value)
|
|
@@ -405,8 +625,19 @@ function validateEventShape(value, definition) {
|
|
|
405
625
|
throw new Error("Team project event type is invalid.");
|
|
406
626
|
}
|
|
407
627
|
assertAllowedKeys(value, `Team project event ${value.eventId ?? "unknown"}`, EVENT_KEYS[value.type]);
|
|
408
|
-
if (value.schemaVersion
|
|
409
|
-
throw new Error(
|
|
628
|
+
if (![EVENT_SCHEMA, EVENT_SCHEMA_V2, EVENT_SCHEMA_V3].includes(value.schemaVersion)) {
|
|
629
|
+
throw new Error(
|
|
630
|
+
`Team project event schemaVersion must be ${EVENT_SCHEMA}, ${EVENT_SCHEMA_V2}, or ${EVENT_SCHEMA_V3}.`,
|
|
631
|
+
);
|
|
632
|
+
}
|
|
633
|
+
if (value.type === "rework_returned" && value.schemaVersion !== EVENT_SCHEMA_V2) {
|
|
634
|
+
throw new Error(`rework_returned requires schemaVersion ${EVENT_SCHEMA_V2}.`);
|
|
635
|
+
}
|
|
636
|
+
if (value.type === "source_delivery_imported" && value.schemaVersion !== EVENT_SCHEMA_V3) {
|
|
637
|
+
throw new Error(`source_delivery_imported requires schemaVersion ${EVENT_SCHEMA_V3}.`);
|
|
638
|
+
}
|
|
639
|
+
if (value.schemaVersion === EVENT_SCHEMA_V3 && value.type !== "source_delivery_imported") {
|
|
640
|
+
throw new Error(`${EVENT_SCHEMA_V3} is reserved for source_delivery_imported events.`);
|
|
410
641
|
}
|
|
411
642
|
safeIdentifier(value.eventId, "eventId");
|
|
412
643
|
validateTimestamp(value.occurredAt);
|
|
@@ -420,11 +651,19 @@ function validateEventShape(value, definition) {
|
|
|
420
651
|
"agent_assigned",
|
|
421
652
|
"checkpoint_recorded",
|
|
422
653
|
"handoff_recorded",
|
|
654
|
+
"rework_returned",
|
|
655
|
+
"source_delivery_imported",
|
|
423
656
|
"verification_deferred",
|
|
424
657
|
].includes(value.type)) {
|
|
425
658
|
assertWorkItem(value.workItemId);
|
|
426
659
|
}
|
|
427
|
-
if ([
|
|
660
|
+
if ([
|
|
661
|
+
"agent_assigned",
|
|
662
|
+
"checkpoint_recorded",
|
|
663
|
+
"handoff_recorded",
|
|
664
|
+
"rework_returned",
|
|
665
|
+
"source_delivery_imported",
|
|
666
|
+
].includes(value.type)) {
|
|
428
667
|
safeIdentifier(value.assignmentId, "assignmentId");
|
|
429
668
|
}
|
|
430
669
|
if (value.type === "agent_assigned") {
|
|
@@ -466,6 +705,16 @@ function validateEventShape(value, definition) {
|
|
|
466
705
|
nonEmptyString(value.summary, "Handoff summary");
|
|
467
706
|
stringArray(value.evidenceRefs, "Handoff evidenceRefs", { allowEmpty: false });
|
|
468
707
|
nonEmptyString(value.nextAction, "Handoff nextAction");
|
|
708
|
+
} else if (value.type === "rework_returned") {
|
|
709
|
+
safeIdentifier(value.reworkId, "reworkId");
|
|
710
|
+
safeIdentifier(value.failedAssignmentId, "failedAssignmentId");
|
|
711
|
+
safeIdentifier(value.failedCheckpointEventId, "failedCheckpointEventId");
|
|
712
|
+
safeIdentifier(value.fromAgentId, "fromAgentId");
|
|
713
|
+
safeIdentifier(value.reviewerAgentId, "reviewerAgentId");
|
|
714
|
+
safeIdentifier(value.toAgentId, "toAgentId");
|
|
715
|
+
nonEmptyString(value.reason, "Rework reason");
|
|
716
|
+
stringArray(value.evidenceRefs, "Rework evidenceRefs", { allowEmpty: false });
|
|
717
|
+
nonEmptyString(value.nextAction, "Rework nextAction");
|
|
469
718
|
} else if (value.type === "decision_opened") {
|
|
470
719
|
safeIdentifier(value.decisionId, "decisionId");
|
|
471
720
|
safeIdentifier(value.ownerAgentId, "ownerAgentId");
|
|
@@ -532,6 +781,89 @@ function validateEventShape(value, definition) {
|
|
|
532
781
|
stringArray(value.verificationRefs, "Data candidate verificationRefs");
|
|
533
782
|
nullableString(value.outcomeRef, "Data candidate outcomeRef");
|
|
534
783
|
nonEmptyString(value.version, "Data candidate version");
|
|
784
|
+
} else if (value.type === "source_delivery_imported") {
|
|
785
|
+
safeIdentifier(value.projectId, "External delivery projectId");
|
|
786
|
+
sha256Ref(value.projectDefinitionSha256, "External delivery projectDefinitionSha256");
|
|
787
|
+
const definitionBinding = teamProjectDefinitionBindingV1(definition);
|
|
788
|
+
if (value.projectId !== definitionBinding.projectId
|
|
789
|
+
|| value.projectDefinitionSha256 !== definitionBinding.projectDefinitionSha256) {
|
|
790
|
+
throw new Error("External delivery project identity does not match current project truth.");
|
|
791
|
+
}
|
|
792
|
+
safeIdentifier(value.deliveryId, "deliveryId");
|
|
793
|
+
safeIdentifier(value.producerId, "producerId");
|
|
794
|
+
nonEmptyString(value.targetWorkspaceRef, "External delivery targetWorkspaceRef");
|
|
795
|
+
if (!path.isAbsolute(value.targetWorkspaceRef)) {
|
|
796
|
+
throw new Error("External delivery targetWorkspaceRef must be absolute.");
|
|
797
|
+
}
|
|
798
|
+
if (typeof value.targetHead !== "string"
|
|
799
|
+
|| !/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/u.test(value.targetHead)) {
|
|
800
|
+
throw new Error("External delivery targetHead is invalid.");
|
|
801
|
+
}
|
|
802
|
+
if ((value.entrySnapshotRef === null) !== (value.entrySnapshotSha256 === null)) {
|
|
803
|
+
throw new Error("External delivery entry snapshot ref and hash must be supplied together.");
|
|
804
|
+
}
|
|
805
|
+
if (value.entrySnapshotRef !== null) {
|
|
806
|
+
nonEmptyString(value.entrySnapshotRef, "External delivery entrySnapshotRef");
|
|
807
|
+
sha256Ref(value.entrySnapshotSha256, "External delivery entrySnapshotSha256");
|
|
808
|
+
}
|
|
809
|
+
sha256Ref(value.deliveryPacketSha256, "External delivery packet hash");
|
|
810
|
+
if (typeof value.deliveryCandidateFingerprint !== "string"
|
|
811
|
+
|| !/^[a-f0-9]{64}$/u.test(value.deliveryCandidateFingerprint)) {
|
|
812
|
+
throw new Error("External delivery candidate fingerprint is invalid.");
|
|
813
|
+
}
|
|
814
|
+
sha256Ref(value.workspaceSnapshotFingerprint, "External delivery workspace snapshot fingerprint");
|
|
815
|
+
if (!["candidate_only", "attested", "attested_with_non_source_changes"].includes(value.overlayStatus)) {
|
|
816
|
+
throw new Error("External delivery overlayStatus is invalid.");
|
|
817
|
+
}
|
|
818
|
+
if (!Array.isArray(value.candidateFiles) || value.candidateFiles.length === 0) {
|
|
819
|
+
throw new Error("External delivery candidateFiles must contain at least one file.");
|
|
820
|
+
}
|
|
821
|
+
const candidateFiles = value.candidateFiles.map((row) => {
|
|
822
|
+
assertAllowedKeys(row, "External delivery candidate file", ["path", "sha256"]);
|
|
823
|
+
const filePath = safeSourcePath(row.path, "External delivery candidate file path");
|
|
824
|
+
if (typeof row.sha256 !== "string" || !/^[a-f0-9]{64}$/u.test(row.sha256)) {
|
|
825
|
+
throw new Error(`External delivery candidate file hash is invalid: ${filePath}.`);
|
|
826
|
+
}
|
|
827
|
+
return { path: filePath, sha256: row.sha256 };
|
|
828
|
+
});
|
|
829
|
+
if (new Set(candidateFiles.map(row => row.path)).size !== candidateFiles.length
|
|
830
|
+
|| canonical([...candidateFiles].sort((left, right) => (
|
|
831
|
+
compareIdentifiers(left.path, right.path)
|
|
832
|
+
)))
|
|
833
|
+
!== canonical(candidateFiles)) {
|
|
834
|
+
throw new Error("External delivery candidateFiles must be uniquely sorted by path.");
|
|
835
|
+
}
|
|
836
|
+
if (canonicalSourceFingerprint(Object.fromEntries(
|
|
837
|
+
candidateFiles.map(row => [row.path, row.sha256]),
|
|
838
|
+
)) !== value.deliveryCandidateFingerprint) {
|
|
839
|
+
throw new Error("External delivery candidate fingerprint does not match its file map.");
|
|
840
|
+
}
|
|
841
|
+
for (const [field, label] of [
|
|
842
|
+
["changedPaths", "External delivery changedPaths"],
|
|
843
|
+
["toolCachePaths", "External delivery toolCachePaths"],
|
|
844
|
+
["dependencyEnvironmentPaths", "External delivery dependencyEnvironmentPaths"],
|
|
845
|
+
["unchangedPacketPaths", "External delivery unchangedPacketPaths"],
|
|
846
|
+
["outOfScopeSourcePaths", "External delivery outOfScopeSourcePaths"],
|
|
847
|
+
]) {
|
|
848
|
+
const paths = stringArray(value[field], label);
|
|
849
|
+
for (const filePath of paths) safeSourcePath(filePath, label);
|
|
850
|
+
if (canonical([...paths].sort()) !== canonical(paths)) {
|
|
851
|
+
throw new Error(`${label} must be sorted.`);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
if (value.outOfScopeSourcePaths.length !== 0) {
|
|
855
|
+
throw new Error("An imported external delivery cannot contain out-of-scope source paths.");
|
|
856
|
+
}
|
|
857
|
+
stringArray(value.proven, "External delivery proven statements", { allowEmpty: false });
|
|
858
|
+
stringArray(value.notProven, "External delivery notProven statements", { allowEmpty: false });
|
|
859
|
+
if (!validWorkspaceSnapshotShape(value.targetSnapshot)
|
|
860
|
+
|| !quickSnapshotFingerprintValid(value.targetSnapshot)
|
|
861
|
+
|| value.targetSnapshot.sourceFingerprint !== value.workspaceSnapshotFingerprint) {
|
|
862
|
+
throw new Error("External delivery targetSnapshot identity is invalid.");
|
|
863
|
+
}
|
|
864
|
+
if (value.targetWritePerformed !== false || value.authorizationGranted !== false) {
|
|
865
|
+
throw new Error("External delivery cannot grant authority or write the target workspace.");
|
|
866
|
+
}
|
|
535
867
|
}
|
|
536
868
|
return structuredClone(value);
|
|
537
869
|
}
|
|
@@ -541,12 +873,16 @@ function projectEvents(definition, records) {
|
|
|
541
873
|
const assignmentHistory = new Map(definition.workItems.map(row => [row.id, []]));
|
|
542
874
|
const checkpoints = new Map();
|
|
543
875
|
const handoffs = new Map();
|
|
876
|
+
const reworks = new Map();
|
|
544
877
|
const decisions = new Map();
|
|
545
878
|
const gatePasses = new Map();
|
|
546
879
|
const verifications = new Map();
|
|
547
880
|
const dataCandidates = [];
|
|
881
|
+
const sourceDeliveries = [];
|
|
548
882
|
const seenAssignments = new Set();
|
|
883
|
+
const seenReworks = new Set();
|
|
549
884
|
const seenDecisions = new Set();
|
|
885
|
+
const seenDeliveries = new Set();
|
|
550
886
|
|
|
551
887
|
for (const { event, ref } of records) {
|
|
552
888
|
validateEventShape(event, definition);
|
|
@@ -561,6 +897,38 @@ function projectEvents(definition, records) {
|
|
|
561
897
|
checkpoints.set(event.workItemId, { ...event, ref });
|
|
562
898
|
} else if (event.type === "handoff_recorded") {
|
|
563
899
|
handoffs.set(event.workItemId, { ...event, ref });
|
|
900
|
+
} else if (event.type === "rework_returned") {
|
|
901
|
+
if (seenReworks.has(event.reworkId)) {
|
|
902
|
+
throw new Error(`Duplicate reworkId ${event.reworkId}.`);
|
|
903
|
+
}
|
|
904
|
+
if (seenAssignments.has(event.assignmentId)) {
|
|
905
|
+
throw new Error(`Duplicate assignmentId ${event.assignmentId}.`);
|
|
906
|
+
}
|
|
907
|
+
const current = assignments.get(event.workItemId) ?? null;
|
|
908
|
+
const failure = checkpoints.get(event.workItemId) ?? null;
|
|
909
|
+
const latestFact = latestEventFact(failure, handoffs.get(event.workItemId) ?? null);
|
|
910
|
+
if (!current
|
|
911
|
+
|| current.assignmentId !== event.failedAssignmentId
|
|
912
|
+
|| current.agentId !== event.fromAgentId
|
|
913
|
+
|| failure?.eventId !== event.failedCheckpointEventId
|
|
914
|
+
|| failure.assignmentId !== event.failedAssignmentId
|
|
915
|
+
|| failure.state !== "failed"
|
|
916
|
+
|| latestFact?.eventId !== failure.eventId) {
|
|
917
|
+
throw new Error(
|
|
918
|
+
`Rework ${event.reworkId} does not bind the current failed checkpoint and assignment.`,
|
|
919
|
+
);
|
|
920
|
+
}
|
|
921
|
+
seenReworks.add(event.reworkId);
|
|
922
|
+
seenAssignments.add(event.assignmentId);
|
|
923
|
+
const assignment = {
|
|
924
|
+
...event,
|
|
925
|
+
agentId: event.toAgentId,
|
|
926
|
+
supersedesAssignmentId: event.failedAssignmentId,
|
|
927
|
+
ref,
|
|
928
|
+
};
|
|
929
|
+
assignments.set(event.workItemId, assignment);
|
|
930
|
+
assignmentHistory.get(event.workItemId).push(assignment);
|
|
931
|
+
reworks.set(event.workItemId, { ...event, ref });
|
|
564
932
|
} else if (event.type === "decision_opened") {
|
|
565
933
|
if (seenDecisions.has(event.decisionId)) {
|
|
566
934
|
throw new Error(`Duplicate decision_opened event for ${event.decisionId}.`);
|
|
@@ -608,6 +976,25 @@ function projectEvents(definition, records) {
|
|
|
608
976
|
throw new Error(`Duplicate data candidate ${event.candidateId}.`);
|
|
609
977
|
}
|
|
610
978
|
dataCandidates.push({ ...event, ref });
|
|
979
|
+
} else if (event.type === "source_delivery_imported") {
|
|
980
|
+
if (seenDeliveries.has(event.deliveryId)) {
|
|
981
|
+
throw new Error(`Duplicate external source delivery ${event.deliveryId}.`);
|
|
982
|
+
}
|
|
983
|
+
const current = assignments.get(event.workItemId) ?? null;
|
|
984
|
+
if (current?.assignmentId !== event.assignmentId) {
|
|
985
|
+
throw new Error(
|
|
986
|
+
`External source delivery ${event.deliveryId} does not bind the current assignment.`,
|
|
987
|
+
);
|
|
988
|
+
}
|
|
989
|
+
const currentCheckpoint = checkpoints.get(event.workItemId) ?? null;
|
|
990
|
+
if (currentCheckpoint?.assignmentId === current.assignmentId
|
|
991
|
+
&& ["completed", "failed"].includes(currentCheckpoint.state)) {
|
|
992
|
+
throw new Error(
|
|
993
|
+
`External source delivery ${event.deliveryId} cannot attach to terminal WorkItem truth.`,
|
|
994
|
+
);
|
|
995
|
+
}
|
|
996
|
+
seenDeliveries.add(event.deliveryId);
|
|
997
|
+
sourceDeliveries.push({ ...event, ref });
|
|
611
998
|
}
|
|
612
999
|
}
|
|
613
1000
|
return {
|
|
@@ -615,10 +1002,12 @@ function projectEvents(definition, records) {
|
|
|
615
1002
|
assignmentHistory,
|
|
616
1003
|
checkpoints,
|
|
617
1004
|
handoffs,
|
|
1005
|
+
reworks,
|
|
618
1006
|
decisions,
|
|
619
1007
|
gatePasses,
|
|
620
1008
|
verifications,
|
|
621
1009
|
dataCandidates,
|
|
1010
|
+
sourceDeliveries,
|
|
622
1011
|
};
|
|
623
1012
|
}
|
|
624
1013
|
|
|
@@ -945,13 +1334,27 @@ function projectStatus(definition, records, workspaceRoot = null) {
|
|
|
945
1334
|
const assignment = state.assignments.get(item.id) ?? null;
|
|
946
1335
|
const checkpoint = state.checkpoints.get(item.id) ?? null;
|
|
947
1336
|
const handoff = state.handoffs.get(item.id) ?? null;
|
|
948
|
-
const
|
|
1337
|
+
const rework = state.reworks.get(item.id) ?? null;
|
|
1338
|
+
const sourceDeliveries = state.sourceDeliveries.filter(row => row.workItemId === item.id);
|
|
1339
|
+
const latestSourceDeliveryFact = sourceDeliveries.at(-1) ?? null;
|
|
1340
|
+
const latestSourceDelivery = sourceDeliveryProjection(sourceDeliveries.at(-1) ?? null);
|
|
1341
|
+
const currentRework = rework?.assignmentId === assignment?.assignmentId ? rework : null;
|
|
1342
|
+
const latestFact = latestEventFact(latestEventFact(checkpoint, handoff), currentRework);
|
|
949
1343
|
const activeHandoff = handoff
|
|
950
1344
|
&& handoff.assignmentId === assignment?.assignmentId
|
|
951
1345
|
&& handoff.fromAgentId === assignment?.agentId
|
|
952
1346
|
? handoff
|
|
953
1347
|
: null;
|
|
954
|
-
const
|
|
1348
|
+
const latestHandoffFact = latestEventFact(activeHandoff, latestSourceDeliveryFact);
|
|
1349
|
+
const handoffSupersededByDelivery = activeHandoff
|
|
1350
|
+
&& latestFact?.eventId === activeHandoff.eventId
|
|
1351
|
+
&& latestHandoffFact?.eventId !== activeHandoff.eventId;
|
|
1352
|
+
const projectedFact = handoffSupersededByDelivery
|
|
1353
|
+
? latestEventFact(checkpoint, currentRework)
|
|
1354
|
+
: latestFact;
|
|
1355
|
+
const pendingHandoff = activeHandoff
|
|
1356
|
+
&& latestFact?.eventId === activeHandoff.eventId
|
|
1357
|
+
&& latestHandoffFact?.eventId === activeHandoff.eventId
|
|
955
1358
|
? pendingHandoffProjection(activeHandoff)
|
|
956
1359
|
: null;
|
|
957
1360
|
const executionTruth = executionTruthFor(workspaceRoot, assignment);
|
|
@@ -959,7 +1362,7 @@ function projectStatus(definition, records, workspaceRoot = null) {
|
|
|
959
1362
|
let status = "planned";
|
|
960
1363
|
if (assignment) status = unresolvedDependencies.length > 0 ? "waiting_dependency" : "active";
|
|
961
1364
|
if (pendingHandoff) status = "planned";
|
|
962
|
-
if (
|
|
1365
|
+
if (projectedFact?.type === "checkpoint_recorded") status = checkpoint.state;
|
|
963
1366
|
if (executionTruth && (executionTruth.status !== "bound"
|
|
964
1367
|
|| (executionTruth.lifecycle === "closed"
|
|
965
1368
|
&& executionTruth.closeoutDecision !== "accepted"))) {
|
|
@@ -973,7 +1376,8 @@ function projectStatus(definition, records, workspaceRoot = null) {
|
|
|
973
1376
|
total: item.measurable.total,
|
|
974
1377
|
}
|
|
975
1378
|
: { kind: "state_only" };
|
|
976
|
-
const
|
|
1379
|
+
const assignmentEvents = state.assignmentHistory.get(item.id);
|
|
1380
|
+
const history = assignmentEvents.map(row => ({
|
|
977
1381
|
assignmentId: row.assignmentId,
|
|
978
1382
|
agentId: row.agentId,
|
|
979
1383
|
occurredAt: row.occurredAt,
|
|
@@ -986,7 +1390,18 @@ function projectStatus(definition, records, workspaceRoot = null) {
|
|
|
986
1390
|
...history.map(row => row.truthRef),
|
|
987
1391
|
...(checkpoint ? [checkpoint.ref] : []),
|
|
988
1392
|
...(handoff ? [handoff.ref] : []),
|
|
1393
|
+
...(rework ? [rework.ref] : []),
|
|
1394
|
+
...sourceDeliveries.map(row => row.ref),
|
|
989
1395
|
];
|
|
1396
|
+
const projectedRework = reworkProjection(
|
|
1397
|
+
currentRework,
|
|
1398
|
+
currentRework
|
|
1399
|
+
? assignmentEvents.findIndex(row => row.assignmentId === currentRework.assignmentId) + 1
|
|
1400
|
+
: null,
|
|
1401
|
+
);
|
|
1402
|
+
const latestSummary = projectedFact?.type === "rework_returned"
|
|
1403
|
+
? projectedFact.reason
|
|
1404
|
+
: projectedFact?.summary ?? null;
|
|
990
1405
|
return {
|
|
991
1406
|
workItemId: item.id,
|
|
992
1407
|
title: item.title,
|
|
@@ -1000,16 +1415,18 @@ function projectStatus(definition, records, workspaceRoot = null) {
|
|
|
1000
1415
|
executionTruth,
|
|
1001
1416
|
dependencies: [...item.dependencies],
|
|
1002
1417
|
unresolvedDependencies,
|
|
1003
|
-
blockers:
|
|
1418
|
+
blockers: projectedFact?.type === "checkpoint_recorded"
|
|
1004
1419
|
? checkpoint.blockerRefs
|
|
1005
1420
|
: unresolvedDependencies.map(id => `work-item:${id}`),
|
|
1006
|
-
decisionRefs:
|
|
1421
|
+
decisionRefs: projectedFact?.type === "checkpoint_recorded" ? checkpoint.decisionRefs : [],
|
|
1007
1422
|
progress: measured,
|
|
1008
|
-
summary:
|
|
1009
|
-
nextAction:
|
|
1423
|
+
summary: latestSummary,
|
|
1424
|
+
nextAction: projectedFact?.nextAction ?? null,
|
|
1010
1425
|
pendingHandoff,
|
|
1426
|
+
rework: projectedRework,
|
|
1427
|
+
latestSourceDelivery,
|
|
1011
1428
|
lastCheckpointAt: checkpoint?.occurredAt ?? null,
|
|
1012
|
-
evidenceRefs:
|
|
1429
|
+
evidenceRefs: projectedFact?.evidenceRefs ?? [],
|
|
1013
1430
|
assignmentHistory: history,
|
|
1014
1431
|
truthRefs,
|
|
1015
1432
|
};
|
|
@@ -1212,6 +1629,7 @@ function projectStatus(definition, records, workspaceRoot = null) {
|
|
|
1212
1629
|
truthRefs: [row.ref],
|
|
1213
1630
|
};
|
|
1214
1631
|
});
|
|
1632
|
+
const sourceDeliveries = state.sourceDeliveries.map(sourceDeliveryProjection);
|
|
1215
1633
|
let overall = "planned";
|
|
1216
1634
|
if (workItems.every(row => row.status === "completed")
|
|
1217
1635
|
&& integrationGates.every(row => row.status === "passed")
|
|
@@ -1219,7 +1637,7 @@ function projectStatus(definition, records, workspaceRoot = null) {
|
|
|
1219
1637
|
else if (openDecisions.length > 0 || workItems.some(row => row.status === "failed")) overall = "active_with_blockers";
|
|
1220
1638
|
else if (workItems.some(row => row.status !== "planned" || row.pendingHandoff !== null)) overall = "active";
|
|
1221
1639
|
const status = {
|
|
1222
|
-
schemaVersion:
|
|
1640
|
+
schemaVersion: STATUS_SCHEMA_V2,
|
|
1223
1641
|
status: "team_project_status",
|
|
1224
1642
|
projectId: definition.projectId,
|
|
1225
1643
|
objective: definition.objective,
|
|
@@ -1237,6 +1655,7 @@ function projectStatus(definition, records, workspaceRoot = null) {
|
|
|
1237
1655
|
openDeferredVerificationIds,
|
|
1238
1656
|
integrationGates,
|
|
1239
1657
|
dataCandidates,
|
|
1658
|
+
sourceDeliveries,
|
|
1240
1659
|
projectTruthHash: sha256(canonical({ definition, events: records.map(row => row.event) })),
|
|
1241
1660
|
authorizationGranted: false,
|
|
1242
1661
|
};
|
|
@@ -1339,6 +1758,42 @@ function validateEventAgainstState(event, definition, records, workspaceRoot) {
|
|
|
1339
1758
|
|| current.agentId !== normalized.fromAgentId) {
|
|
1340
1759
|
throw new Error("Handoff must bind the current assignment and Agent.");
|
|
1341
1760
|
}
|
|
1761
|
+
} else if (normalized.type === "rework_returned") {
|
|
1762
|
+
const current = state.assignments.get(normalized.workItemId) ?? null;
|
|
1763
|
+
const failure = state.checkpoints.get(normalized.workItemId) ?? null;
|
|
1764
|
+
const projected = status.workItems.find(row => row.workItemId === normalized.workItemId);
|
|
1765
|
+
const latestFact = latestEventFact(
|
|
1766
|
+
failure,
|
|
1767
|
+
state.handoffs.get(normalized.workItemId) ?? null,
|
|
1768
|
+
);
|
|
1769
|
+
if (projected?.status !== "failed"
|
|
1770
|
+
|| !current
|
|
1771
|
+
|| current.assignmentId !== normalized.failedAssignmentId
|
|
1772
|
+
|| current.agentId !== normalized.fromAgentId
|
|
1773
|
+
|| failure?.eventId !== normalized.failedCheckpointEventId
|
|
1774
|
+
|| failure.assignmentId !== normalized.failedAssignmentId
|
|
1775
|
+
|| failure.state !== "failed"
|
|
1776
|
+
|| latestFact?.eventId !== failure.eventId) {
|
|
1777
|
+
throw new Error(
|
|
1778
|
+
"reject-and-return requires one current failed checkpoint bound to the current assignment.",
|
|
1779
|
+
);
|
|
1780
|
+
}
|
|
1781
|
+
if (state.assignmentHistory.get(normalized.workItemId).some(
|
|
1782
|
+
row => row.assignmentId === normalized.assignmentId,
|
|
1783
|
+
)) {
|
|
1784
|
+
throw new Error(`Assignment ${normalized.assignmentId} already exists.`);
|
|
1785
|
+
}
|
|
1786
|
+
for (const otherProjection of status.workItems) {
|
|
1787
|
+
if (otherProjection.workItemId === normalized.workItemId
|
|
1788
|
+
|| otherProjection.assignmentId === null
|
|
1789
|
+
|| ["completed", "failed"].includes(otherProjection.status)) continue;
|
|
1790
|
+
const other = definition.workItems.find(row => row.id === otherProjection.workItemId);
|
|
1791
|
+
if (pathsOverlap(workItem.ownedPaths, other.ownedPaths)) {
|
|
1792
|
+
throw new Error(
|
|
1793
|
+
`Owned paths overlap active assignment ${otherProjection.assignmentId}: ${normalized.workItemId} <> ${otherProjection.workItemId}.`,
|
|
1794
|
+
);
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1342
1797
|
} else if (normalized.type === "decision_opened") {
|
|
1343
1798
|
if (state.decisions.has(normalized.decisionId)) throw new Error(`Decision ${normalized.decisionId} already exists.`);
|
|
1344
1799
|
} else if (normalized.type === "decision_resolved") {
|
|
@@ -1378,6 +1833,18 @@ function validateEventAgainstState(event, definition, records, workspaceRoot) {
|
|
|
1378
1833
|
if (state.dataCandidates.some(row => row.candidateId === normalized.candidateId)) {
|
|
1379
1834
|
throw new Error(`Data candidate ${normalized.candidateId} already exists.`);
|
|
1380
1835
|
}
|
|
1836
|
+
} else if (normalized.type === "source_delivery_imported") {
|
|
1837
|
+
if (state.sourceDeliveries.some(row => row.deliveryId === normalized.deliveryId)) {
|
|
1838
|
+
throw new Error(`External source delivery ${normalized.deliveryId} already exists.`);
|
|
1839
|
+
}
|
|
1840
|
+
const current = state.assignments.get(normalized.workItemId) ?? null;
|
|
1841
|
+
if (current?.assignmentId !== normalized.assignmentId) {
|
|
1842
|
+
throw new Error("External source delivery must bind the current assignment.");
|
|
1843
|
+
}
|
|
1844
|
+
const projected = status.workItems.find(row => row.workItemId === normalized.workItemId);
|
|
1845
|
+
if (["completed", "failed"].includes(projected?.status)) {
|
|
1846
|
+
throw new Error("External source delivery requires a non-terminal WorkItem.");
|
|
1847
|
+
}
|
|
1381
1848
|
}
|
|
1382
1849
|
return normalized;
|
|
1383
1850
|
}
|
|
@@ -1416,52 +1883,55 @@ export function initializeTeamProjectV1({ workspaceRoot, definition }) {
|
|
|
1416
1883
|
};
|
|
1417
1884
|
}
|
|
1418
1885
|
|
|
1419
|
-
|
|
1420
|
-
const
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
const
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
const existing = readRegularJson(eventPath, `Team project event ${shaped.eventId}`);
|
|
1427
|
-
if (canonical(existing) !== canonical(shaped)) {
|
|
1428
|
-
throw new Error(`Team project immutable event differs: ${shaped.eventId}.`);
|
|
1429
|
-
}
|
|
1430
|
-
return {
|
|
1431
|
-
status: "team_project_event_recorded",
|
|
1432
|
-
exitCode: 0,
|
|
1433
|
-
eventId: shaped.eventId,
|
|
1434
|
-
eventPath: relativeToWorkspace(refreshed.root, eventPath),
|
|
1435
|
-
eventSha256: sha256(canonical(shaped)),
|
|
1436
|
-
resumed: true,
|
|
1437
|
-
authorizationGranted: false,
|
|
1438
|
-
};
|
|
1439
|
-
}
|
|
1440
|
-
const latest = refreshed.events.at(-1)?.event ?? null;
|
|
1441
|
-
if (latest && (shaped.occurredAt < latest.occurredAt
|
|
1442
|
-
|| (shaped.occurredAt === latest.occurredAt
|
|
1443
|
-
&& shaped.eventId.localeCompare(latest.eventId) <= 0))) {
|
|
1444
|
-
throw new Error(
|
|
1445
|
-
`Team project event must sort after the latest event ${latest.eventId}.`,
|
|
1446
|
-
);
|
|
1886
|
+
function appendTeamProjectEventWithinLock(refreshed, event) {
|
|
1887
|
+
const shaped = validateEventShape(event, refreshed.definition);
|
|
1888
|
+
const eventPath = path.join(refreshed.projectRoot, "events", `${shaped.eventId}.json`);
|
|
1889
|
+
if (existsSync(eventPath)) {
|
|
1890
|
+
const existing = readRegularJson(eventPath, `Team project event ${shaped.eventId}`);
|
|
1891
|
+
if (canonical(existing) !== canonical(shaped)) {
|
|
1892
|
+
throw new Error(`Team project immutable event differs: ${shaped.eventId}.`);
|
|
1447
1893
|
}
|
|
1448
|
-
const normalized = validateEventAgainstState(
|
|
1449
|
-
shaped,
|
|
1450
|
-
refreshed.definition,
|
|
1451
|
-
refreshed.events,
|
|
1452
|
-
refreshed.root,
|
|
1453
|
-
);
|
|
1454
|
-
writeJsonExclusiveAtomically(eventPath, normalized);
|
|
1455
1894
|
return {
|
|
1456
1895
|
status: "team_project_event_recorded",
|
|
1457
1896
|
exitCode: 0,
|
|
1458
|
-
eventId:
|
|
1897
|
+
eventId: shaped.eventId,
|
|
1459
1898
|
eventPath: relativeToWorkspace(refreshed.root, eventPath),
|
|
1460
|
-
eventSha256: sha256(canonical(
|
|
1461
|
-
resumed:
|
|
1899
|
+
eventSha256: sha256(canonical(shaped)),
|
|
1900
|
+
resumed: true,
|
|
1462
1901
|
authorizationGranted: false,
|
|
1463
1902
|
};
|
|
1464
|
-
}
|
|
1903
|
+
}
|
|
1904
|
+
const latest = refreshed.events.at(-1)?.event ?? null;
|
|
1905
|
+
if (latest && (shaped.occurredAt < latest.occurredAt
|
|
1906
|
+
|| (shaped.occurredAt === latest.occurredAt
|
|
1907
|
+
&& shaped.eventId.localeCompare(latest.eventId) <= 0))) {
|
|
1908
|
+
throw new Error(
|
|
1909
|
+
`Team project event must sort after the latest event ${latest.eventId}.`,
|
|
1910
|
+
);
|
|
1911
|
+
}
|
|
1912
|
+
const normalized = validateEventAgainstState(
|
|
1913
|
+
shaped,
|
|
1914
|
+
refreshed.definition,
|
|
1915
|
+
refreshed.events,
|
|
1916
|
+
refreshed.root,
|
|
1917
|
+
);
|
|
1918
|
+
writeJsonExclusiveAtomically(eventPath, normalized);
|
|
1919
|
+
return {
|
|
1920
|
+
status: "team_project_event_recorded",
|
|
1921
|
+
exitCode: 0,
|
|
1922
|
+
eventId: normalized.eventId,
|
|
1923
|
+
eventPath: relativeToWorkspace(refreshed.root, eventPath),
|
|
1924
|
+
eventSha256: sha256(canonical(normalized)),
|
|
1925
|
+
resumed: false,
|
|
1926
|
+
authorizationGranted: false,
|
|
1927
|
+
};
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
export function appendTeamProjectEventV1({ workspaceRoot, event }) {
|
|
1931
|
+
const loaded = loadProject(workspaceRoot);
|
|
1932
|
+
return withProjectLock(loaded.projectRoot, () => (
|
|
1933
|
+
appendTeamProjectEventWithinLock(loadProject(workspaceRoot), event)
|
|
1934
|
+
));
|
|
1465
1935
|
}
|
|
1466
1936
|
|
|
1467
1937
|
export function assignTeamProjectV1({
|
|
@@ -1584,6 +2054,133 @@ export function handoffTeamProjectV1({
|
|
|
1584
2054
|
};
|
|
1585
2055
|
}
|
|
1586
2056
|
|
|
2057
|
+
export function rejectAndReturnTeamProjectV1({
|
|
2058
|
+
workspaceRoot,
|
|
2059
|
+
reworkId,
|
|
2060
|
+
occurredAt,
|
|
2061
|
+
workItemId,
|
|
2062
|
+
reviewerAgentId,
|
|
2063
|
+
toAgentId,
|
|
2064
|
+
reason,
|
|
2065
|
+
nextAction,
|
|
2066
|
+
evidenceRefs,
|
|
2067
|
+
}) {
|
|
2068
|
+
safeIdentifier(reworkId, "reworkId");
|
|
2069
|
+
const eventId = `rework-returned-${reworkId}`;
|
|
2070
|
+
const assignmentId = `rework-${reworkId}`;
|
|
2071
|
+
const loaded = loadProject(workspaceRoot);
|
|
2072
|
+
return withProjectLock(loaded.projectRoot, () => {
|
|
2073
|
+
const refreshed = loadProject(workspaceRoot);
|
|
2074
|
+
const existing = refreshed.events.find(row => row.event.eventId === eventId)?.event ?? null;
|
|
2075
|
+
let event;
|
|
2076
|
+
if (existing) {
|
|
2077
|
+
event = {
|
|
2078
|
+
schemaVersion: EVENT_SCHEMA_V2,
|
|
2079
|
+
eventId,
|
|
2080
|
+
type: "rework_returned",
|
|
2081
|
+
occurredAt,
|
|
2082
|
+
reworkId,
|
|
2083
|
+
assignmentId,
|
|
2084
|
+
failedAssignmentId: existing.failedAssignmentId,
|
|
2085
|
+
failedCheckpointEventId: existing.failedCheckpointEventId,
|
|
2086
|
+
workItemId,
|
|
2087
|
+
fromAgentId: existing.fromAgentId,
|
|
2088
|
+
reviewerAgentId,
|
|
2089
|
+
toAgentId,
|
|
2090
|
+
reason,
|
|
2091
|
+
evidenceRefs,
|
|
2092
|
+
nextAction,
|
|
2093
|
+
};
|
|
2094
|
+
} else {
|
|
2095
|
+
const state = projectEvents(refreshed.definition, refreshed.events);
|
|
2096
|
+
const status = projectStatus(refreshed.definition, refreshed.events, refreshed.root);
|
|
2097
|
+
const current = state.assignments.get(workItemId) ?? null;
|
|
2098
|
+
const failure = state.checkpoints.get(workItemId) ?? null;
|
|
2099
|
+
const projected = status.workItems.find(row => row.workItemId === workItemId) ?? null;
|
|
2100
|
+
const latestFact = latestEventFact(failure, state.handoffs.get(workItemId) ?? null);
|
|
2101
|
+
if (projected?.status !== "failed"
|
|
2102
|
+
|| !current
|
|
2103
|
+
|| failure?.state !== "failed"
|
|
2104
|
+
|| failure.assignmentId !== current.assignmentId
|
|
2105
|
+
|| latestFact?.eventId !== failure.eventId) {
|
|
2106
|
+
throw new Error(
|
|
2107
|
+
"reject-and-return requires one current failed checkpoint bound to the current assignment.",
|
|
2108
|
+
);
|
|
2109
|
+
}
|
|
2110
|
+
event = {
|
|
2111
|
+
schemaVersion: EVENT_SCHEMA_V2,
|
|
2112
|
+
eventId,
|
|
2113
|
+
type: "rework_returned",
|
|
2114
|
+
occurredAt,
|
|
2115
|
+
reworkId,
|
|
2116
|
+
assignmentId,
|
|
2117
|
+
failedAssignmentId: current.assignmentId,
|
|
2118
|
+
failedCheckpointEventId: failure.eventId,
|
|
2119
|
+
workItemId,
|
|
2120
|
+
fromAgentId: current.agentId,
|
|
2121
|
+
reviewerAgentId,
|
|
2122
|
+
toAgentId,
|
|
2123
|
+
reason,
|
|
2124
|
+
evidenceRefs,
|
|
2125
|
+
nextAction,
|
|
2126
|
+
};
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
const recorded = appendTeamProjectEventWithinLock(refreshed, event);
|
|
2130
|
+
const projectedEvents = recorded.resumed
|
|
2131
|
+
? refreshed.events
|
|
2132
|
+
: [
|
|
2133
|
+
...refreshed.events,
|
|
2134
|
+
{ event, ref: `project/events/${event.eventId}.json` },
|
|
2135
|
+
];
|
|
2136
|
+
const status = projectStatus(refreshed.definition, projectedEvents, refreshed.root);
|
|
2137
|
+
const projectedState = projectEvents(refreshed.definition, projectedEvents);
|
|
2138
|
+
const current = status.workItems.find(row => row.workItemId === workItemId) ?? null;
|
|
2139
|
+
const latestFact = latestEventFact(
|
|
2140
|
+
latestEventFact(
|
|
2141
|
+
projectedState.checkpoints.get(workItemId) ?? null,
|
|
2142
|
+
projectedState.handoffs.get(workItemId) ?? null,
|
|
2143
|
+
),
|
|
2144
|
+
projectedState.reworks.get(workItemId) ?? null,
|
|
2145
|
+
);
|
|
2146
|
+
if (current?.assignmentId !== assignmentId
|
|
2147
|
+
|| current?.rework?.eventId !== eventId
|
|
2148
|
+
|| latestFact?.eventId !== eventId) {
|
|
2149
|
+
throw new Error(
|
|
2150
|
+
`Cannot resume historical/superseded rework ${reworkId}; `
|
|
2151
|
+
+ `current assignment=${current?.assignmentId ?? "none"}, `
|
|
2152
|
+
+ `current owner=${current?.agentId ?? "none"}.`,
|
|
2153
|
+
);
|
|
2154
|
+
}
|
|
2155
|
+
return {
|
|
2156
|
+
status: "team_project_rework",
|
|
2157
|
+
reworkId,
|
|
2158
|
+
assignmentId,
|
|
2159
|
+
failedAssignmentId: event.failedAssignmentId,
|
|
2160
|
+
failedCheckpointEventId: event.failedCheckpointEventId,
|
|
2161
|
+
workItemId,
|
|
2162
|
+
fromAgentId: event.fromAgentId,
|
|
2163
|
+
reviewerAgentId,
|
|
2164
|
+
toAgentId,
|
|
2165
|
+
reason,
|
|
2166
|
+
nextAction: current.nextAction,
|
|
2167
|
+
eventId: recorded.eventId,
|
|
2168
|
+
eventPath: recorded.eventPath,
|
|
2169
|
+
eventSha256: recorded.eventSha256,
|
|
2170
|
+
resumed: recorded.resumed,
|
|
2171
|
+
reworkAttempt: current.rework.attempt,
|
|
2172
|
+
projectTruthHash: status.projectTruthHash,
|
|
2173
|
+
overall: status.overall,
|
|
2174
|
+
projectNextAction: status.nextAction,
|
|
2175
|
+
projectNextActorId: status.nextActorId,
|
|
2176
|
+
gitWritePerformed: false,
|
|
2177
|
+
releaseWritePerformed: false,
|
|
2178
|
+
authorizationGranted: false,
|
|
2179
|
+
exitCode: 0,
|
|
2180
|
+
};
|
|
2181
|
+
});
|
|
2182
|
+
}
|
|
2183
|
+
|
|
1587
2184
|
export function openTeamProjectDecisionV1({
|
|
1588
2185
|
workspaceRoot,
|
|
1589
2186
|
decisionId,
|
|
@@ -1890,7 +2487,28 @@ export function checkpointTeamProjectV1({
|
|
|
1890
2487
|
};
|
|
1891
2488
|
}
|
|
1892
2489
|
|
|
2490
|
+
function legacyWorkItemProjection(workItem) {
|
|
2491
|
+
if (workItem === null) return null;
|
|
2492
|
+
const { latestSourceDelivery: _latestSourceDelivery, ...legacy } = workItem;
|
|
2493
|
+
return legacy;
|
|
2494
|
+
}
|
|
2495
|
+
|
|
1893
2496
|
export function readTeamProjectStatusV1({ workspaceRoot }) {
|
|
2497
|
+
const loaded = loadProject(workspaceRoot);
|
|
2498
|
+
const { sourceDeliveries: _sourceDeliveries, ...legacy } = projectStatus(
|
|
2499
|
+
loaded.definition,
|
|
2500
|
+
loaded.events,
|
|
2501
|
+
loaded.root,
|
|
2502
|
+
);
|
|
2503
|
+
return {
|
|
2504
|
+
...legacy,
|
|
2505
|
+
schemaVersion: STATUS_SCHEMA_V1,
|
|
2506
|
+
workItems: legacy.workItems.map(legacyWorkItemProjection),
|
|
2507
|
+
exitCode: 0,
|
|
2508
|
+
};
|
|
2509
|
+
}
|
|
2510
|
+
|
|
2511
|
+
export function readTeamProjectStatusV2({ workspaceRoot }) {
|
|
1894
2512
|
const loaded = loadProject(workspaceRoot);
|
|
1895
2513
|
return {
|
|
1896
2514
|
...projectStatus(loaded.definition, loaded.events, loaded.root),
|
|
@@ -1898,7 +2516,7 @@ export function readTeamProjectStatusV1({ workspaceRoot }) {
|
|
|
1898
2516
|
};
|
|
1899
2517
|
}
|
|
1900
2518
|
|
|
1901
|
-
export function
|
|
2519
|
+
export function buildTeamProjectTakeoverV2({ workspaceRoot, agentId }) {
|
|
1902
2520
|
safeIdentifier(agentId, "agentId");
|
|
1903
2521
|
const loaded = loadProject(workspaceRoot);
|
|
1904
2522
|
const status = projectStatus(loaded.definition, loaded.events, loaded.root);
|
|
@@ -1915,6 +2533,29 @@ export function buildTeamProjectTakeoverV1({ workspaceRoot, agentId }) {
|
|
|
1915
2533
|
row.pendingHandoff?.toAgentId === agentId
|
|
1916
2534
|
))?.pendingHandoff ?? null;
|
|
1917
2535
|
const pendingHandoff = pendingForCurrent ?? pendingForAgent;
|
|
2536
|
+
const staleHandoffs = staleHandoffsForTakeover({
|
|
2537
|
+
loaded,
|
|
2538
|
+
status,
|
|
2539
|
+
agentId,
|
|
2540
|
+
responsibilities,
|
|
2541
|
+
});
|
|
2542
|
+
const latestStaleHandoff = [...staleHandoffs]
|
|
2543
|
+
.sort(compareEventFacts)
|
|
2544
|
+
.at(-1) ?? null;
|
|
2545
|
+
const focusResponsibility = current
|
|
2546
|
+
?? (pendingHandoff
|
|
2547
|
+
? status.workItems.find(row => row.workItemId === pendingHandoff.workItemId) ?? null
|
|
2548
|
+
: null)
|
|
2549
|
+
?? (latestStaleHandoff
|
|
2550
|
+
? status.workItems.find(row => row.workItemId === latestStaleHandoff.workItemId) ?? null
|
|
2551
|
+
: null);
|
|
2552
|
+
const focusedDeliveries = focusResponsibility
|
|
2553
|
+
? status.sourceDeliveries.filter(row => (
|
|
2554
|
+
row.workItemId === focusResponsibility.workItemId
|
|
2555
|
+
))
|
|
2556
|
+
: [];
|
|
2557
|
+
const currentSourceDelivery = focusedDeliveries.at(-1) ?? null;
|
|
2558
|
+
const deliveryDelta = sourceDeliveryDelta(focusedDeliveries);
|
|
1918
2559
|
const checkpoints = loaded.events
|
|
1919
2560
|
.filter(row => row.event.type === "checkpoint_recorded"
|
|
1920
2561
|
&& responsibilities.some(item => item.workItemId === row.event.workItemId))
|
|
@@ -1924,6 +2565,9 @@ export function buildTeamProjectTakeoverV1({ workspaceRoot, agentId }) {
|
|
|
1924
2565
|
...(lastCheckpoint?.evidenceRefs ?? []),
|
|
1925
2566
|
...(current?.evidenceRefs ?? []),
|
|
1926
2567
|
...(pendingHandoff?.evidenceRefs ?? []),
|
|
2568
|
+
...(currentSourceDelivery ? [currentSourceDelivery.truthRef] : []),
|
|
2569
|
+
...(deliveryDelta?.previousDeliveryId ? [focusedDeliveries.at(-2).truthRef] : []),
|
|
2570
|
+
...staleHandoffs.map(row => row.truthRef),
|
|
1927
2571
|
])].sort();
|
|
1928
2572
|
const unresolvedDecisionsById = new Map();
|
|
1929
2573
|
for (const decision of status.openDecisions) {
|
|
@@ -1965,7 +2609,7 @@ export function buildTeamProjectTakeoverV1({ workspaceRoot, agentId }) {
|
|
|
1965
2609
|
: current ? null : status.readyQueue[0] ? `Take ${status.readyQueue[0]}.` : null;
|
|
1966
2610
|
}
|
|
1967
2611
|
return {
|
|
1968
|
-
schemaVersion: "
|
|
2612
|
+
schemaVersion: "OwlCodaRunKitTeamProjectTakeoverV2",
|
|
1969
2613
|
status: "team_project_takeover",
|
|
1970
2614
|
projectId: status.projectId,
|
|
1971
2615
|
objective: status.objective,
|
|
@@ -1978,6 +2622,9 @@ export function buildTeamProjectTakeoverV1({ workspaceRoot, agentId }) {
|
|
|
1978
2622
|
currentResponsibility: current,
|
|
1979
2623
|
allResponsibilities: responsibilities,
|
|
1980
2624
|
pendingHandoff,
|
|
2625
|
+
staleHandoffs,
|
|
2626
|
+
currentSourceDelivery,
|
|
2627
|
+
sourceDeliveryDelta: deliveryDelta,
|
|
1981
2628
|
lastAcceptedCheckpoint: lastCheckpoint
|
|
1982
2629
|
? {
|
|
1983
2630
|
workItemId: lastCheckpoint.workItemId,
|
|
@@ -2000,6 +2647,27 @@ export function buildTeamProjectTakeoverV1({ workspaceRoot, agentId }) {
|
|
|
2000
2647
|
};
|
|
2001
2648
|
}
|
|
2002
2649
|
|
|
2650
|
+
export function buildTeamProjectTakeoverV1(input) {
|
|
2651
|
+
const {
|
|
2652
|
+
staleHandoffs: _staleHandoffs,
|
|
2653
|
+
currentSourceDelivery: _currentSourceDelivery,
|
|
2654
|
+
sourceDeliveryDelta: _sourceDeliveryDelta,
|
|
2655
|
+
...legacy
|
|
2656
|
+
} = buildTeamProjectTakeoverV2(input);
|
|
2657
|
+
const v2OnlyEvidenceRefs = new Set([
|
|
2658
|
+
..._staleHandoffs.map(row => row.truthRef),
|
|
2659
|
+
...readTeamProjectStatusV2({ workspaceRoot: input.workspaceRoot })
|
|
2660
|
+
.sourceDeliveries.map(row => row.truthRef),
|
|
2661
|
+
]);
|
|
2662
|
+
return {
|
|
2663
|
+
...legacy,
|
|
2664
|
+
schemaVersion: "OwlCodaRunKitTeamProjectTakeoverV1",
|
|
2665
|
+
currentResponsibility: legacyWorkItemProjection(legacy.currentResponsibility),
|
|
2666
|
+
allResponsibilities: legacy.allResponsibilities.map(legacyWorkItemProjection),
|
|
2667
|
+
evidenceRefs: legacy.evidenceRefs.filter(ref => !v2OnlyEvidenceRefs.has(ref)),
|
|
2668
|
+
};
|
|
2669
|
+
}
|
|
2670
|
+
|
|
2003
2671
|
export function formatTeamProjectStatusHumanV1(status) {
|
|
2004
2672
|
const nextAction = status.nextAction === null ? "none" : singleLine(status.nextAction);
|
|
2005
2673
|
return [
|
|
@@ -2020,6 +2688,65 @@ export function formatTeamProjectStatusHumanV1(status) {
|
|
|
2020
2688
|
].join("\n");
|
|
2021
2689
|
}
|
|
2022
2690
|
|
|
2691
|
+
export function formatTeamProjectStatusHumanV2(status) {
|
|
2692
|
+
const nextAction = status.nextAction === null ? "none" : singleLine(status.nextAction);
|
|
2693
|
+
const latestSourceDelivery = status.sourceDeliveries.at(-1) ?? null;
|
|
2694
|
+
return [
|
|
2695
|
+
status.headline,
|
|
2696
|
+
`Project: ${status.projectId} — ${singleLine(status.objective)}`,
|
|
2697
|
+
`State: ${status.overall}`,
|
|
2698
|
+
`Work: ${status.counts.completed}/${status.counts.total} completed; ${status.counts.active} active; ${status.counts.waiting_dependency + status.counts.waiting_decision} waiting`,
|
|
2699
|
+
`Agents: ${status.agents.map(agent => agent.agentId).join(", ") || "none assigned"}`,
|
|
2700
|
+
`Open decisions: ${status.openDecisions.map(row => row.decisionId).join(", ") || "none"}`,
|
|
2701
|
+
`Deferred verification: ${status.openDeferredVerificationIds.length} open${
|
|
2702
|
+
status.openDeferredVerificationIds.length > 0
|
|
2703
|
+
? ` (${status.openDeferredVerificationIds.join(", ")})`
|
|
2704
|
+
: ""
|
|
2705
|
+
}`,
|
|
2706
|
+
`Ready queue: ${status.readyQueue.join(", ") || "none"}`,
|
|
2707
|
+
`Source deliveries: ${status.sourceDeliveries.length}${latestSourceDelivery
|
|
2708
|
+
? `; latest ${latestSourceDelivery.deliveryId} (${latestSourceDelivery.overlayStatus}, ${latestSourceDelivery.deliveryCandidateFingerprint})`
|
|
2709
|
+
: ""}`,
|
|
2710
|
+
`Next: ${nextAction}${status.nextActorId ? ` (owner ${status.nextActorId})` : ""}`,
|
|
2711
|
+
"",
|
|
2712
|
+
].join("\n");
|
|
2713
|
+
}
|
|
2714
|
+
|
|
2715
|
+
export function formatTeamProjectTakeoverHumanV2(takeover) {
|
|
2716
|
+
const responsibility = takeover.currentResponsibility;
|
|
2717
|
+
const agentNextAction = takeover.nextAction === null
|
|
2718
|
+
? "none"
|
|
2719
|
+
: singleLine(takeover.nextAction);
|
|
2720
|
+
const projectNextAction = takeover.projectNextAction === null
|
|
2721
|
+
? "none"
|
|
2722
|
+
: singleLine(takeover.projectNextAction);
|
|
2723
|
+
return [
|
|
2724
|
+
takeover.projectHeadline,
|
|
2725
|
+
`Project: ${takeover.projectId} — ${singleLine(takeover.objective)}`,
|
|
2726
|
+
`Agent: ${takeover.agentId}`,
|
|
2727
|
+
`Responsibility: ${responsibility?.workItemId ?? "none"}`,
|
|
2728
|
+
`Open decisions: ${takeover.unresolvedDecisions.map(row => row.decisionId).join(", ") || "none"}`,
|
|
2729
|
+
`Deferred verification: ${takeover.deferredVerifications
|
|
2730
|
+
.map(row => row.verificationId).join(", ") || "none"}`,
|
|
2731
|
+
`Handoffs: pending ${takeover.pendingHandoff
|
|
2732
|
+
? `${takeover.pendingHandoff.fromAgentId} -> ${takeover.pendingHandoff.toAgentId} for ${takeover.pendingHandoff.workItemId}`
|
|
2733
|
+
: "none"}; stale ${takeover.staleHandoffs.length > 0
|
|
2734
|
+
? takeover.staleHandoffs.map(row => `${row.eventId} (${row.reason})`).join(", ")
|
|
2735
|
+
: "none"}`,
|
|
2736
|
+
`Candidate: ${takeover.currentSourceDelivery
|
|
2737
|
+
? `${takeover.currentSourceDelivery.deliveryId} (${takeover.currentSourceDelivery.overlayStatus}, ${takeover.currentSourceDelivery.deliveryCandidateFingerprint})`
|
|
2738
|
+
: "none"}; delta ${takeover.sourceDeliveryDelta === null
|
|
2739
|
+
? "none"
|
|
2740
|
+
: takeover.sourceDeliveryDelta.previousDeliveryId === null
|
|
2741
|
+
? `first delivery; added ${takeover.sourceDeliveryDelta.addedPaths.length}`
|
|
2742
|
+
: `vs ${takeover.sourceDeliveryDelta.previousDeliveryId}; added ${takeover.sourceDeliveryDelta.addedPaths.length}, changed ${takeover.sourceDeliveryDelta.changedPaths.length}, removed ${takeover.sourceDeliveryDelta.removedPaths.length}, unchanged ${takeover.sourceDeliveryDelta.unchangedPaths.length}`}`,
|
|
2743
|
+
`Agent next: ${agentNextAction}`,
|
|
2744
|
+
`Project next: ${projectNextAction}${takeover.projectNextActorId ? ` (owner ${takeover.projectNextActorId})` : ""}`,
|
|
2745
|
+
`State: ${takeover.overall}`,
|
|
2746
|
+
"",
|
|
2747
|
+
].join("\n");
|
|
2748
|
+
}
|
|
2749
|
+
|
|
2023
2750
|
export function formatTeamProjectTakeoverHumanV1(takeover) {
|
|
2024
2751
|
const responsibility = takeover.currentResponsibility;
|
|
2025
2752
|
const agentNextAction = takeover.nextAction === null
|