owlrunkit 0.19.1 → 0.19.2
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 +37 -0
- package/README.md +71 -0
- package/docs/architecture/OWLCODA_RUNKIT_0191_PROFILE_RECONCILE_PATCH_PRODUCT_DECISION_20260813.md +11 -5
- package/docs/architecture/OWLCODA_RUNKIT_0192_FORMAL_USABILITY_PRODUCT_DECISION_20260813.md +141 -0
- package/integrations/codex/skills/owlcoda-runkit/SKILL.md +50 -7
- package/integrations/codex/skills/owlcoda-runkit/assets/templates/config.json +3 -3
- package/package.json +1 -1
- package/packages/attest/distribution-manifest-v1.json +5 -0
- package/packages/attest/src/quick.mjs +37 -4
- package/registry-release-policy-v1.json +5 -5
- package/sbom.spdx.json +5 -5
- package/scripts/runkit-contract/core-contract.mjs +35 -2
- package/scripts/runkit-contract/efficiency-metrics.mjs +9 -7
- package/scripts/runkit-contract/formal-preflight.mjs +352 -0
- package/scripts/runkit-contract/formal-supplemental-evidence.mjs +713 -0
- package/scripts/runkit-contract/formal-workflow.mjs +529 -59
- package/scripts/runkit-contract/onboarding-doctor.mjs +32 -13
- package/scripts/runkit-contract/project-cli-resolver.mjs +30 -3
- package/scripts/runkit-contract/project-external-delivery.mjs +16 -9
- package/scripts/runkit-contract/quick-attest.mjs +24 -0
- package/scripts/runkit-contract/runkit-cli.mjs +365 -49
- package/scripts/runkit-contract/schemas/formal-check-v2.schema.json +231 -0
- package/scripts/runkit-contract/schemas/formal-envelope-preflight-v1.schema.json +212 -0
- package/scripts/runkit-contract/schemas/formal-evidence-summary-v1.schema.json +114 -0
- package/scripts/runkit-contract/schemas/formal-supplemental-evidence-v1.schema.json +103 -0
- package/scripts/runkit-contract/schemas/team-project-event-v5.schema.json +89 -0
- package/scripts/runkit-contract/schemas/team-project-status-v3.schema.json +173 -0
- package/scripts/runkit-contract/snapshot.mjs +125 -5
- package/scripts/runkit-contract/source-candidate.mjs +271 -69
- package/scripts/runkit-contract/sync-release-identity.mjs +39 -4
- package/scripts/runkit-contract/team-project-successor.mjs +7 -4
- package/scripts/runkit-contract/team-project.mjs +440 -28
- package/scripts/runkit-contract/usage-mode.mjs +9 -3
- package/scripts/runkit-contract/verification-envelope.mjs +425 -37
|
@@ -31,8 +31,10 @@ const EVENT_SCHEMA = "OwlCodaRunKitTeamProjectEventV1";
|
|
|
31
31
|
const EVENT_SCHEMA_V2 = "OwlCodaRunKitTeamProjectEventV2";
|
|
32
32
|
const EVENT_SCHEMA_V3 = "OwlCodaRunKitTeamProjectEventV3";
|
|
33
33
|
const EVENT_SCHEMA_V4 = "OwlCodaRunKitTeamProjectEventV4";
|
|
34
|
+
const EVENT_SCHEMA_V5 = "OwlCodaRunKitTeamProjectEventV5";
|
|
34
35
|
const STATUS_SCHEMA_V1 = "OwlCodaRunKitTeamProjectStatusV1";
|
|
35
36
|
const STATUS_SCHEMA_V2 = "OwlCodaRunKitTeamProjectStatusV2";
|
|
37
|
+
const STATUS_SCHEMA_V3 = "OwlCodaRunKitTeamProjectStatusV3";
|
|
36
38
|
|
|
37
39
|
const DEFINITION_KEYS = [
|
|
38
40
|
"schemaVersion",
|
|
@@ -124,6 +126,12 @@ const EVENT_KEYS = Object.freeze({
|
|
|
124
126
|
"toAgentId", "state", "summary", "completedUnits", "evidenceRefs",
|
|
125
127
|
"blockerRefs", "decisionRefs", "nextAction", "sourceFingerprint",
|
|
126
128
|
],
|
|
129
|
+
work_item_scope_revised: [
|
|
130
|
+
"schemaVersion", "eventId", "type", "occurredAt", "revisionId",
|
|
131
|
+
"supersedesRevisionId", "assignmentId", "workItemId", "ownerAgentId",
|
|
132
|
+
"previousOwnedPaths", "ownedPaths", "previousMeasurable", "measurable",
|
|
133
|
+
"reason", "evidenceRefs",
|
|
134
|
+
],
|
|
127
135
|
});
|
|
128
136
|
|
|
129
137
|
function canonical(value) {
|
|
@@ -617,6 +625,7 @@ function staleHandoffsForTakeover({ loaded, status, agentId, responsibilities })
|
|
|
617
625
|
"source_delivery_imported",
|
|
618
626
|
"upstream_reopened",
|
|
619
627
|
"responsibility_transferred",
|
|
628
|
+
"work_item_scope_revised",
|
|
620
629
|
].includes(row.event.type)
|
|
621
630
|
));
|
|
622
631
|
let reason = "superseded_by_current_truth";
|
|
@@ -646,13 +655,16 @@ function staleHandoffsForTakeover({ loaded, status, agentId, responsibilities })
|
|
|
646
655
|
.sort((left, right) => compareIdentifiers(left.eventId, right.eventId));
|
|
647
656
|
}
|
|
648
657
|
|
|
649
|
-
function
|
|
658
|
+
function normalizeTimestamp(value) {
|
|
650
659
|
if (typeof value !== "string"
|
|
651
|
-
|| !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}
|
|
660
|
+
|| !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z$/u.test(value)
|
|
652
661
|
|| Number.isNaN(Date.parse(value))) {
|
|
653
|
-
throw new Error(
|
|
662
|
+
throw new Error(
|
|
663
|
+
"Team project event occurredAt must be an ISO-8601 UTC timestamp "
|
|
664
|
+
+ "(YYYY-MM-DDTHH:mm:ssZ or YYYY-MM-DDTHH:mm:ss.sssZ).",
|
|
665
|
+
);
|
|
654
666
|
}
|
|
655
|
-
return value;
|
|
667
|
+
return new Date(value).toISOString();
|
|
656
668
|
}
|
|
657
669
|
|
|
658
670
|
function validateEventShape(value, definition) {
|
|
@@ -660,10 +672,10 @@ function validateEventShape(value, definition) {
|
|
|
660
672
|
throw new Error("Team project event type is invalid.");
|
|
661
673
|
}
|
|
662
674
|
assertAllowedKeys(value, `Team project event ${value.eventId ?? "unknown"}`, EVENT_KEYS[value.type]);
|
|
663
|
-
if (![EVENT_SCHEMA, EVENT_SCHEMA_V2, EVENT_SCHEMA_V3, EVENT_SCHEMA_V4]
|
|
675
|
+
if (![EVENT_SCHEMA, EVENT_SCHEMA_V2, EVENT_SCHEMA_V3, EVENT_SCHEMA_V4, EVENT_SCHEMA_V5]
|
|
664
676
|
.includes(value.schemaVersion)) {
|
|
665
677
|
throw new Error(
|
|
666
|
-
`Team project event schemaVersion must be ${EVENT_SCHEMA}, ${EVENT_SCHEMA_V2}, ${EVENT_SCHEMA_V3}, or ${
|
|
678
|
+
`Team project event schemaVersion must be ${EVENT_SCHEMA}, ${EVENT_SCHEMA_V2}, ${EVENT_SCHEMA_V3}, ${EVENT_SCHEMA_V4}, or ${EVENT_SCHEMA_V5}.`,
|
|
667
679
|
);
|
|
668
680
|
}
|
|
669
681
|
if (value.type === "rework_returned" && value.schemaVersion !== EVENT_SCHEMA_V2) {
|
|
@@ -684,8 +696,14 @@ function validateEventShape(value, definition) {
|
|
|
684
696
|
`${EVENT_SCHEMA_V4} is reserved for upstream_reopened and responsibility_transferred events.`,
|
|
685
697
|
);
|
|
686
698
|
}
|
|
699
|
+
if (value.type === "work_item_scope_revised" && value.schemaVersion !== EVENT_SCHEMA_V5) {
|
|
700
|
+
throw new Error(`work_item_scope_revised requires schemaVersion ${EVENT_SCHEMA_V5}.`);
|
|
701
|
+
}
|
|
702
|
+
if (value.schemaVersion === EVENT_SCHEMA_V5 && value.type !== "work_item_scope_revised") {
|
|
703
|
+
throw new Error(`${EVENT_SCHEMA_V5} is reserved for work_item_scope_revised events.`);
|
|
704
|
+
}
|
|
687
705
|
safeIdentifier(value.eventId, "eventId");
|
|
688
|
-
|
|
706
|
+
const normalizedOccurredAt = normalizeTimestamp(value.occurredAt);
|
|
689
707
|
const workItemIds = new Set(definition.workItems.map(row => row.id));
|
|
690
708
|
const gateIds = new Set(definition.integrationGates.map(row => row.id));
|
|
691
709
|
const assertWorkItem = (id) => {
|
|
@@ -700,6 +718,7 @@ function validateEventShape(value, definition) {
|
|
|
700
718
|
"source_delivery_imported",
|
|
701
719
|
"upstream_reopened",
|
|
702
720
|
"responsibility_transferred",
|
|
721
|
+
"work_item_scope_revised",
|
|
703
722
|
"verification_deferred",
|
|
704
723
|
].includes(value.type)) {
|
|
705
724
|
assertWorkItem(value.workItemId);
|
|
@@ -712,6 +731,7 @@ function validateEventShape(value, definition) {
|
|
|
712
731
|
"source_delivery_imported",
|
|
713
732
|
"upstream_reopened",
|
|
714
733
|
"responsibility_transferred",
|
|
734
|
+
"work_item_scope_revised",
|
|
715
735
|
].includes(value.type)) {
|
|
716
736
|
safeIdentifier(value.assignmentId, "assignmentId");
|
|
717
737
|
}
|
|
@@ -962,8 +982,41 @@ function validateEventShape(value, definition) {
|
|
|
962
982
|
&& !/^(?:sha256:)?[a-f0-9]{64}$/u.test(value.sourceFingerprint)) {
|
|
963
983
|
throw new Error("Responsibility transfer sourceFingerprint is invalid.");
|
|
964
984
|
}
|
|
985
|
+
} else if (value.type === "work_item_scope_revised") {
|
|
986
|
+
safeIdentifier(value.revisionId, "revisionId");
|
|
987
|
+
if (value.supersedesRevisionId !== undefined) {
|
|
988
|
+
safeIdentifier(value.supersedesRevisionId, "supersedesRevisionId");
|
|
989
|
+
}
|
|
990
|
+
safeIdentifier(value.ownerAgentId, "ownerAgentId");
|
|
991
|
+
const previousOwnedPaths = validateLeaseOwnedPaths(value.previousOwnedPaths);
|
|
992
|
+
const ownedPaths = validateLeaseOwnedPaths(value.ownedPaths);
|
|
993
|
+
const validateMeasurable = (measurable, label) => {
|
|
994
|
+
if (measurable === null) return null;
|
|
995
|
+
if (!isRecord(measurable)) throw new Error(`${label} must be an object or null.`);
|
|
996
|
+
assertAllowedKeys(measurable, label, ["unit", "total"]);
|
|
997
|
+
nonEmptyString(measurable.unit, `${label} unit`);
|
|
998
|
+
if (!Number.isSafeInteger(measurable.total) || measurable.total < 1) {
|
|
999
|
+
throw new Error(`${label} total must be a positive integer.`);
|
|
1000
|
+
}
|
|
1001
|
+
return { unit: measurable.unit, total: measurable.total };
|
|
1002
|
+
};
|
|
1003
|
+
const previousMeasurable = validateMeasurable(
|
|
1004
|
+
value.previousMeasurable,
|
|
1005
|
+
"Previous WorkItem measurable",
|
|
1006
|
+
);
|
|
1007
|
+
const measurable = validateMeasurable(value.measurable, "Revised WorkItem measurable");
|
|
1008
|
+
nonEmptyString(value.reason, "WorkItem scope revision reason");
|
|
1009
|
+
stringArray(value.evidenceRefs, "WorkItem scope revision evidenceRefs");
|
|
1010
|
+
return {
|
|
1011
|
+
...structuredClone(value),
|
|
1012
|
+
occurredAt: normalizedOccurredAt,
|
|
1013
|
+
previousOwnedPaths,
|
|
1014
|
+
ownedPaths,
|
|
1015
|
+
previousMeasurable,
|
|
1016
|
+
measurable,
|
|
1017
|
+
};
|
|
965
1018
|
}
|
|
966
|
-
return structuredClone(value);
|
|
1019
|
+
return { ...structuredClone(value), occurredAt: normalizedOccurredAt };
|
|
967
1020
|
}
|
|
968
1021
|
|
|
969
1022
|
function projectEvents(definition, records) {
|
|
@@ -979,12 +1032,15 @@ function projectEvents(definition, records) {
|
|
|
979
1032
|
const verifications = new Map();
|
|
980
1033
|
const dataCandidates = [];
|
|
981
1034
|
const sourceDeliveries = [];
|
|
1035
|
+
const scopeRevisions = new Map();
|
|
1036
|
+
const scopeRevisionHistory = new Map(definition.workItems.map(row => [row.id, []]));
|
|
982
1037
|
const seenAssignments = new Set();
|
|
983
1038
|
const seenReworks = new Set();
|
|
984
1039
|
const seenReopens = new Set();
|
|
985
1040
|
const seenTransfers = new Set();
|
|
986
1041
|
const seenDecisions = new Set();
|
|
987
1042
|
const seenDeliveries = new Set();
|
|
1043
|
+
const seenScopeRevisions = new Set();
|
|
988
1044
|
const seenFactIds = new Set(records.map(row => row.event.eventId));
|
|
989
1045
|
|
|
990
1046
|
for (const { event, ref } of records) {
|
|
@@ -1052,7 +1108,8 @@ function projectEvents(definition, records) {
|
|
|
1052
1108
|
}
|
|
1053
1109
|
if (gatePasses.has(event.dueGateId)) {
|
|
1054
1110
|
throw new Error(
|
|
1055
|
-
`Deferred verification cannot target integration gate ${event.dueGateId}; gate already passed
|
|
1111
|
+
`Deferred verification cannot target integration gate ${event.dueGateId}; gate already passed. `
|
|
1112
|
+
+ "Create a successor project with project successor, or define a new unpassed gate before deferring.",
|
|
1056
1113
|
);
|
|
1057
1114
|
}
|
|
1058
1115
|
verifications.set(event.verificationId, {
|
|
@@ -1098,6 +1155,38 @@ function projectEvents(definition, records) {
|
|
|
1098
1155
|
}
|
|
1099
1156
|
seenDeliveries.add(event.deliveryId);
|
|
1100
1157
|
sourceDeliveries.push({ ...event, ref });
|
|
1158
|
+
} else if (event.type === "work_item_scope_revised") {
|
|
1159
|
+
if (seenScopeRevisions.has(event.revisionId)) {
|
|
1160
|
+
throw new Error(`Duplicate WorkItem scope revision ${event.revisionId}.`);
|
|
1161
|
+
}
|
|
1162
|
+
const baseWorkItem = definition.workItems.find(row => row.id === event.workItemId);
|
|
1163
|
+
const previous = scopeRevisions.get(event.workItemId) ?? null;
|
|
1164
|
+
const currentAssignment = assignments.get(event.workItemId) ?? null;
|
|
1165
|
+
const currentOwnedPaths = previous?.ownedPaths ?? baseWorkItem.ownedPaths;
|
|
1166
|
+
const currentMeasurable = previous
|
|
1167
|
+
? previous.measurable
|
|
1168
|
+
: baseWorkItem.measurable ?? null;
|
|
1169
|
+
if (event.supersedesRevisionId !== previous?.revisionId) {
|
|
1170
|
+
throw new Error(
|
|
1171
|
+
`WorkItem scope revision ${event.revisionId} must supersede current revision ${previous?.revisionId ?? "none"}.`,
|
|
1172
|
+
);
|
|
1173
|
+
}
|
|
1174
|
+
if (canonical(event.previousOwnedPaths) !== canonical(currentOwnedPaths)
|
|
1175
|
+
|| canonical(event.previousMeasurable) !== canonical(currentMeasurable)) {
|
|
1176
|
+
throw new Error(
|
|
1177
|
+
`WorkItem scope revision ${event.revisionId} does not bind the current scope.`,
|
|
1178
|
+
);
|
|
1179
|
+
}
|
|
1180
|
+
if (currentAssignment?.assignmentId !== event.assignmentId
|
|
1181
|
+
|| currentAssignment.agentId !== event.ownerAgentId) {
|
|
1182
|
+
throw new Error(
|
|
1183
|
+
`WorkItem scope revision ${event.revisionId} does not bind the current assignment and owner.`,
|
|
1184
|
+
);
|
|
1185
|
+
}
|
|
1186
|
+
seenScopeRevisions.add(event.revisionId);
|
|
1187
|
+
const revision = { ...event, ref };
|
|
1188
|
+
scopeRevisions.set(event.workItemId, revision);
|
|
1189
|
+
scopeRevisionHistory.get(event.workItemId).push(revision);
|
|
1101
1190
|
} else if (event.type === "upstream_reopened") {
|
|
1102
1191
|
if (seenReopens.has(event.reopenId)) {
|
|
1103
1192
|
throw new Error(`Duplicate upstream reopen ${event.reopenId}.`);
|
|
@@ -1273,6 +1362,26 @@ function projectEvents(definition, records) {
|
|
|
1273
1362
|
verifications,
|
|
1274
1363
|
dataCandidates,
|
|
1275
1364
|
sourceDeliveries,
|
|
1365
|
+
scopeRevisions,
|
|
1366
|
+
scopeRevisionHistory,
|
|
1367
|
+
};
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
function definitionWithScopeRevisions(definition, state) {
|
|
1371
|
+
return {
|
|
1372
|
+
...definition,
|
|
1373
|
+
workItems: definition.workItems.map((item) => {
|
|
1374
|
+
const revision = state.scopeRevisions.get(item.id) ?? null;
|
|
1375
|
+
if (!revision) return item;
|
|
1376
|
+
const { measurable: _baseMeasurable, ...base } = item;
|
|
1377
|
+
return {
|
|
1378
|
+
...base,
|
|
1379
|
+
ownedPaths: [...revision.ownedPaths],
|
|
1380
|
+
...(revision.measurable === null
|
|
1381
|
+
? {}
|
|
1382
|
+
: { measurable: structuredClone(revision.measurable) }),
|
|
1383
|
+
};
|
|
1384
|
+
}),
|
|
1276
1385
|
};
|
|
1277
1386
|
}
|
|
1278
1387
|
|
|
@@ -1610,7 +1719,9 @@ function completedWorkItemIds(definition, state) {
|
|
|
1610
1719
|
for (const item of definition.workItems) {
|
|
1611
1720
|
if (completed.has(item.id)) continue;
|
|
1612
1721
|
const checkpoint = state.checkpoints.get(item.id) ?? null;
|
|
1722
|
+
const scopeRevision = state.scopeRevisions.get(item.id) ?? null;
|
|
1613
1723
|
if (checkpoint?.state !== "completed"
|
|
1724
|
+
|| (scopeRevision && compareEventFacts(checkpoint, scopeRevision) <= 0)
|
|
1614
1725
|
|| invalidatingReopensForWorkItem(state, item.id).length > 0
|
|
1615
1726
|
|| item.dependencies.some(id => !completed.has(id))) continue;
|
|
1616
1727
|
completed.add(item.id);
|
|
@@ -1620,8 +1731,28 @@ function completedWorkItemIds(definition, state) {
|
|
|
1620
1731
|
return completed;
|
|
1621
1732
|
}
|
|
1622
1733
|
|
|
1734
|
+
function hasBoundVerificationReceipt(evidenceRefs) {
|
|
1735
|
+
return evidenceRefs.some(ref => (
|
|
1736
|
+
/(?:quick[^\s]*receipt|receipt[^\s]*quick|formal[^\s]*(?:receipt|check|closeout)|(?:receipt|check|closeout)[^\s]*formal|closeout-receipt)/iu
|
|
1737
|
+
.test(ref)
|
|
1738
|
+
));
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
function verificationReceiptWarning(workItem) {
|
|
1742
|
+
if (!["verifying", "completed"].includes(workItem.status)
|
|
1743
|
+
|| hasBoundVerificationReceipt(workItem.evidenceRefs)) return null;
|
|
1744
|
+
return {
|
|
1745
|
+
code: "no_bound_verification_receipt",
|
|
1746
|
+
workItemId: workItem.workItemId,
|
|
1747
|
+
message: "No Quick or Formal receipt reference is bound to the current verification state.",
|
|
1748
|
+
truthRefs: uniqueSorted(workItem.truthRefs),
|
|
1749
|
+
};
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1623
1752
|
function projectStatus(definition, records, workspaceRoot = null) {
|
|
1753
|
+
const projectDefinition = definition;
|
|
1624
1754
|
const state = projectEvents(definition, records);
|
|
1755
|
+
definition = definitionWithScopeRevisions(definition, state);
|
|
1625
1756
|
const completed = completedWorkItemIds(definition, state);
|
|
1626
1757
|
let workItems = definition.workItems.map((item) => {
|
|
1627
1758
|
const assignment = state.assignments.get(item.id) ?? null;
|
|
@@ -1629,6 +1760,8 @@ function projectStatus(definition, records, workspaceRoot = null) {
|
|
|
1629
1760
|
const handoff = state.handoffs.get(item.id) ?? null;
|
|
1630
1761
|
const rework = state.reworks.get(item.id) ?? null;
|
|
1631
1762
|
const sourceDeliveries = state.sourceDeliveries.filter(row => row.workItemId === item.id);
|
|
1763
|
+
const scopeRevisionHistory = state.scopeRevisionHistory.get(item.id) ?? [];
|
|
1764
|
+
const latestScopeRevision = scopeRevisionHistory.at(-1) ?? null;
|
|
1632
1765
|
const latestSourceDeliveryFact = sourceDeliveries.at(-1) ?? null;
|
|
1633
1766
|
const latestSourceDelivery = sourceDeliveryProjection(sourceDeliveries.at(-1) ?? null);
|
|
1634
1767
|
const currentRework = rework?.assignmentId === assignment?.assignmentId ? rework : null;
|
|
@@ -1640,8 +1773,19 @@ function projectStatus(definition, records, workspaceRoot = null) {
|
|
|
1640
1773
|
const currentTransfer = state.transfers.get(item.id)?.assignmentId === assignment?.assignmentId
|
|
1641
1774
|
? state.transfers.get(item.id)
|
|
1642
1775
|
: null;
|
|
1643
|
-
const
|
|
1644
|
-
|
|
1776
|
+
const scopeInvalidatesCheckpoint = checkpoint && latestScopeRevision
|
|
1777
|
+
&& compareEventFacts(checkpoint, latestScopeRevision) <= 0;
|
|
1778
|
+
const effectiveCheckpoint = invalidatingReopens.length === 0 && !scopeInvalidatesCheckpoint
|
|
1779
|
+
? checkpoint
|
|
1780
|
+
: null;
|
|
1781
|
+
let latestFact = [
|
|
1782
|
+
effectiveCheckpoint,
|
|
1783
|
+
handoff,
|
|
1784
|
+
currentRework,
|
|
1785
|
+
currentReopen,
|
|
1786
|
+
currentTransfer,
|
|
1787
|
+
latestScopeRevision,
|
|
1788
|
+
]
|
|
1645
1789
|
.reduce((latest, fact) => latestEventFact(latest, fact), null);
|
|
1646
1790
|
if (currentTransfer && latestFact?.occurredAt === currentTransfer.occurredAt) {
|
|
1647
1791
|
latestFact = currentTransfer;
|
|
@@ -1656,7 +1800,13 @@ function projectStatus(definition, records, workspaceRoot = null) {
|
|
|
1656
1800
|
&& latestFact?.eventId === activeHandoff.eventId
|
|
1657
1801
|
&& latestHandoffFact?.eventId !== activeHandoff.eventId;
|
|
1658
1802
|
const projectedFact = handoffSupersededByDelivery
|
|
1659
|
-
? [
|
|
1803
|
+
? [
|
|
1804
|
+
effectiveCheckpoint,
|
|
1805
|
+
currentRework,
|
|
1806
|
+
currentReopen,
|
|
1807
|
+
currentTransfer,
|
|
1808
|
+
latestScopeRevision,
|
|
1809
|
+
]
|
|
1660
1810
|
.reduce((latest, fact) => latestEventFact(latest, fact), null)
|
|
1661
1811
|
: latestFact;
|
|
1662
1812
|
const pendingHandoff = activeHandoff
|
|
@@ -1672,6 +1822,7 @@ function projectStatus(definition, records, workspaceRoot = null) {
|
|
|
1672
1822
|
if (projectedFact?.type === "checkpoint_recorded") status = effectiveCheckpoint.state;
|
|
1673
1823
|
if (projectedFact?.type === "responsibility_transferred") status = projectedFact.state;
|
|
1674
1824
|
if (projectedFact?.type === "upstream_reopened") status = "active";
|
|
1825
|
+
if (projectedFact?.type === "work_item_scope_revised") status = "active";
|
|
1675
1826
|
if (assignment && unresolvedDependencies.length > 0
|
|
1676
1827
|
&& !["completed", "failed"].includes(status)) status = "waiting_dependency";
|
|
1677
1828
|
if (executionTruth && (executionTruth.status !== "bound"
|
|
@@ -1705,6 +1856,7 @@ function projectStatus(definition, records, workspaceRoot = null) {
|
|
|
1705
1856
|
...reopenFacts.map(row => row.ref),
|
|
1706
1857
|
...(currentTransfer ? [currentTransfer.ref] : []),
|
|
1707
1858
|
...sourceDeliveries.map(row => row.ref),
|
|
1859
|
+
...scopeRevisionHistory.map(row => row.ref),
|
|
1708
1860
|
];
|
|
1709
1861
|
const projectedRework = reworkProjection(
|
|
1710
1862
|
currentRework,
|
|
@@ -1712,7 +1864,11 @@ function projectStatus(definition, records, workspaceRoot = null) {
|
|
|
1712
1864
|
? assignmentEvents.findIndex(row => row.assignmentId === currentRework.assignmentId) + 1
|
|
1713
1865
|
: null,
|
|
1714
1866
|
);
|
|
1715
|
-
const latestSummary = [
|
|
1867
|
+
const latestSummary = [
|
|
1868
|
+
"rework_returned",
|
|
1869
|
+
"upstream_reopened",
|
|
1870
|
+
"work_item_scope_revised",
|
|
1871
|
+
].includes(projectedFact?.type)
|
|
1716
1872
|
? projectedFact.reason
|
|
1717
1873
|
: projectedFact?.summary ?? null;
|
|
1718
1874
|
const checkpointLikeFact = ["checkpoint_recorded", "responsibility_transferred"]
|
|
@@ -1737,6 +1893,19 @@ function projectStatus(definition, records, workspaceRoot = null) {
|
|
|
1737
1893
|
: unresolvedDependencies.map(id => `work-item:${id}`),
|
|
1738
1894
|
decisionRefs: checkpointLikeFact ? checkpointLikeFact.decisionRefs : [],
|
|
1739
1895
|
progress: measured,
|
|
1896
|
+
ownedPaths: [...item.ownedPaths],
|
|
1897
|
+
measurable: item.measurable ? structuredClone(item.measurable) : null,
|
|
1898
|
+
scopeRevision: latestScopeRevision
|
|
1899
|
+
? {
|
|
1900
|
+
revisionId: latestScopeRevision.revisionId,
|
|
1901
|
+
supersedesRevisionId: latestScopeRevision.supersedesRevisionId ?? null,
|
|
1902
|
+
occurredAt: latestScopeRevision.occurredAt,
|
|
1903
|
+
ownerAgentId: latestScopeRevision.ownerAgentId,
|
|
1904
|
+
reason: latestScopeRevision.reason,
|
|
1905
|
+
evidenceRefs: [...latestScopeRevision.evidenceRefs],
|
|
1906
|
+
truthRef: latestScopeRevision.ref,
|
|
1907
|
+
}
|
|
1908
|
+
: null,
|
|
1740
1909
|
summary: latestSummary,
|
|
1741
1910
|
nextAction: projectedFact?.nextAction ?? null,
|
|
1742
1911
|
pendingHandoff,
|
|
@@ -1950,6 +2119,7 @@ function projectStatus(definition, records, workspaceRoot = null) {
|
|
|
1950
2119
|
};
|
|
1951
2120
|
});
|
|
1952
2121
|
const sourceDeliveries = state.sourceDeliveries.map(sourceDeliveryProjection);
|
|
2122
|
+
const warnings = workItems.map(verificationReceiptWarning).filter(Boolean);
|
|
1953
2123
|
let overall = "planned";
|
|
1954
2124
|
if (workItems.every(row => row.status === "completed")
|
|
1955
2125
|
&& integrationGates.every(row => row.status === "passed")
|
|
@@ -1957,7 +2127,7 @@ function projectStatus(definition, records, workspaceRoot = null) {
|
|
|
1957
2127
|
else if (openDecisions.length > 0 || workItems.some(row => row.status === "failed")) overall = "active_with_blockers";
|
|
1958
2128
|
else if (workItems.some(row => row.status !== "planned" || row.pendingHandoff !== null)) overall = "active";
|
|
1959
2129
|
const status = {
|
|
1960
|
-
schemaVersion:
|
|
2130
|
+
schemaVersion: STATUS_SCHEMA_V3,
|
|
1961
2131
|
status: "team_project_status",
|
|
1962
2132
|
projectId: definition.projectId,
|
|
1963
2133
|
objective: definition.objective,
|
|
@@ -1976,7 +2146,11 @@ function projectStatus(definition, records, workspaceRoot = null) {
|
|
|
1976
2146
|
integrationGates,
|
|
1977
2147
|
dataCandidates,
|
|
1978
2148
|
sourceDeliveries,
|
|
1979
|
-
|
|
2149
|
+
warnings,
|
|
2150
|
+
projectTruthHash: sha256(canonical({
|
|
2151
|
+
definition: projectDefinition,
|
|
2152
|
+
events: records.map(row => row.event),
|
|
2153
|
+
})),
|
|
1980
2154
|
authorizationGranted: false,
|
|
1981
2155
|
};
|
|
1982
2156
|
return { ...status, ...deriveProjectDriverSummary(status) };
|
|
@@ -1986,7 +2160,8 @@ function validateEventAgainstState(event, definition, records, workspaceRoot) {
|
|
|
1986
2160
|
const normalized = validateEventShape(event, definition);
|
|
1987
2161
|
const state = projectEvents(definition, records);
|
|
1988
2162
|
const status = projectStatus(definition, records, workspaceRoot);
|
|
1989
|
-
const
|
|
2163
|
+
const effectiveDefinition = definitionWithScopeRevisions(definition, state);
|
|
2164
|
+
const workItem = effectiveDefinition.workItems.find(row => row.id === normalized.workItemId);
|
|
1990
2165
|
if (normalized.type === "agent_assigned") {
|
|
1991
2166
|
if (normalized.executionRunId !== undefined) {
|
|
1992
2167
|
const executionTruth = executionTruthFor(workspaceRoot, normalized);
|
|
@@ -2016,7 +2191,7 @@ function validateEventAgainstState(event, definition, records, workspaceRoot) {
|
|
|
2016
2191
|
if (projected.workItemId === normalized.workItemId
|
|
2017
2192
|
|| projected.assignmentId === null
|
|
2018
2193
|
|| ["completed", "failed"].includes(projected.status)) continue;
|
|
2019
|
-
const other =
|
|
2194
|
+
const other = effectiveDefinition.workItems.find(row => row.id === projected.workItemId);
|
|
2020
2195
|
if (pathsOverlap(workItem.ownedPaths, other.ownedPaths)) {
|
|
2021
2196
|
throw new Error(
|
|
2022
2197
|
`Owned paths overlap active assignment ${projected.assignmentId}: ${normalized.workItemId} <> ${projected.workItemId}.`,
|
|
@@ -2043,6 +2218,16 @@ function validateEventAgainstState(event, definition, records, workspaceRoot) {
|
|
|
2043
2218
|
if (normalized.state === "completed" && unresolved.length > 0) {
|
|
2044
2219
|
throw new Error(`Completed checkpoint has unresolved dependencies: ${unresolved.join(", ")}.`);
|
|
2045
2220
|
}
|
|
2221
|
+
if (normalized.state === "verifying") {
|
|
2222
|
+
const history = state.assignmentHistory.get(normalized.workItemId) ?? [];
|
|
2223
|
+
const prior = history.at(-2) ?? null;
|
|
2224
|
+
if (!prior || prior.agentId === current.agentId) {
|
|
2225
|
+
throw new Error(
|
|
2226
|
+
"Independent verification requires a different current owner from the implementation owner. "
|
|
2227
|
+
+ `Record a handoff to the verifier, then run: npx --no-install owlrunkit project assign --workspace ${JSON.stringify(workspaceRoot)} --assignment-id <verifier-assignment-id> --at <ISO-UTC> --work-item ${normalized.workItemId} --agent <verifier-agent> --supersedes ${current.assignmentId}`,
|
|
2228
|
+
);
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2046
2231
|
const executionTruth = status.workItems.find(
|
|
2047
2232
|
row => row.workItemId === normalized.workItemId,
|
|
2048
2233
|
).executionTruth;
|
|
@@ -2107,7 +2292,7 @@ function validateEventAgainstState(event, definition, records, workspaceRoot) {
|
|
|
2107
2292
|
if (otherProjection.workItemId === normalized.workItemId
|
|
2108
2293
|
|| otherProjection.assignmentId === null
|
|
2109
2294
|
|| ["completed", "failed"].includes(otherProjection.status)) continue;
|
|
2110
|
-
const other =
|
|
2295
|
+
const other = effectiveDefinition.workItems.find(row => row.id === otherProjection.workItemId);
|
|
2111
2296
|
if (pathsOverlap(workItem.ownedPaths, other.ownedPaths)) {
|
|
2112
2297
|
throw new Error(
|
|
2113
2298
|
`Owned paths overlap active assignment ${otherProjection.assignmentId}: ${normalized.workItemId} <> ${otherProjection.workItemId}.`,
|
|
@@ -2155,7 +2340,7 @@ function validateEventAgainstState(event, definition, records, workspaceRoot) {
|
|
|
2155
2340
|
if (otherProjection.workItemId === normalized.workItemId
|
|
2156
2341
|
|| otherProjection.assignmentId === null
|
|
2157
2342
|
|| ["completed", "failed"].includes(otherProjection.status)) continue;
|
|
2158
|
-
const other =
|
|
2343
|
+
const other = effectiveDefinition.workItems.find(row => row.id === otherProjection.workItemId);
|
|
2159
2344
|
if (pathsOverlap(workItem.ownedPaths, other.ownedPaths)) {
|
|
2160
2345
|
throw new Error(
|
|
2161
2346
|
`Owned paths overlap active assignment ${otherProjection.assignmentId}: ${normalized.workItemId} <> ${otherProjection.workItemId}.`,
|
|
@@ -2220,9 +2405,54 @@ function validateEventAgainstState(event, definition, records, workspaceRoot) {
|
|
|
2220
2405
|
}
|
|
2221
2406
|
if (state.gatePasses.has(normalized.dueGateId)) {
|
|
2222
2407
|
throw new Error(
|
|
2223
|
-
`Deferred verification cannot target integration gate ${normalized.dueGateId}; gate already passed
|
|
2408
|
+
`Deferred verification cannot target integration gate ${normalized.dueGateId}; gate already passed. `
|
|
2409
|
+
+ "Create a successor project with: npx --no-install owlrunkit project successor --workspace <directory> --transition-id <id> --at <ISO-UTC> --definition <project.json> --reason <text>; or define a new unpassed gate before deferring.",
|
|
2410
|
+
);
|
|
2411
|
+
}
|
|
2412
|
+
} else if (normalized.type === "work_item_scope_revised") {
|
|
2413
|
+
const current = state.assignments.get(normalized.workItemId) ?? null;
|
|
2414
|
+
const currentRevision = state.scopeRevisions.get(normalized.workItemId) ?? null;
|
|
2415
|
+
const currentScope = currentRevision?.ownedPaths ?? workItem.ownedPaths;
|
|
2416
|
+
const currentMeasurable = currentRevision
|
|
2417
|
+
? currentRevision.measurable
|
|
2418
|
+
: workItem.measurable ?? null;
|
|
2419
|
+
const projected = status.workItems.find(row => row.workItemId === normalized.workItemId);
|
|
2420
|
+
if (!current
|
|
2421
|
+
|| current.assignmentId !== normalized.assignmentId
|
|
2422
|
+
|| current.agentId !== normalized.ownerAgentId) {
|
|
2423
|
+
throw new Error("WorkItem scope revision must bind the current assignment and owner.");
|
|
2424
|
+
}
|
|
2425
|
+
if (["completed", "failed"].includes(projected?.status)) {
|
|
2426
|
+
throw new Error("WorkItem scope revision requires a non-terminal WorkItem.");
|
|
2427
|
+
}
|
|
2428
|
+
if (normalized.supersedesRevisionId !== currentRevision?.revisionId) {
|
|
2429
|
+
throw new Error(
|
|
2430
|
+
`WorkItem scope revision must supersede current revision ${currentRevision?.revisionId ?? "none"}.`,
|
|
2224
2431
|
);
|
|
2225
2432
|
}
|
|
2433
|
+
if (canonical(normalized.previousOwnedPaths) !== canonical(currentScope)
|
|
2434
|
+
|| canonical(normalized.previousMeasurable) !== canonical(currentMeasurable)) {
|
|
2435
|
+
throw new Error("WorkItem scope revision does not bind the current scope.");
|
|
2436
|
+
}
|
|
2437
|
+
if (canonical(normalized.ownedPaths) === canonical(currentScope)
|
|
2438
|
+
&& canonical(normalized.measurable) === canonical(currentMeasurable)) {
|
|
2439
|
+
throw new Error("WorkItem scope revision must change ownedPaths or measurable truth.");
|
|
2440
|
+
}
|
|
2441
|
+
for (const otherProjection of status.workItems) {
|
|
2442
|
+
if (otherProjection.workItemId === normalized.workItemId
|
|
2443
|
+
|| otherProjection.assignmentId === null
|
|
2444
|
+
|| ["completed", "failed"].includes(otherProjection.status)) continue;
|
|
2445
|
+
const otherRevision = state.scopeRevisions.get(otherProjection.workItemId) ?? null;
|
|
2446
|
+
const otherDefinition = effectiveDefinition.workItems.find(
|
|
2447
|
+
row => row.id === otherProjection.workItemId,
|
|
2448
|
+
);
|
|
2449
|
+
const otherOwnedPaths = otherRevision?.ownedPaths ?? otherDefinition.ownedPaths;
|
|
2450
|
+
if (pathsOverlap(normalized.ownedPaths, otherOwnedPaths)) {
|
|
2451
|
+
throw new Error(
|
|
2452
|
+
`Revised owned paths overlap active assignment ${otherProjection.assignmentId}: ${normalized.workItemId} <> ${otherProjection.workItemId}.`,
|
|
2453
|
+
);
|
|
2454
|
+
}
|
|
2455
|
+
}
|
|
2226
2456
|
} else if (normalized.type === "verification_closed") {
|
|
2227
2457
|
const verification = state.verifications.get(normalized.verificationId);
|
|
2228
2458
|
if (!verification || verification.closed) {
|
|
@@ -2376,7 +2606,7 @@ export function assignTeamProjectV1({
|
|
|
2376
2606
|
if (executionWorkItemId !== undefined) event.executionWorkItemId = executionWorkItemId;
|
|
2377
2607
|
|
|
2378
2608
|
const recorded = appendTeamProjectEventV1({ workspaceRoot, event });
|
|
2379
|
-
const status =
|
|
2609
|
+
const status = readTeamProjectStatusV3({ workspaceRoot });
|
|
2380
2610
|
if (recorded.resumed) {
|
|
2381
2611
|
const current = status.workItems.find(row => row.workItemId === workItemId);
|
|
2382
2612
|
if (current?.assignmentId !== assignmentId) {
|
|
@@ -3010,7 +3240,7 @@ export function integrateTeamProjectV1({
|
|
|
3010
3240
|
evidenceRefs,
|
|
3011
3241
|
};
|
|
3012
3242
|
const recorded = appendTeamProjectEventV1({ workspaceRoot, event });
|
|
3013
|
-
const status =
|
|
3243
|
+
const status = readTeamProjectStatusV3({ workspaceRoot });
|
|
3014
3244
|
const gate = status.integrationGates.find(row => row.gateId === gateId);
|
|
3015
3245
|
return {
|
|
3016
3246
|
status: "team_project_integration",
|
|
@@ -3021,6 +3251,7 @@ export function integrateTeamProjectV1({
|
|
|
3021
3251
|
resumed: recorded.resumed,
|
|
3022
3252
|
gateStatus: gate?.status ?? null,
|
|
3023
3253
|
overall: status.overall,
|
|
3254
|
+
deliveryDisposition: status.deliveryDisposition,
|
|
3024
3255
|
projectTruthHash: status.projectTruthHash,
|
|
3025
3256
|
nextAction: status.nextAction,
|
|
3026
3257
|
nextActorId: status.nextActorId,
|
|
@@ -3064,7 +3295,7 @@ export function checkpointTeamProjectV1({
|
|
|
3064
3295
|
...(sourceFingerprint === undefined ? {} : { sourceFingerprint }),
|
|
3065
3296
|
};
|
|
3066
3297
|
const recorded = appendTeamProjectEventV1({ workspaceRoot, event });
|
|
3067
|
-
const status =
|
|
3298
|
+
const status = readTeamProjectStatusV3({ workspaceRoot });
|
|
3068
3299
|
if (recorded.resumed) {
|
|
3069
3300
|
const loaded = loadProject(workspaceRoot);
|
|
3070
3301
|
const stateProjection = projectEvents(loaded.definition, loaded.events);
|
|
@@ -3094,6 +3325,89 @@ export function checkpointTeamProjectV1({
|
|
|
3094
3325
|
resumed: recorded.resumed,
|
|
3095
3326
|
projectTruthHash: status.projectTruthHash,
|
|
3096
3327
|
overall: status.overall,
|
|
3328
|
+
warnings: status.warnings.filter(row => row.workItemId === workItemId),
|
|
3329
|
+
nextAction: status.nextAction,
|
|
3330
|
+
nextActorId: status.nextActorId,
|
|
3331
|
+
authorizationGranted: false,
|
|
3332
|
+
exitCode: 0,
|
|
3333
|
+
};
|
|
3334
|
+
}
|
|
3335
|
+
|
|
3336
|
+
export function reviseTeamProjectWorkItemScopeV1({
|
|
3337
|
+
workspaceRoot,
|
|
3338
|
+
revisionId,
|
|
3339
|
+
occurredAt,
|
|
3340
|
+
assignmentId,
|
|
3341
|
+
workItemId,
|
|
3342
|
+
ownerAgentId,
|
|
3343
|
+
ownedPaths,
|
|
3344
|
+
measurable = null,
|
|
3345
|
+
reason,
|
|
3346
|
+
evidenceRefs = [],
|
|
3347
|
+
}) {
|
|
3348
|
+
safeIdentifier(revisionId, "revisionId");
|
|
3349
|
+
const loaded = loadProject(workspaceRoot);
|
|
3350
|
+
const state = projectEvents(loaded.definition, loaded.events);
|
|
3351
|
+
const baseWorkItem = loaded.definition.workItems.find(row => row.id === workItemId);
|
|
3352
|
+
if (!baseWorkItem) throw new Error(`Unknown team project WorkItem: ${workItemId}.`);
|
|
3353
|
+
const existing = loaded.events.find(row => (
|
|
3354
|
+
row.event.type === "work_item_scope_revised"
|
|
3355
|
+
&& row.event.revisionId === revisionId
|
|
3356
|
+
))?.event ?? null;
|
|
3357
|
+
let event;
|
|
3358
|
+
if (existing) {
|
|
3359
|
+
const requested = {
|
|
3360
|
+
occurredAt: normalizeTimestamp(occurredAt),
|
|
3361
|
+
assignmentId,
|
|
3362
|
+
workItemId,
|
|
3363
|
+
ownerAgentId,
|
|
3364
|
+
ownedPaths: validateLeaseOwnedPaths(ownedPaths),
|
|
3365
|
+
measurable,
|
|
3366
|
+
reason,
|
|
3367
|
+
evidenceRefs,
|
|
3368
|
+
};
|
|
3369
|
+
for (const key of Object.keys(requested)) {
|
|
3370
|
+
if (canonical(existing[key]) !== canonical(requested[key])) {
|
|
3371
|
+
throw new Error(`Cannot resume WorkItem scope revision ${revisionId}; ${key} differs.`);
|
|
3372
|
+
}
|
|
3373
|
+
}
|
|
3374
|
+
event = existing;
|
|
3375
|
+
} else {
|
|
3376
|
+
const previous = state.scopeRevisions.get(workItemId) ?? null;
|
|
3377
|
+
event = {
|
|
3378
|
+
schemaVersion: EVENT_SCHEMA_V5,
|
|
3379
|
+
eventId: `work-item-scope-revised-${revisionId}`,
|
|
3380
|
+
type: "work_item_scope_revised",
|
|
3381
|
+
occurredAt,
|
|
3382
|
+
revisionId,
|
|
3383
|
+
...(previous ? { supersedesRevisionId: previous.revisionId } : {}),
|
|
3384
|
+
assignmentId,
|
|
3385
|
+
workItemId,
|
|
3386
|
+
ownerAgentId,
|
|
3387
|
+
previousOwnedPaths: [...(previous?.ownedPaths ?? baseWorkItem.ownedPaths)],
|
|
3388
|
+
ownedPaths,
|
|
3389
|
+
previousMeasurable: structuredClone(
|
|
3390
|
+
previous ? previous.measurable : baseWorkItem.measurable ?? null,
|
|
3391
|
+
),
|
|
3392
|
+
measurable,
|
|
3393
|
+
reason,
|
|
3394
|
+
evidenceRefs,
|
|
3395
|
+
};
|
|
3396
|
+
}
|
|
3397
|
+
const recorded = appendTeamProjectEventV1({ workspaceRoot, event });
|
|
3398
|
+
const status = readTeamProjectStatusV3({ workspaceRoot });
|
|
3399
|
+
const workItem = status.workItems.find(row => row.workItemId === workItemId);
|
|
3400
|
+
return {
|
|
3401
|
+
status: "team_project_work_item_scope_revised",
|
|
3402
|
+
revisionId,
|
|
3403
|
+
workItemId,
|
|
3404
|
+
ownedPaths: [...workItem.ownedPaths],
|
|
3405
|
+
measurable: structuredClone(workItem.measurable),
|
|
3406
|
+
eventId: recorded.eventId,
|
|
3407
|
+
eventPath: recorded.eventPath,
|
|
3408
|
+
eventSha256: recorded.eventSha256,
|
|
3409
|
+
resumed: recorded.resumed,
|
|
3410
|
+
projectTruthHash: status.projectTruthHash,
|
|
3097
3411
|
nextAction: status.nextAction,
|
|
3098
3412
|
nextActorId: status.nextActorId,
|
|
3099
3413
|
authorizationGranted: false,
|
|
@@ -3106,14 +3420,59 @@ function legacyWorkItemProjection(workItem) {
|
|
|
3106
3420
|
const {
|
|
3107
3421
|
latestSourceDelivery: _latestSourceDelivery,
|
|
3108
3422
|
invalidatedByReopenIds: _invalidatedByReopenIds,
|
|
3423
|
+
ownedPaths: _ownedPaths,
|
|
3424
|
+
measurable: _measurable,
|
|
3425
|
+
scopeRevision: _scopeRevision,
|
|
3109
3426
|
...legacy
|
|
3110
3427
|
} = workItem;
|
|
3111
3428
|
return legacy;
|
|
3112
3429
|
}
|
|
3113
3430
|
|
|
3431
|
+
function statusV2WorkItemProjection(workItem) {
|
|
3432
|
+
if (workItem === null) return null;
|
|
3433
|
+
const {
|
|
3434
|
+
ownedPaths: _ownedPaths,
|
|
3435
|
+
measurable: _measurable,
|
|
3436
|
+
scopeRevision: _scopeRevision,
|
|
3437
|
+
...v2
|
|
3438
|
+
} = workItem;
|
|
3439
|
+
return v2;
|
|
3440
|
+
}
|
|
3441
|
+
|
|
3442
|
+
function statusV3CompletionProjection(status) {
|
|
3443
|
+
if (status.overall !== "completed") {
|
|
3444
|
+
return { ...status, deliveryDisposition: "work_in_progress" };
|
|
3445
|
+
}
|
|
3446
|
+
const dominantGap = {
|
|
3447
|
+
kind: "release_authority",
|
|
3448
|
+
id: "release-authority",
|
|
3449
|
+
title: "Separate release authority",
|
|
3450
|
+
workItemId: null,
|
|
3451
|
+
agentId: null,
|
|
3452
|
+
reason: "Project work ledger is complete, but Git, release, deployment, production, and business authority remain outside Project Driver.",
|
|
3453
|
+
truthRefs: uniqueSorted([
|
|
3454
|
+
"project/definition.json",
|
|
3455
|
+
...status.integrationGates.flatMap(gate => gate.truthRefs),
|
|
3456
|
+
]),
|
|
3457
|
+
};
|
|
3458
|
+
const nextAction = "Obtain separate Git/release/deployment authority if activation is intended; otherwise stop.";
|
|
3459
|
+
return {
|
|
3460
|
+
...status,
|
|
3461
|
+
deliveryDisposition: "code_complete_without_release_authority",
|
|
3462
|
+
headline: `code_complete_without_release_authority: work ledger completed; authorizationGranted=false; next: ${nextAction}`,
|
|
3463
|
+
dominantGap,
|
|
3464
|
+
nextAction,
|
|
3465
|
+
nextActorId: null,
|
|
3466
|
+
};
|
|
3467
|
+
}
|
|
3468
|
+
|
|
3114
3469
|
export function readTeamProjectStatusV1({ workspaceRoot }) {
|
|
3115
3470
|
const loaded = loadProject(workspaceRoot);
|
|
3116
|
-
const {
|
|
3471
|
+
const {
|
|
3472
|
+
sourceDeliveries: _sourceDeliveries,
|
|
3473
|
+
warnings: _warnings,
|
|
3474
|
+
...legacy
|
|
3475
|
+
} = projectStatus(
|
|
3117
3476
|
loaded.definition,
|
|
3118
3477
|
loaded.events,
|
|
3119
3478
|
loaded.root,
|
|
@@ -3128,8 +3487,27 @@ export function readTeamProjectStatusV1({ workspaceRoot }) {
|
|
|
3128
3487
|
|
|
3129
3488
|
export function readTeamProjectStatusV2({ workspaceRoot }) {
|
|
3130
3489
|
const loaded = loadProject(workspaceRoot);
|
|
3490
|
+
const { warnings: _warnings, ...v2 } = projectStatus(
|
|
3491
|
+
loaded.definition,
|
|
3492
|
+
loaded.events,
|
|
3493
|
+
loaded.root,
|
|
3494
|
+
);
|
|
3131
3495
|
return {
|
|
3132
|
-
...
|
|
3496
|
+
...v2,
|
|
3497
|
+
schemaVersion: STATUS_SCHEMA_V2,
|
|
3498
|
+
workItems: v2.workItems.map(statusV2WorkItemProjection),
|
|
3499
|
+
exitCode: 0,
|
|
3500
|
+
};
|
|
3501
|
+
}
|
|
3502
|
+
|
|
3503
|
+
export function readTeamProjectStatusV3({ workspaceRoot }) {
|
|
3504
|
+
const loaded = loadProject(workspaceRoot);
|
|
3505
|
+
return {
|
|
3506
|
+
...statusV3CompletionProjection(projectStatus(
|
|
3507
|
+
loaded.definition,
|
|
3508
|
+
loaded.events,
|
|
3509
|
+
loaded.root,
|
|
3510
|
+
)),
|
|
3133
3511
|
exitCode: 0,
|
|
3134
3512
|
};
|
|
3135
3513
|
}
|
|
@@ -3307,8 +3685,8 @@ export function buildTeamProjectTakeoverV2({ workspaceRoot, agentId }) {
|
|
|
3307
3685
|
projectNextAction: status.nextAction,
|
|
3308
3686
|
projectNextActorId: status.nextActorId,
|
|
3309
3687
|
agentId,
|
|
3310
|
-
currentResponsibility: current,
|
|
3311
|
-
allResponsibilities: responsibilities,
|
|
3688
|
+
currentResponsibility: statusV2WorkItemProjection(current),
|
|
3689
|
+
allResponsibilities: responsibilities.map(statusV2WorkItemProjection),
|
|
3312
3690
|
pendingHandoff,
|
|
3313
3691
|
staleHandoffs,
|
|
3314
3692
|
currentSourceDelivery,
|
|
@@ -3356,6 +3734,29 @@ export function buildTeamProjectTakeoverV1(input) {
|
|
|
3356
3734
|
};
|
|
3357
3735
|
}
|
|
3358
3736
|
|
|
3737
|
+
export function buildTeamProjectTakeoverV3(input) {
|
|
3738
|
+
const legacy = buildTeamProjectTakeoverV2(input);
|
|
3739
|
+
const status = readTeamProjectStatusV3({ workspaceRoot: input.workspaceRoot });
|
|
3740
|
+
const byId = new Map(status.workItems.map(row => [row.workItemId, row]));
|
|
3741
|
+
return {
|
|
3742
|
+
...legacy,
|
|
3743
|
+
schemaVersion: "OwlCodaRunKitTeamProjectTakeoverV3",
|
|
3744
|
+
projectHeadline: status.headline,
|
|
3745
|
+
dominantGap: status.dominantGap,
|
|
3746
|
+
projectNextAction: status.nextAction,
|
|
3747
|
+
projectNextActorId: status.nextActorId,
|
|
3748
|
+
currentResponsibility: legacy.currentResponsibility
|
|
3749
|
+
? byId.get(legacy.currentResponsibility.workItemId) ?? null
|
|
3750
|
+
: null,
|
|
3751
|
+
allResponsibilities: legacy.allResponsibilities
|
|
3752
|
+
.map(row => byId.get(row.workItemId))
|
|
3753
|
+
.filter(Boolean),
|
|
3754
|
+
deliveryDisposition: status.deliveryDisposition,
|
|
3755
|
+
warnings: status.warnings,
|
|
3756
|
+
authorizationGranted: false,
|
|
3757
|
+
};
|
|
3758
|
+
}
|
|
3759
|
+
|
|
3359
3760
|
export function formatTeamProjectStatusHumanV1(status) {
|
|
3360
3761
|
const nextAction = status.nextAction === null ? "none" : singleLine(status.nextAction);
|
|
3361
3762
|
return [
|
|
@@ -3382,7 +3783,9 @@ export function formatTeamProjectStatusHumanV2(status) {
|
|
|
3382
3783
|
return [
|
|
3383
3784
|
status.headline,
|
|
3384
3785
|
`Project: ${status.projectId} — ${singleLine(status.objective)}`,
|
|
3385
|
-
`
|
|
3786
|
+
`Work ledger: ${status.overall}${status.deliveryDisposition === "code_complete_without_release_authority"
|
|
3787
|
+
? `; delivery: ${status.deliveryDisposition}`
|
|
3788
|
+
: ""}`,
|
|
3386
3789
|
`Work: ${status.counts.completed}/${status.counts.total} completed; ${status.counts.active} active; ${status.counts.waiting_dependency + status.counts.waiting_decision} waiting`,
|
|
3387
3790
|
`Agents: ${status.agents.map(agent => agent.agentId).join(", ") || "none assigned"}`,
|
|
3388
3791
|
`Open decisions: ${status.openDecisions.map(row => row.decisionId).join(", ") || "none"}`,
|
|
@@ -3395,6 +3798,9 @@ export function formatTeamProjectStatusHumanV2(status) {
|
|
|
3395
3798
|
`Source deliveries: ${status.sourceDeliveries.length}${latestSourceDelivery
|
|
3396
3799
|
? `; latest ${latestSourceDelivery.deliveryId} (${latestSourceDelivery.overlayStatus}, ${latestSourceDelivery.deliveryCandidateFingerprint})`
|
|
3397
3800
|
: ""}`,
|
|
3801
|
+
...(status.warnings?.length > 0
|
|
3802
|
+
? [`Warnings: ${status.warnings.map(row => `${row.code}:${row.workItemId}`).join(", ")}`]
|
|
3803
|
+
: []),
|
|
3398
3804
|
`Next: ${nextAction}${status.nextActorId ? ` (owner ${status.nextActorId})` : ""}`,
|
|
3399
3805
|
"",
|
|
3400
3806
|
].join("\n");
|
|
@@ -3411,6 +3817,9 @@ export function formatTeamProjectTakeoverHumanV2(takeover) {
|
|
|
3411
3817
|
return [
|
|
3412
3818
|
takeover.projectHeadline,
|
|
3413
3819
|
`Project: ${takeover.projectId} — ${singleLine(takeover.objective)}`,
|
|
3820
|
+
...(takeover.deliveryDisposition
|
|
3821
|
+
? [`Delivery disposition: ${takeover.deliveryDisposition}`]
|
|
3822
|
+
: []),
|
|
3414
3823
|
`Agent: ${takeover.agentId}`,
|
|
3415
3824
|
`Responsibility: ${responsibility?.workItemId ?? "none"}`,
|
|
3416
3825
|
`Open decisions: ${takeover.unresolvedDecisions.map(row => row.decisionId).join(", ") || "none"}`,
|
|
@@ -3431,6 +3840,9 @@ export function formatTeamProjectTakeoverHumanV2(takeover) {
|
|
|
3431
3840
|
`Agent next: ${agentNextAction}`,
|
|
3432
3841
|
`Project next: ${projectNextAction}${takeover.projectNextActorId ? ` (owner ${takeover.projectNextActorId})` : ""}`,
|
|
3433
3842
|
`State: ${takeover.overall}`,
|
|
3843
|
+
...(takeover.warnings?.length > 0
|
|
3844
|
+
? [`Warnings: ${takeover.warnings.map(row => `${row.code}:${row.workItemId}`).join(", ")}`]
|
|
3845
|
+
: []),
|
|
3434
3846
|
"",
|
|
3435
3847
|
].join("\n");
|
|
3436
3848
|
}
|