owlrunkit 0.19.2 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +63 -0
- package/README.md +94 -8
- package/docs/architecture/OWLCODA_RUNKIT_0201_RELEASE_TRUTH_AND_PROFILE_NORMALIZATION_PATCH_DECISION_20260813.md +95 -0
- package/docs/architecture/OWLCODA_RUNKIT_020_TEAM_DELIVERY_PHASE1_PRODUCT_DECISION_20260813.md +296 -0
- package/docs/architecture/OWLCODA_RUNKIT_021_TEAM_DELIVERY_PHASE2_PRODUCT_DECISION_20260813.md +249 -0
- package/integrations/codex/skills/owlcoda-runkit/SKILL.md +114 -12
- package/integrations/codex/skills/owlcoda-runkit/assets/templates/config.json +3 -3
- package/package.json +1 -1
- package/packages/attest/README.md +7 -1
- package/packages/attest/consumer-surface-v3.json +275 -0
- package/packages/attest/distribution-manifest-v1.json +19 -0
- package/packages/attest/schemas/canonical-candidate-identity-v1.schema.json +112 -0
- package/packages/attest/schemas/foreign-workspace-filesystem-identity-v1.schema.json +32 -0
- package/packages/attest/schemas/quick-verification-receipt-v4.schema.json +226 -0
- package/packages/attest/src/candidate-identity.mjs +454 -0
- package/packages/attest/src/index.mjs +11 -0
- package/packages/attest/src/quick-receipt-contract.mjs +126 -5
- package/packages/attest/src/quick.mjs +48 -8
- package/{registry-release-policy-v1.json → registry-release-policy-v2.json} +8 -9
- package/sbom.spdx.json +4 -4
- package/scripts/runkit-contract/core-contract.mjs +21 -1
- package/scripts/runkit-contract/fleet-skill-plan.mjs +223 -0
- package/scripts/runkit-contract/formal-workflow.mjs +3 -1
- package/scripts/runkit-contract/inspect-presentation.mjs +56 -0
- package/scripts/runkit-contract/install-codex-skill.mjs +1 -0
- package/scripts/runkit-contract/onboarding-bootstrap.mjs +15 -0
- package/scripts/runkit-contract/onboarding-doctor.mjs +201 -7
- package/scripts/runkit-contract/profile-onboarding.mjs +6 -0
- package/scripts/runkit-contract/project-cli-resolver.mjs +12 -1
- package/scripts/runkit-contract/quick-metrics.mjs +9 -5
- package/scripts/runkit-contract/quick-verify.mjs +108 -24
- package/scripts/runkit-contract/registry-adoption-gate.mjs +12 -3
- package/scripts/runkit-contract/registry-adoption.mjs +10 -5
- package/scripts/runkit-contract/runkit-cli.mjs +367 -20
- package/scripts/runkit-contract/schemas/candidate-transfer-packet-v1.schema.json +98 -0
- package/scripts/runkit-contract/schemas/delivery-lifecycle-v1.schema.json +91 -0
- package/scripts/runkit-contract/schemas/external-gate-v1.schema.json +68 -0
- package/scripts/runkit-contract/schemas/fleet-skill-upgrade-plan-v1.schema.json +140 -0
- package/scripts/runkit-contract/schemas/inspect-compact-v1.schema.json +93 -0
- package/scripts/runkit-contract/schemas/team-acceptance-packet-v2.schema.json +109 -0
- package/scripts/runkit-contract/schemas/team-delivery-status-v1.schema.json +72 -0
- package/scripts/runkit-contract/schemas/team-delivery-status-v2.schema.json +219 -0
- package/scripts/runkit-contract/schemas/team-packet-validation-v1.schema.json +33 -0
- package/scripts/runkit-contract/schemas/team-project-event-v6.schema.json +82 -0
- package/scripts/runkit-contract/schemas/team-project-event-v7.schema.json +80 -0
- package/scripts/runkit-contract/schemas/team-project-status-v4.schema.json +208 -0
- package/scripts/runkit-contract/schemas/team-recommendation-v1.schema.json +121 -0
- package/scripts/runkit-contract/schemas/team-task-packet-v2.schema.json +228 -0
- package/scripts/runkit-contract/sync-release-identity.mjs +87 -0
- package/scripts/runkit-contract/team-delivery.mjs +1031 -0
- package/scripts/runkit-contract/team-project.mjs +970 -9
|
@@ -17,6 +17,8 @@ import {
|
|
|
17
17
|
quickSnapshotFingerprintValid,
|
|
18
18
|
validWorkspaceSnapshotShape,
|
|
19
19
|
} from "../../packages/attest/src/quick-receipt-contract.mjs";
|
|
20
|
+
import { readFileBytesBounded } from "../../packages/attest/src/formal.mjs";
|
|
21
|
+
import { attestCoreBoundQuickReceiptDetailsFromBytes } from "./quick-attest.mjs";
|
|
20
22
|
import {
|
|
21
23
|
assertAllowedKeys,
|
|
22
24
|
isRecord,
|
|
@@ -32,9 +34,12 @@ const EVENT_SCHEMA_V2 = "OwlCodaRunKitTeamProjectEventV2";
|
|
|
32
34
|
const EVENT_SCHEMA_V3 = "OwlCodaRunKitTeamProjectEventV3";
|
|
33
35
|
const EVENT_SCHEMA_V4 = "OwlCodaRunKitTeamProjectEventV4";
|
|
34
36
|
const EVENT_SCHEMA_V5 = "OwlCodaRunKitTeamProjectEventV5";
|
|
37
|
+
const EVENT_SCHEMA_V6 = "OwlCodaRunKitTeamProjectEventV6";
|
|
38
|
+
const EVENT_SCHEMA_V7 = "OwlCodaRunKitTeamProjectEventV7";
|
|
35
39
|
const STATUS_SCHEMA_V1 = "OwlCodaRunKitTeamProjectStatusV1";
|
|
36
40
|
const STATUS_SCHEMA_V2 = "OwlCodaRunKitTeamProjectStatusV2";
|
|
37
41
|
const STATUS_SCHEMA_V3 = "OwlCodaRunKitTeamProjectStatusV3";
|
|
42
|
+
const STATUS_SCHEMA_V4 = "OwlCodaRunKitTeamProjectStatusV4";
|
|
38
43
|
|
|
39
44
|
const DEFINITION_KEYS = [
|
|
40
45
|
"schemaVersion",
|
|
@@ -55,6 +60,31 @@ const WORK_ITEM_STATES = new Set([
|
|
|
55
60
|
"failed",
|
|
56
61
|
]);
|
|
57
62
|
const VERIFICATION_DISPOSITIONS = new Set(["verified", "no_longer_required"]);
|
|
63
|
+
const DELIVERY_STAGES = Object.freeze([
|
|
64
|
+
"source",
|
|
65
|
+
"integration",
|
|
66
|
+
"materialization",
|
|
67
|
+
"deployment",
|
|
68
|
+
"live_readback",
|
|
69
|
+
"product_acceptance",
|
|
70
|
+
]);
|
|
71
|
+
const DELIVERY_STAGE_OUTCOMES = Object.freeze({
|
|
72
|
+
source: new Set(["accepted", "rejected"]),
|
|
73
|
+
integration: new Set(["integrated", "rejected"]),
|
|
74
|
+
materialization: new Set(["materialized", "failed"]),
|
|
75
|
+
deployment: new Set(["deployed", "failed", "rolled_back"]),
|
|
76
|
+
live_readback: new Set(["passed", "failed"]),
|
|
77
|
+
product_acceptance: new Set(["accepted", "rejected", "deferred"]),
|
|
78
|
+
});
|
|
79
|
+
const DELIVERY_POSITIVE_OUTCOME = Object.freeze({
|
|
80
|
+
source: "accepted",
|
|
81
|
+
integration: "integrated",
|
|
82
|
+
materialization: "materialized",
|
|
83
|
+
deployment: "deployed",
|
|
84
|
+
live_readback: "passed",
|
|
85
|
+
product_acceptance: "accepted",
|
|
86
|
+
});
|
|
87
|
+
const EXTERNAL_GATE_STATUSES = new Set(["pending", "passed", "blocked", "deferred"]);
|
|
58
88
|
const EVENT_KEYS = Object.freeze({
|
|
59
89
|
agent_assigned: [
|
|
60
90
|
"schemaVersion", "eventId", "type", "occurredAt", "assignmentId",
|
|
@@ -132,6 +162,28 @@ const EVENT_KEYS = Object.freeze({
|
|
|
132
162
|
"previousOwnedPaths", "ownedPaths", "previousMeasurable", "measurable",
|
|
133
163
|
"reason", "evidenceRefs",
|
|
134
164
|
],
|
|
165
|
+
external_gate_reconciled: [
|
|
166
|
+
"schemaVersion", "eventId", "type", "occurredAt", "reconciliationId",
|
|
167
|
+
"supersedesReconciliationId", "gateId", "title", "requiredFor",
|
|
168
|
+
"gateStatus", "ownerAgentId", "sourceAdapterId", "sourceRef",
|
|
169
|
+
"sourceSha256", "summary", "evidenceRefs", "authorizationGranted",
|
|
170
|
+
],
|
|
171
|
+
delivery_lifecycle_recorded: [
|
|
172
|
+
"schemaVersion", "eventId", "type", "occurredAt", "recordId",
|
|
173
|
+
"supersedesRecordId", "stage", "stageStatus", "ownerAgentId",
|
|
174
|
+
"summary", "evidenceRefs", "externalGateIds", "authorizationGranted",
|
|
175
|
+
],
|
|
176
|
+
work_item_evidence_requirement_set: [
|
|
177
|
+
"schemaVersion", "eventId", "type", "occurredAt", "policyId",
|
|
178
|
+
"supersedesPolicyId", "workItemId", "ownerAgentId", "requirement",
|
|
179
|
+
"reason", "evidenceRefs", "authorizationGranted",
|
|
180
|
+
],
|
|
181
|
+
checkpoint_receipt_bound: [
|
|
182
|
+
"schemaVersion", "eventId", "type", "occurredAt", "checkpointId",
|
|
183
|
+
"assignmentId", "workItemId", "state", "summary", "completedUnits",
|
|
184
|
+
"evidenceRefs", "blockerRefs", "decisionRefs", "nextAction",
|
|
185
|
+
"sourceFingerprint", "receiptBinding", "authorizationGranted",
|
|
186
|
+
],
|
|
135
187
|
});
|
|
136
188
|
|
|
137
189
|
function canonical(value) {
|
|
@@ -672,10 +724,10 @@ function validateEventShape(value, definition) {
|
|
|
672
724
|
throw new Error("Team project event type is invalid.");
|
|
673
725
|
}
|
|
674
726
|
assertAllowedKeys(value, `Team project event ${value.eventId ?? "unknown"}`, EVENT_KEYS[value.type]);
|
|
675
|
-
if (![EVENT_SCHEMA, EVENT_SCHEMA_V2, EVENT_SCHEMA_V3, EVENT_SCHEMA_V4, EVENT_SCHEMA_V5]
|
|
727
|
+
if (![EVENT_SCHEMA, EVENT_SCHEMA_V2, EVENT_SCHEMA_V3, EVENT_SCHEMA_V4, EVENT_SCHEMA_V5, EVENT_SCHEMA_V6, EVENT_SCHEMA_V7]
|
|
676
728
|
.includes(value.schemaVersion)) {
|
|
677
729
|
throw new Error(
|
|
678
|
-
`Team project event schemaVersion must be ${EVENT_SCHEMA}, ${EVENT_SCHEMA_V2}, ${EVENT_SCHEMA_V3}, ${EVENT_SCHEMA_V4}, or ${
|
|
730
|
+
`Team project event schemaVersion must be ${EVENT_SCHEMA}, ${EVENT_SCHEMA_V2}, ${EVENT_SCHEMA_V3}, ${EVENT_SCHEMA_V4}, ${EVENT_SCHEMA_V5}, ${EVENT_SCHEMA_V6}, or ${EVENT_SCHEMA_V7}.`,
|
|
679
731
|
);
|
|
680
732
|
}
|
|
681
733
|
if (value.type === "rework_returned" && value.schemaVersion !== EVENT_SCHEMA_V2) {
|
|
@@ -702,6 +754,27 @@ function validateEventShape(value, definition) {
|
|
|
702
754
|
if (value.schemaVersion === EVENT_SCHEMA_V5 && value.type !== "work_item_scope_revised") {
|
|
703
755
|
throw new Error(`${EVENT_SCHEMA_V5} is reserved for work_item_scope_revised events.`);
|
|
704
756
|
}
|
|
757
|
+
const eventV6Types = new Set(["external_gate_reconciled", "delivery_lifecycle_recorded"]);
|
|
758
|
+
if (eventV6Types.has(value.type) && value.schemaVersion !== EVENT_SCHEMA_V6) {
|
|
759
|
+
throw new Error(`${value.type} requires schemaVersion ${EVENT_SCHEMA_V6}.`);
|
|
760
|
+
}
|
|
761
|
+
if (value.schemaVersion === EVENT_SCHEMA_V6 && !eventV6Types.has(value.type)) {
|
|
762
|
+
throw new Error(
|
|
763
|
+
`${EVENT_SCHEMA_V6} is reserved for external Gate and delivery lifecycle events.`,
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
const eventV7Types = new Set([
|
|
767
|
+
"work_item_evidence_requirement_set",
|
|
768
|
+
"checkpoint_receipt_bound",
|
|
769
|
+
]);
|
|
770
|
+
if (eventV7Types.has(value.type) && value.schemaVersion !== EVENT_SCHEMA_V7) {
|
|
771
|
+
throw new Error(`${value.type} requires schemaVersion ${EVENT_SCHEMA_V7}.`);
|
|
772
|
+
}
|
|
773
|
+
if (value.schemaVersion === EVENT_SCHEMA_V7 && !eventV7Types.has(value.type)) {
|
|
774
|
+
throw new Error(
|
|
775
|
+
`${EVENT_SCHEMA_V7} is reserved for WorkItem evidence requirements and receipt binding.`,
|
|
776
|
+
);
|
|
777
|
+
}
|
|
705
778
|
safeIdentifier(value.eventId, "eventId");
|
|
706
779
|
const normalizedOccurredAt = normalizeTimestamp(value.occurredAt);
|
|
707
780
|
const workItemIds = new Set(definition.workItems.map(row => row.id));
|
|
@@ -720,6 +793,8 @@ function validateEventShape(value, definition) {
|
|
|
720
793
|
"responsibility_transferred",
|
|
721
794
|
"work_item_scope_revised",
|
|
722
795
|
"verification_deferred",
|
|
796
|
+
"work_item_evidence_requirement_set",
|
|
797
|
+
"checkpoint_receipt_bound",
|
|
723
798
|
].includes(value.type)) {
|
|
724
799
|
assertWorkItem(value.workItemId);
|
|
725
800
|
}
|
|
@@ -732,6 +807,7 @@ function validateEventShape(value, definition) {
|
|
|
732
807
|
"upstream_reopened",
|
|
733
808
|
"responsibility_transferred",
|
|
734
809
|
"work_item_scope_revised",
|
|
810
|
+
"checkpoint_receipt_bound",
|
|
735
811
|
].includes(value.type)) {
|
|
736
812
|
safeIdentifier(value.assignmentId, "assignmentId");
|
|
737
813
|
}
|
|
@@ -1015,6 +1091,107 @@ function validateEventShape(value, definition) {
|
|
|
1015
1091
|
previousMeasurable,
|
|
1016
1092
|
measurable,
|
|
1017
1093
|
};
|
|
1094
|
+
} else if (value.type === "external_gate_reconciled") {
|
|
1095
|
+
safeIdentifier(value.reconciliationId, "reconciliationId");
|
|
1096
|
+
if (value.supersedesReconciliationId !== undefined) {
|
|
1097
|
+
safeIdentifier(value.supersedesReconciliationId, "supersedesReconciliationId");
|
|
1098
|
+
}
|
|
1099
|
+
safeIdentifier(value.gateId, "external gateId");
|
|
1100
|
+
nonEmptyString(value.title, "External Gate title");
|
|
1101
|
+
if (!DELIVERY_STAGES.includes(value.requiredFor)) {
|
|
1102
|
+
throw new Error("External Gate requiredFor must be a delivery stage.");
|
|
1103
|
+
}
|
|
1104
|
+
if (!EXTERNAL_GATE_STATUSES.has(value.gateStatus)) {
|
|
1105
|
+
throw new Error("External Gate status is invalid.");
|
|
1106
|
+
}
|
|
1107
|
+
safeIdentifier(value.ownerAgentId, "External Gate ownerAgentId");
|
|
1108
|
+
safeIdentifier(value.sourceAdapterId, "External Gate sourceAdapterId");
|
|
1109
|
+
nonEmptyString(value.sourceRef, "External Gate sourceRef");
|
|
1110
|
+
sha256Ref(value.sourceSha256, "External Gate sourceSha256");
|
|
1111
|
+
nonEmptyString(value.summary, "External Gate summary");
|
|
1112
|
+
stringArray(value.evidenceRefs, "External Gate evidenceRefs", { allowEmpty: false });
|
|
1113
|
+
if (value.authorizationGranted !== false) {
|
|
1114
|
+
throw new Error("External Gate authorizationGranted must remain false.");
|
|
1115
|
+
}
|
|
1116
|
+
} else if (value.type === "delivery_lifecycle_recorded") {
|
|
1117
|
+
safeIdentifier(value.recordId, "delivery recordId");
|
|
1118
|
+
if (value.supersedesRecordId !== undefined) {
|
|
1119
|
+
safeIdentifier(value.supersedesRecordId, "supersedesRecordId");
|
|
1120
|
+
}
|
|
1121
|
+
if (!DELIVERY_STAGES.includes(value.stage)) {
|
|
1122
|
+
throw new Error("Delivery lifecycle stage is invalid.");
|
|
1123
|
+
}
|
|
1124
|
+
if (!DELIVERY_STAGE_OUTCOMES[value.stage].has(value.stageStatus)) {
|
|
1125
|
+
throw new Error(`Delivery lifecycle outcome is invalid for ${value.stage}.`);
|
|
1126
|
+
}
|
|
1127
|
+
safeIdentifier(value.ownerAgentId, "Delivery lifecycle ownerAgentId");
|
|
1128
|
+
nonEmptyString(value.summary, "Delivery lifecycle summary");
|
|
1129
|
+
stringArray(
|
|
1130
|
+
value.evidenceRefs,
|
|
1131
|
+
"Delivery lifecycle evidenceRefs",
|
|
1132
|
+
{ allowEmpty: false },
|
|
1133
|
+
);
|
|
1134
|
+
const gateIdsForRecord = stringArray(
|
|
1135
|
+
value.externalGateIds,
|
|
1136
|
+
"Delivery lifecycle externalGateIds",
|
|
1137
|
+
);
|
|
1138
|
+
for (const gateId of gateIdsForRecord) safeIdentifier(gateId, "externalGateId");
|
|
1139
|
+
if (value.authorizationGranted !== false) {
|
|
1140
|
+
throw new Error("Delivery lifecycle authorizationGranted must remain false.");
|
|
1141
|
+
}
|
|
1142
|
+
} else if (value.type === "work_item_evidence_requirement_set") {
|
|
1143
|
+
safeIdentifier(value.policyId, "evidence policyId");
|
|
1144
|
+
if (value.supersedesPolicyId !== undefined) {
|
|
1145
|
+
safeIdentifier(value.supersedesPolicyId, "supersedesPolicyId");
|
|
1146
|
+
}
|
|
1147
|
+
safeIdentifier(value.ownerAgentId, "Evidence requirement ownerAgentId");
|
|
1148
|
+
if (!new Set(["none", "quick"]).has(value.requirement)) {
|
|
1149
|
+
throw new Error("WorkItem evidence requirement is invalid.");
|
|
1150
|
+
}
|
|
1151
|
+
nonEmptyString(value.reason, "WorkItem evidence requirement reason");
|
|
1152
|
+
stringArray(
|
|
1153
|
+
value.evidenceRefs,
|
|
1154
|
+
"WorkItem evidence requirement evidenceRefs",
|
|
1155
|
+
{ allowEmpty: false },
|
|
1156
|
+
);
|
|
1157
|
+
if (value.authorizationGranted !== false) {
|
|
1158
|
+
throw new Error("WorkItem evidence requirement authorizationGranted must remain false.");
|
|
1159
|
+
}
|
|
1160
|
+
} else if (value.type === "checkpoint_receipt_bound") {
|
|
1161
|
+
safeIdentifier(value.checkpointId, "checkpointId");
|
|
1162
|
+
if (!WORK_ITEM_STATES.has(value.state)) throw new Error("Checkpoint state is invalid.");
|
|
1163
|
+
nonEmptyString(value.summary, "Checkpoint summary");
|
|
1164
|
+
stringArray(value.evidenceRefs, "Checkpoint evidenceRefs");
|
|
1165
|
+
stringArray(value.blockerRefs, "Checkpoint blockerRefs");
|
|
1166
|
+
stringArray(value.decisionRefs, "Checkpoint decisionRefs");
|
|
1167
|
+
nullableString(value.nextAction, "Checkpoint nextAction");
|
|
1168
|
+
if (value.sourceFingerprint !== undefined
|
|
1169
|
+
&& !/^(?:sha256:)?[a-f0-9]{64}$/u.test(value.sourceFingerprint)) {
|
|
1170
|
+
throw new Error("Checkpoint sourceFingerprint is invalid.");
|
|
1171
|
+
}
|
|
1172
|
+
if (value.completedUnits !== undefined
|
|
1173
|
+
&& (!Number.isSafeInteger(value.completedUnits) || value.completedUnits < 0)) {
|
|
1174
|
+
throw new Error("Checkpoint completedUnits must be a non-negative integer.");
|
|
1175
|
+
}
|
|
1176
|
+
if (!isRecord(value.receiptBinding)) {
|
|
1177
|
+
throw new Error("Checkpoint receiptBinding must be an object.");
|
|
1178
|
+
}
|
|
1179
|
+
assertAllowedKeys(value.receiptBinding, "Checkpoint receiptBinding", [
|
|
1180
|
+
"kind", "receiptId", "receiptRef", "receiptSha256",
|
|
1181
|
+
"sourceFingerprint", "attestationDecision", "issueCodes",
|
|
1182
|
+
]);
|
|
1183
|
+
if (value.receiptBinding.kind !== "quick"
|
|
1184
|
+
|| value.receiptBinding.attestationDecision !== "GO") {
|
|
1185
|
+
throw new Error("Checkpoint receiptBinding must be one GO Quick receipt.");
|
|
1186
|
+
}
|
|
1187
|
+
safeIdentifier(value.receiptBinding.receiptId, "Quick receiptId");
|
|
1188
|
+
safeSourcePath(value.receiptBinding.receiptRef, "Quick receiptRef");
|
|
1189
|
+
sha256Ref(value.receiptBinding.receiptSha256, "Quick receiptSha256");
|
|
1190
|
+
sha256Ref(value.receiptBinding.sourceFingerprint, "Quick sourceFingerprint");
|
|
1191
|
+
stringArray(value.receiptBinding.issueCodes, "Quick attestation issueCodes");
|
|
1192
|
+
if (value.authorizationGranted !== false) {
|
|
1193
|
+
throw new Error("Receipt-bound checkpoint authorizationGranted must remain false.");
|
|
1194
|
+
}
|
|
1018
1195
|
}
|
|
1019
1196
|
return { ...structuredClone(value), occurredAt: normalizedOccurredAt };
|
|
1020
1197
|
}
|
|
@@ -1034,6 +1211,10 @@ function projectEvents(definition, records) {
|
|
|
1034
1211
|
const sourceDeliveries = [];
|
|
1035
1212
|
const scopeRevisions = new Map();
|
|
1036
1213
|
const scopeRevisionHistory = new Map(definition.workItems.map(row => [row.id, []]));
|
|
1214
|
+
const externalGates = new Map();
|
|
1215
|
+
const deliveryRecords = new Map();
|
|
1216
|
+
const evidenceRequirements = new Map();
|
|
1217
|
+
const receiptBindings = new Map();
|
|
1037
1218
|
const seenAssignments = new Set();
|
|
1038
1219
|
const seenReworks = new Set();
|
|
1039
1220
|
const seenReopens = new Set();
|
|
@@ -1041,6 +1222,10 @@ function projectEvents(definition, records) {
|
|
|
1041
1222
|
const seenDecisions = new Set();
|
|
1042
1223
|
const seenDeliveries = new Set();
|
|
1043
1224
|
const seenScopeRevisions = new Set();
|
|
1225
|
+
const seenExternalGateReconciliations = new Set();
|
|
1226
|
+
const seenDeliveryRecords = new Set();
|
|
1227
|
+
const seenEvidencePolicies = new Set();
|
|
1228
|
+
const seenReceiptBindings = new Set();
|
|
1044
1229
|
const seenFactIds = new Set(records.map(row => row.event.eventId));
|
|
1045
1230
|
|
|
1046
1231
|
for (const { event, ref } of records) {
|
|
@@ -1347,6 +1532,130 @@ function projectEvents(definition, records) {
|
|
|
1347
1532
|
assignments.set(event.workItemId, assignment);
|
|
1348
1533
|
assignmentHistory.get(event.workItemId).push(assignment);
|
|
1349
1534
|
transfers.set(event.workItemId, { ...event, ref });
|
|
1535
|
+
} else if (event.type === "external_gate_reconciled") {
|
|
1536
|
+
if (seenExternalGateReconciliations.has(event.reconciliationId)) {
|
|
1537
|
+
throw new Error(`Duplicate external Gate reconciliation ${event.reconciliationId}.`);
|
|
1538
|
+
}
|
|
1539
|
+
const previous = externalGates.get(event.gateId) ?? null;
|
|
1540
|
+
if (event.supersedesReconciliationId !== previous?.reconciliationId) {
|
|
1541
|
+
throw new Error(
|
|
1542
|
+
`External Gate ${event.gateId} must supersede current reconciliation `
|
|
1543
|
+
+ `${previous?.reconciliationId ?? "none"}.`,
|
|
1544
|
+
);
|
|
1545
|
+
}
|
|
1546
|
+
if (previous && previous.requiredFor !== event.requiredFor) {
|
|
1547
|
+
throw new Error(
|
|
1548
|
+
`External Gate ${event.gateId} cannot change required delivery stage.`,
|
|
1549
|
+
);
|
|
1550
|
+
}
|
|
1551
|
+
seenExternalGateReconciliations.add(event.reconciliationId);
|
|
1552
|
+
externalGates.set(event.gateId, { ...event, ref });
|
|
1553
|
+
} else if (event.type === "delivery_lifecycle_recorded") {
|
|
1554
|
+
if (seenDeliveryRecords.has(event.recordId)) {
|
|
1555
|
+
throw new Error(`Duplicate delivery lifecycle record ${event.recordId}.`);
|
|
1556
|
+
}
|
|
1557
|
+
const previous = deliveryRecords.get(event.stage) ?? null;
|
|
1558
|
+
if (event.supersedesRecordId !== previous?.recordId) {
|
|
1559
|
+
throw new Error(
|
|
1560
|
+
`Delivery stage ${event.stage} must supersede current record `
|
|
1561
|
+
+ `${previous?.recordId ?? "none"}.`,
|
|
1562
|
+
);
|
|
1563
|
+
}
|
|
1564
|
+
const stageIndex = DELIVERY_STAGES.indexOf(event.stage);
|
|
1565
|
+
const downstream = DELIVERY_STAGES.slice(stageIndex + 1)
|
|
1566
|
+
.find(stage => deliveryRecords.has(stage));
|
|
1567
|
+
if (downstream) {
|
|
1568
|
+
throw new Error(
|
|
1569
|
+
`Delivery stage ${event.stage} cannot change after downstream delivery truth already exists.`,
|
|
1570
|
+
);
|
|
1571
|
+
}
|
|
1572
|
+
const decisionsResolved = [...decisions.values()].every(row => row.resolved !== null);
|
|
1573
|
+
const stateSnapshot = {
|
|
1574
|
+
checkpoints,
|
|
1575
|
+
reopens,
|
|
1576
|
+
scopeRevisions,
|
|
1577
|
+
};
|
|
1578
|
+
if (completedWorkItemIds(definition, stateSnapshot).size !== definition.workItems.length
|
|
1579
|
+
|| gatePasses.size !== definition.integrationGates.length
|
|
1580
|
+
|| !decisionsResolved) {
|
|
1581
|
+
throw new Error("Delivery lifecycle requires a completed Project Driver work ledger.");
|
|
1582
|
+
}
|
|
1583
|
+
if (stageIndex > 0) {
|
|
1584
|
+
const prerequisiteStage = DELIVERY_STAGES[stageIndex - 1];
|
|
1585
|
+
const prerequisite = deliveryRecords.get(prerequisiteStage) ?? null;
|
|
1586
|
+
const requiredOutcome = DELIVERY_POSITIVE_OUTCOME[prerequisiteStage];
|
|
1587
|
+
if (prerequisite?.stageStatus !== requiredOutcome) {
|
|
1588
|
+
throw new Error(
|
|
1589
|
+
`Delivery stage ${event.stage} requires ${prerequisiteStage}=${requiredOutcome}.`,
|
|
1590
|
+
);
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
const requiredGates = [...externalGates.values()]
|
|
1594
|
+
.filter(gate => gate.requiredFor === event.stage)
|
|
1595
|
+
.sort((left, right) => compareIdentifiers(left.gateId, right.gateId));
|
|
1596
|
+
const unresolvedGate = requiredGates.find(gate => gate.gateStatus !== "passed");
|
|
1597
|
+
if (unresolvedGate) {
|
|
1598
|
+
throw new Error(
|
|
1599
|
+
`Delivery stage ${event.stage} external Gate ${unresolvedGate.gateId} is `
|
|
1600
|
+
+ `${unresolvedGate.gateStatus}.`,
|
|
1601
|
+
);
|
|
1602
|
+
}
|
|
1603
|
+
const requiredGateIds = requiredGates.map(gate => gate.gateId);
|
|
1604
|
+
if (canonical(event.externalGateIds) !== canonical(requiredGateIds)) {
|
|
1605
|
+
throw new Error(
|
|
1606
|
+
`Delivery stage ${event.stage} must bind passed external Gates: `
|
|
1607
|
+
+ `${requiredGateIds.join(", ") || "none"}.`,
|
|
1608
|
+
);
|
|
1609
|
+
}
|
|
1610
|
+
seenDeliveryRecords.add(event.recordId);
|
|
1611
|
+
deliveryRecords.set(event.stage, { ...event, ref });
|
|
1612
|
+
} else if (event.type === "work_item_evidence_requirement_set") {
|
|
1613
|
+
if (seenEvidencePolicies.has(event.policyId)) {
|
|
1614
|
+
throw new Error(`Duplicate WorkItem evidence policy ${event.policyId}.`);
|
|
1615
|
+
}
|
|
1616
|
+
const current = evidenceRequirements.get(event.workItemId) ?? null;
|
|
1617
|
+
if (event.supersedesPolicyId !== current?.policyId) {
|
|
1618
|
+
throw new Error(
|
|
1619
|
+
`WorkItem ${event.workItemId} evidence policy must supersede `
|
|
1620
|
+
+ `${current?.policyId ?? "none"}.`,
|
|
1621
|
+
);
|
|
1622
|
+
}
|
|
1623
|
+
const assignment = assignments.get(event.workItemId) ?? null;
|
|
1624
|
+
if (assignment?.agentId !== event.ownerAgentId) {
|
|
1625
|
+
throw new Error("WorkItem evidence policy must bind the current owner.");
|
|
1626
|
+
}
|
|
1627
|
+
const checkpoint = checkpoints.get(event.workItemId) ?? null;
|
|
1628
|
+
if (checkpoint && ["completed", "failed"].includes(checkpoint.state)) {
|
|
1629
|
+
throw new Error("WorkItem evidence policy cannot change terminal WorkItem truth.");
|
|
1630
|
+
}
|
|
1631
|
+
seenEvidencePolicies.add(event.policyId);
|
|
1632
|
+
evidenceRequirements.set(event.workItemId, { ...event, ref });
|
|
1633
|
+
} else if (event.type === "checkpoint_receipt_bound") {
|
|
1634
|
+
if (seenReceiptBindings.has(event.checkpointId)) {
|
|
1635
|
+
throw new Error(`Duplicate receipt-bound checkpoint ${event.checkpointId}.`);
|
|
1636
|
+
}
|
|
1637
|
+
const current = assignments.get(event.workItemId) ?? null;
|
|
1638
|
+
if (current?.assignmentId !== event.assignmentId) {
|
|
1639
|
+
throw new Error("Receipt-bound checkpoint must bind the current assignment.");
|
|
1640
|
+
}
|
|
1641
|
+
seenReceiptBindings.add(event.checkpointId);
|
|
1642
|
+
const checkpoint = {
|
|
1643
|
+
...event,
|
|
1644
|
+
type: "checkpoint_recorded",
|
|
1645
|
+
evidenceRefs: uniqueSorted([
|
|
1646
|
+
...event.evidenceRefs,
|
|
1647
|
+
event.receiptBinding.receiptRef,
|
|
1648
|
+
]),
|
|
1649
|
+
ref,
|
|
1650
|
+
};
|
|
1651
|
+
checkpoints.set(event.workItemId, checkpoint);
|
|
1652
|
+
receiptBindings.set(event.workItemId, {
|
|
1653
|
+
...structuredClone(event.receiptBinding),
|
|
1654
|
+
checkpointId: event.checkpointId,
|
|
1655
|
+
assignmentId: event.assignmentId,
|
|
1656
|
+
occurredAt: event.occurredAt,
|
|
1657
|
+
truthRef: ref,
|
|
1658
|
+
});
|
|
1350
1659
|
}
|
|
1351
1660
|
}
|
|
1352
1661
|
return {
|
|
@@ -1364,6 +1673,10 @@ function projectEvents(definition, records) {
|
|
|
1364
1673
|
sourceDeliveries,
|
|
1365
1674
|
scopeRevisions,
|
|
1366
1675
|
scopeRevisionHistory,
|
|
1676
|
+
externalGates,
|
|
1677
|
+
deliveryRecords,
|
|
1678
|
+
evidenceRequirements,
|
|
1679
|
+
receiptBindings,
|
|
1367
1680
|
};
|
|
1368
1681
|
}
|
|
1369
1682
|
|
|
@@ -1749,6 +2062,103 @@ function verificationReceiptWarning(workItem) {
|
|
|
1749
2062
|
};
|
|
1750
2063
|
}
|
|
1751
2064
|
|
|
2065
|
+
function externalGateProjection(gate) {
|
|
2066
|
+
return {
|
|
2067
|
+
schemaVersion: "OwlCodaRunKitExternalGateV1",
|
|
2068
|
+
gateId: gate.gateId,
|
|
2069
|
+
title: gate.title,
|
|
2070
|
+
requiredFor: gate.requiredFor,
|
|
2071
|
+
gateStatus: gate.gateStatus,
|
|
2072
|
+
blocking: gate.gateStatus !== "passed",
|
|
2073
|
+
ownerAgentId: gate.ownerAgentId,
|
|
2074
|
+
source: {
|
|
2075
|
+
adapterId: gate.sourceAdapterId,
|
|
2076
|
+
ref: gate.sourceRef,
|
|
2077
|
+
sha256: gate.sourceSha256,
|
|
2078
|
+
},
|
|
2079
|
+
summary: gate.summary,
|
|
2080
|
+
evidenceRefs: [...gate.evidenceRefs],
|
|
2081
|
+
occurredAt: gate.occurredAt,
|
|
2082
|
+
truthRefs: [gate.ref],
|
|
2083
|
+
authorizationGranted: false,
|
|
2084
|
+
};
|
|
2085
|
+
}
|
|
2086
|
+
|
|
2087
|
+
function deliveryLifecycleProjection(overall, state) {
|
|
2088
|
+
const stages = DELIVERY_STAGES.map((stage) => {
|
|
2089
|
+
const record = state.deliveryRecords.get(stage) ?? null;
|
|
2090
|
+
return {
|
|
2091
|
+
stage,
|
|
2092
|
+
status: record?.stageStatus ?? "pending",
|
|
2093
|
+
recordId: record?.recordId ?? null,
|
|
2094
|
+
ownerAgentId: record?.ownerAgentId ?? null,
|
|
2095
|
+
summary: record?.summary ?? null,
|
|
2096
|
+
evidenceRefs: [...(record?.evidenceRefs ?? [])],
|
|
2097
|
+
externalGateIds: [...(record?.externalGateIds ?? [])],
|
|
2098
|
+
occurredAt: record?.occurredAt ?? null,
|
|
2099
|
+
truthRef: record?.ref ?? null,
|
|
2100
|
+
};
|
|
2101
|
+
});
|
|
2102
|
+
let currentStage = overall === "completed"
|
|
2103
|
+
? "work_ledger_complete"
|
|
2104
|
+
: "work_ledger_incomplete";
|
|
2105
|
+
let nextRequiredStage = overall === "completed" ? DELIVERY_STAGES[0] : null;
|
|
2106
|
+
let blockingStage = null;
|
|
2107
|
+
if (overall === "completed") {
|
|
2108
|
+
const projectedNames = {
|
|
2109
|
+
source: "source_accepted",
|
|
2110
|
+
integration: "integrated",
|
|
2111
|
+
materialization: "materialized",
|
|
2112
|
+
deployment: "deployed",
|
|
2113
|
+
live_readback: "live_readback_passed",
|
|
2114
|
+
product_acceptance: "product_accepted",
|
|
2115
|
+
};
|
|
2116
|
+
for (const stage of stages) {
|
|
2117
|
+
if (stage.status === "pending") {
|
|
2118
|
+
nextRequiredStage = stage.stage;
|
|
2119
|
+
break;
|
|
2120
|
+
}
|
|
2121
|
+
if (stage.status !== DELIVERY_POSITIVE_OUTCOME[stage.stage]) {
|
|
2122
|
+
blockingStage = stage.stage;
|
|
2123
|
+
nextRequiredStage = stage.stage;
|
|
2124
|
+
break;
|
|
2125
|
+
}
|
|
2126
|
+
currentStage = projectedNames[stage.stage];
|
|
2127
|
+
nextRequiredStage = stage.stage === DELIVERY_STAGES.at(-1)
|
|
2128
|
+
? null
|
|
2129
|
+
: DELIVERY_STAGES[DELIVERY_STAGES.indexOf(stage.stage) + 1];
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
const unresolvedGates = [...state.externalGates.values()]
|
|
2133
|
+
.filter(gate => gate.gateStatus !== "passed")
|
|
2134
|
+
.sort((left, right) => (
|
|
2135
|
+
DELIVERY_STAGES.indexOf(left.requiredFor) - DELIVERY_STAGES.indexOf(right.requiredFor)
|
|
2136
|
+
|| compareIdentifiers(left.gateId, right.gateId)
|
|
2137
|
+
));
|
|
2138
|
+
const nextStageIndex = nextRequiredStage === null
|
|
2139
|
+
? DELIVERY_STAGES.length
|
|
2140
|
+
: DELIVERY_STAGES.indexOf(nextRequiredStage);
|
|
2141
|
+
const blockingExternalGateIds = unresolvedGates
|
|
2142
|
+
.filter(gate => DELIVERY_STAGES.indexOf(gate.requiredFor) <= nextStageIndex)
|
|
2143
|
+
.map(gate => gate.gateId);
|
|
2144
|
+
const lifecycleStatus = currentStage === "product_accepted"
|
|
2145
|
+
&& blockingExternalGateIds.length === 0
|
|
2146
|
+
? "completed"
|
|
2147
|
+
: blockingStage !== null || blockingExternalGateIds.length > 0
|
|
2148
|
+
? "blocked"
|
|
2149
|
+
: "in_progress";
|
|
2150
|
+
return {
|
|
2151
|
+
schemaVersion: "OwlCodaRunKitDeliveryLifecycleV1",
|
|
2152
|
+
lifecycleStatus,
|
|
2153
|
+
currentStage,
|
|
2154
|
+
nextRequiredStage,
|
|
2155
|
+
blockingStage,
|
|
2156
|
+
blockingExternalGateIds,
|
|
2157
|
+
stages,
|
|
2158
|
+
authorizationGranted: false,
|
|
2159
|
+
};
|
|
2160
|
+
}
|
|
2161
|
+
|
|
1752
2162
|
function projectStatus(definition, records, workspaceRoot = null) {
|
|
1753
2163
|
const projectDefinition = definition;
|
|
1754
2164
|
const state = projectEvents(definition, records);
|
|
@@ -1761,6 +2171,8 @@ function projectStatus(definition, records, workspaceRoot = null) {
|
|
|
1761
2171
|
const rework = state.reworks.get(item.id) ?? null;
|
|
1762
2172
|
const sourceDeliveries = state.sourceDeliveries.filter(row => row.workItemId === item.id);
|
|
1763
2173
|
const scopeRevisionHistory = state.scopeRevisionHistory.get(item.id) ?? [];
|
|
2174
|
+
const evidenceRequirementFact = state.evidenceRequirements.get(item.id) ?? null;
|
|
2175
|
+
const receiptBindingFact = state.receiptBindings.get(item.id) ?? null;
|
|
1764
2176
|
const latestScopeRevision = scopeRevisionHistory.at(-1) ?? null;
|
|
1765
2177
|
const latestSourceDeliveryFact = sourceDeliveries.at(-1) ?? null;
|
|
1766
2178
|
const latestSourceDelivery = sourceDeliveryProjection(sourceDeliveries.at(-1) ?? null);
|
|
@@ -1857,6 +2269,7 @@ function projectStatus(definition, records, workspaceRoot = null) {
|
|
|
1857
2269
|
...(currentTransfer ? [currentTransfer.ref] : []),
|
|
1858
2270
|
...sourceDeliveries.map(row => row.ref),
|
|
1859
2271
|
...scopeRevisionHistory.map(row => row.ref),
|
|
2272
|
+
...(evidenceRequirementFact ? [evidenceRequirementFact.ref] : []),
|
|
1860
2273
|
];
|
|
1861
2274
|
const projectedRework = reworkProjection(
|
|
1862
2275
|
currentRework,
|
|
@@ -1911,6 +2324,30 @@ function projectStatus(definition, records, workspaceRoot = null) {
|
|
|
1911
2324
|
pendingHandoff,
|
|
1912
2325
|
rework: projectedRework,
|
|
1913
2326
|
latestSourceDelivery,
|
|
2327
|
+
evidenceRequirement: evidenceRequirementFact
|
|
2328
|
+
? {
|
|
2329
|
+
policyId: evidenceRequirementFact.policyId,
|
|
2330
|
+
requirement: evidenceRequirementFact.requirement,
|
|
2331
|
+
ownerAgentId: evidenceRequirementFact.ownerAgentId,
|
|
2332
|
+
reason: evidenceRequirementFact.reason,
|
|
2333
|
+
evidenceRefs: [...evidenceRequirementFact.evidenceRefs],
|
|
2334
|
+
occurredAt: evidenceRequirementFact.occurredAt,
|
|
2335
|
+
truthRef: evidenceRequirementFact.ref,
|
|
2336
|
+
}
|
|
2337
|
+
: {
|
|
2338
|
+
policyId: null,
|
|
2339
|
+
requirement: "none",
|
|
2340
|
+
ownerAgentId: null,
|
|
2341
|
+
reason: null,
|
|
2342
|
+
evidenceRefs: [],
|
|
2343
|
+
occurredAt: null,
|
|
2344
|
+
truthRef: null,
|
|
2345
|
+
},
|
|
2346
|
+
boundVerificationReceipt: receiptBindingFact !== null
|
|
2347
|
+
&& receiptBindingFact.assignmentId === assignment?.assignmentId
|
|
2348
|
+
&& effectiveCheckpoint?.checkpointId === receiptBindingFact.checkpointId
|
|
2349
|
+
? structuredClone(receiptBindingFact)
|
|
2350
|
+
: null,
|
|
1914
2351
|
lastCheckpointAt: checkpoint?.occurredAt ?? null,
|
|
1915
2352
|
evidenceRefs: projectedFact?.evidenceRefs ?? [],
|
|
1916
2353
|
assignmentHistory: history,
|
|
@@ -2126,6 +2563,10 @@ function projectStatus(definition, records, workspaceRoot = null) {
|
|
|
2126
2563
|
&& openDecisions.length === 0) overall = "completed";
|
|
2127
2564
|
else if (openDecisions.length > 0 || workItems.some(row => row.status === "failed")) overall = "active_with_blockers";
|
|
2128
2565
|
else if (workItems.some(row => row.status !== "planned" || row.pendingHandoff !== null)) overall = "active";
|
|
2566
|
+
const externalGates = [...state.externalGates.values()]
|
|
2567
|
+
.map(externalGateProjection)
|
|
2568
|
+
.sort((left, right) => compareIdentifiers(left.gateId, right.gateId));
|
|
2569
|
+
const deliveryLifecycle = deliveryLifecycleProjection(overall, state);
|
|
2129
2570
|
const status = {
|
|
2130
2571
|
schemaVersion: STATUS_SCHEMA_V3,
|
|
2131
2572
|
status: "team_project_status",
|
|
@@ -2147,6 +2588,8 @@ function projectStatus(definition, records, workspaceRoot = null) {
|
|
|
2147
2588
|
dataCandidates,
|
|
2148
2589
|
sourceDeliveries,
|
|
2149
2590
|
warnings,
|
|
2591
|
+
externalGates,
|
|
2592
|
+
deliveryLifecycle,
|
|
2150
2593
|
projectTruthHash: sha256(canonical({
|
|
2151
2594
|
definition: projectDefinition,
|
|
2152
2595
|
events: records.map(row => row.event),
|
|
@@ -2156,10 +2599,55 @@ function projectStatus(definition, records, workspaceRoot = null) {
|
|
|
2156
2599
|
return { ...status, ...deriveProjectDriverSummary(status) };
|
|
2157
2600
|
}
|
|
2158
2601
|
|
|
2159
|
-
function validateEventAgainstState(
|
|
2602
|
+
function validateEventAgainstState(
|
|
2603
|
+
event,
|
|
2604
|
+
definition,
|
|
2605
|
+
records,
|
|
2606
|
+
workspaceRoot,
|
|
2607
|
+
{ allowBoundReceipt = false } = {},
|
|
2608
|
+
) {
|
|
2160
2609
|
const normalized = validateEventShape(event, definition);
|
|
2161
2610
|
const state = projectEvents(definition, records);
|
|
2162
2611
|
const status = projectStatus(definition, records, workspaceRoot);
|
|
2612
|
+
if (normalized.schemaVersion === EVENT_SCHEMA_V6) {
|
|
2613
|
+
projectEvents(definition, [
|
|
2614
|
+
...records,
|
|
2615
|
+
{ event: normalized, ref: `project/events/${normalized.eventId}.json` },
|
|
2616
|
+
]);
|
|
2617
|
+
return normalized;
|
|
2618
|
+
}
|
|
2619
|
+
if (normalized.type === "work_item_evidence_requirement_set") {
|
|
2620
|
+
projectEvents(definition, [
|
|
2621
|
+
...records,
|
|
2622
|
+
{ event: normalized, ref: `project/events/${normalized.eventId}.json` },
|
|
2623
|
+
]);
|
|
2624
|
+
return normalized;
|
|
2625
|
+
}
|
|
2626
|
+
if (normalized.type === "checkpoint_receipt_bound") {
|
|
2627
|
+
const {
|
|
2628
|
+
receiptBinding: _receiptBinding,
|
|
2629
|
+
authorizationGranted: _authorizationGranted,
|
|
2630
|
+
checkpointId: _checkpointId,
|
|
2631
|
+
...checkpointFields
|
|
2632
|
+
} = normalized;
|
|
2633
|
+
const checkpointEvent = {
|
|
2634
|
+
...checkpointFields,
|
|
2635
|
+
schemaVersion: EVENT_SCHEMA,
|
|
2636
|
+
type: "checkpoint_recorded",
|
|
2637
|
+
};
|
|
2638
|
+
validateEventAgainstState(
|
|
2639
|
+
checkpointEvent,
|
|
2640
|
+
definition,
|
|
2641
|
+
records,
|
|
2642
|
+
workspaceRoot,
|
|
2643
|
+
{ allowBoundReceipt: true },
|
|
2644
|
+
);
|
|
2645
|
+
projectEvents(definition, [
|
|
2646
|
+
...records,
|
|
2647
|
+
{ event: normalized, ref: `project/events/${normalized.eventId}.json` },
|
|
2648
|
+
]);
|
|
2649
|
+
return normalized;
|
|
2650
|
+
}
|
|
2163
2651
|
const effectiveDefinition = definitionWithScopeRevisions(definition, state);
|
|
2164
2652
|
const workItem = effectiveDefinition.workItems.find(row => row.id === normalized.workItemId);
|
|
2165
2653
|
if (normalized.type === "agent_assigned") {
|
|
@@ -2218,6 +2706,15 @@ function validateEventAgainstState(event, definition, records, workspaceRoot) {
|
|
|
2218
2706
|
if (normalized.state === "completed" && unresolved.length > 0) {
|
|
2219
2707
|
throw new Error(`Completed checkpoint has unresolved dependencies: ${unresolved.join(", ")}.`);
|
|
2220
2708
|
}
|
|
2709
|
+
const evidenceRequirement = state.evidenceRequirements.get(normalized.workItemId) ?? null;
|
|
2710
|
+
if (!allowBoundReceipt
|
|
2711
|
+
&& ["verifying", "completed"].includes(normalized.state)
|
|
2712
|
+
&& evidenceRequirement?.requirement === "quick") {
|
|
2713
|
+
throw new Error(
|
|
2714
|
+
`WorkItem ${normalized.workItemId} requires a bound Quick receipt. `
|
|
2715
|
+
+ "Use project checkpoint --from-quick-receipt <receipt.json>.",
|
|
2716
|
+
);
|
|
2717
|
+
}
|
|
2221
2718
|
if (normalized.state === "verifying") {
|
|
2222
2719
|
const history = state.assignmentHistory.get(normalized.workItemId) ?? [];
|
|
2223
2720
|
const prior = history.at(-2) ?? null;
|
|
@@ -3262,6 +3759,340 @@ export function integrateTeamProjectV1({
|
|
|
3262
3759
|
};
|
|
3263
3760
|
}
|
|
3264
3761
|
|
|
3762
|
+
export function reconcileExternalGateV1({
|
|
3763
|
+
workspaceRoot,
|
|
3764
|
+
reconciliationId,
|
|
3765
|
+
occurredAt,
|
|
3766
|
+
gateId,
|
|
3767
|
+
title,
|
|
3768
|
+
requiredFor,
|
|
3769
|
+
gateStatus,
|
|
3770
|
+
ownerAgentId,
|
|
3771
|
+
sourceAdapterId,
|
|
3772
|
+
sourceRef,
|
|
3773
|
+
sourceSha256,
|
|
3774
|
+
summary,
|
|
3775
|
+
evidenceRefs = [],
|
|
3776
|
+
}) {
|
|
3777
|
+
safeIdentifier(reconciliationId, "reconciliationId");
|
|
3778
|
+
const loaded = loadProject(workspaceRoot);
|
|
3779
|
+
const state = projectEvents(loaded.definition, loaded.events);
|
|
3780
|
+
const existing = loaded.events.find(row => (
|
|
3781
|
+
row.event.type === "external_gate_reconciled"
|
|
3782
|
+
&& row.event.reconciliationId === reconciliationId
|
|
3783
|
+
))?.event ?? null;
|
|
3784
|
+
let event;
|
|
3785
|
+
if (existing) {
|
|
3786
|
+
const requested = {
|
|
3787
|
+
occurredAt: normalizeTimestamp(occurredAt),
|
|
3788
|
+
gateId,
|
|
3789
|
+
title,
|
|
3790
|
+
requiredFor,
|
|
3791
|
+
gateStatus,
|
|
3792
|
+
ownerAgentId,
|
|
3793
|
+
sourceAdapterId,
|
|
3794
|
+
sourceRef,
|
|
3795
|
+
sourceSha256,
|
|
3796
|
+
summary,
|
|
3797
|
+
evidenceRefs,
|
|
3798
|
+
};
|
|
3799
|
+
for (const key of Object.keys(requested)) {
|
|
3800
|
+
if (canonical(existing[key]) !== canonical(requested[key])) {
|
|
3801
|
+
throw new Error(`Cannot resume external Gate reconciliation ${reconciliationId}; ${key} differs.`);
|
|
3802
|
+
}
|
|
3803
|
+
}
|
|
3804
|
+
event = existing;
|
|
3805
|
+
} else {
|
|
3806
|
+
const previous = state.externalGates.get(gateId) ?? null;
|
|
3807
|
+
event = {
|
|
3808
|
+
schemaVersion: EVENT_SCHEMA_V6,
|
|
3809
|
+
eventId: `external-gate-${reconciliationId}`,
|
|
3810
|
+
type: "external_gate_reconciled",
|
|
3811
|
+
occurredAt,
|
|
3812
|
+
reconciliationId,
|
|
3813
|
+
...(previous ? { supersedesReconciliationId: previous.reconciliationId } : {}),
|
|
3814
|
+
gateId,
|
|
3815
|
+
title,
|
|
3816
|
+
requiredFor,
|
|
3817
|
+
gateStatus,
|
|
3818
|
+
ownerAgentId,
|
|
3819
|
+
sourceAdapterId,
|
|
3820
|
+
sourceRef,
|
|
3821
|
+
sourceSha256,
|
|
3822
|
+
summary,
|
|
3823
|
+
evidenceRefs,
|
|
3824
|
+
authorizationGranted: false,
|
|
3825
|
+
};
|
|
3826
|
+
}
|
|
3827
|
+
const recorded = appendTeamProjectEventV1({ workspaceRoot, event });
|
|
3828
|
+
const status = readTeamProjectStatusV4({ workspaceRoot });
|
|
3829
|
+
const gate = status.externalGates.find(row => row.gateId === gateId);
|
|
3830
|
+
if (!gate || gate.gateStatus !== gateStatus) {
|
|
3831
|
+
throw new Error(`External Gate reconciliation ${reconciliationId} is not current.`);
|
|
3832
|
+
}
|
|
3833
|
+
return {
|
|
3834
|
+
status: "team_project_external_gate_reconciled",
|
|
3835
|
+
reconciliationId,
|
|
3836
|
+
supersedesReconciliationId: event.supersedesReconciliationId ?? null,
|
|
3837
|
+
gateId,
|
|
3838
|
+
gateStatus,
|
|
3839
|
+
eventId: recorded.eventId,
|
|
3840
|
+
eventPath: recorded.eventPath,
|
|
3841
|
+
eventSha256: recorded.eventSha256,
|
|
3842
|
+
resumed: recorded.resumed,
|
|
3843
|
+
projectTruthHash: status.projectTruthHash,
|
|
3844
|
+
dominantGap: status.dominantGap,
|
|
3845
|
+
nextAction: status.nextAction,
|
|
3846
|
+
nextActorId: status.nextActorId,
|
|
3847
|
+
authorizationGranted: false,
|
|
3848
|
+
exitCode: 0,
|
|
3849
|
+
};
|
|
3850
|
+
}
|
|
3851
|
+
|
|
3852
|
+
export function recordDeliveryLifecycleV1({
|
|
3853
|
+
workspaceRoot,
|
|
3854
|
+
recordId,
|
|
3855
|
+
occurredAt,
|
|
3856
|
+
stage,
|
|
3857
|
+
stageStatus,
|
|
3858
|
+
ownerAgentId,
|
|
3859
|
+
summary,
|
|
3860
|
+
evidenceRefs = [],
|
|
3861
|
+
externalGateIds = [],
|
|
3862
|
+
}) {
|
|
3863
|
+
safeIdentifier(recordId, "delivery recordId");
|
|
3864
|
+
const loaded = loadProject(workspaceRoot);
|
|
3865
|
+
const state = projectEvents(loaded.definition, loaded.events);
|
|
3866
|
+
const existing = loaded.events.find(row => (
|
|
3867
|
+
row.event.type === "delivery_lifecycle_recorded"
|
|
3868
|
+
&& row.event.recordId === recordId
|
|
3869
|
+
))?.event ?? null;
|
|
3870
|
+
let event;
|
|
3871
|
+
if (existing) {
|
|
3872
|
+
const requested = {
|
|
3873
|
+
occurredAt: normalizeTimestamp(occurredAt),
|
|
3874
|
+
stage,
|
|
3875
|
+
stageStatus,
|
|
3876
|
+
ownerAgentId,
|
|
3877
|
+
summary,
|
|
3878
|
+
evidenceRefs,
|
|
3879
|
+
externalGateIds,
|
|
3880
|
+
};
|
|
3881
|
+
for (const key of Object.keys(requested)) {
|
|
3882
|
+
if (canonical(existing[key]) !== canonical(requested[key])) {
|
|
3883
|
+
throw new Error(`Cannot resume delivery lifecycle record ${recordId}; ${key} differs.`);
|
|
3884
|
+
}
|
|
3885
|
+
}
|
|
3886
|
+
event = existing;
|
|
3887
|
+
} else {
|
|
3888
|
+
const previous = state.deliveryRecords.get(stage) ?? null;
|
|
3889
|
+
event = {
|
|
3890
|
+
schemaVersion: EVENT_SCHEMA_V6,
|
|
3891
|
+
eventId: `delivery-lifecycle-${recordId}`,
|
|
3892
|
+
type: "delivery_lifecycle_recorded",
|
|
3893
|
+
occurredAt,
|
|
3894
|
+
recordId,
|
|
3895
|
+
...(previous ? { supersedesRecordId: previous.recordId } : {}),
|
|
3896
|
+
stage,
|
|
3897
|
+
stageStatus,
|
|
3898
|
+
ownerAgentId,
|
|
3899
|
+
summary,
|
|
3900
|
+
evidenceRefs,
|
|
3901
|
+
externalGateIds,
|
|
3902
|
+
authorizationGranted: false,
|
|
3903
|
+
};
|
|
3904
|
+
}
|
|
3905
|
+
const recorded = appendTeamProjectEventV1({ workspaceRoot, event });
|
|
3906
|
+
const status = readTeamProjectStatusV4({ workspaceRoot });
|
|
3907
|
+
const stageProjection = status.deliveryLifecycle.stages.find(row => row.stage === stage);
|
|
3908
|
+
if (stageProjection?.recordId !== recordId) {
|
|
3909
|
+
throw new Error(`Delivery lifecycle record ${recordId} is not current.`);
|
|
3910
|
+
}
|
|
3911
|
+
return {
|
|
3912
|
+
status: "team_project_delivery_lifecycle_recorded",
|
|
3913
|
+
recordId,
|
|
3914
|
+
supersedesRecordId: event.supersedesRecordId ?? null,
|
|
3915
|
+
stage,
|
|
3916
|
+
stageStatus,
|
|
3917
|
+
eventId: recorded.eventId,
|
|
3918
|
+
eventPath: recorded.eventPath,
|
|
3919
|
+
eventSha256: recorded.eventSha256,
|
|
3920
|
+
resumed: recorded.resumed,
|
|
3921
|
+
projectTruthHash: status.projectTruthHash,
|
|
3922
|
+
deliveryLifecycle: status.deliveryLifecycle,
|
|
3923
|
+
dominantGap: status.dominantGap,
|
|
3924
|
+
nextAction: status.nextAction,
|
|
3925
|
+
nextActorId: status.nextActorId,
|
|
3926
|
+
authorizationGranted: false,
|
|
3927
|
+
exitCode: 0,
|
|
3928
|
+
};
|
|
3929
|
+
}
|
|
3930
|
+
|
|
3931
|
+
export function setWorkItemEvidenceRequirementV1({
|
|
3932
|
+
workspaceRoot,
|
|
3933
|
+
policyId,
|
|
3934
|
+
occurredAt,
|
|
3935
|
+
workItemId,
|
|
3936
|
+
ownerAgentId,
|
|
3937
|
+
requirement,
|
|
3938
|
+
reason,
|
|
3939
|
+
evidenceRefs = [],
|
|
3940
|
+
}) {
|
|
3941
|
+
safeIdentifier(policyId, "evidence policyId");
|
|
3942
|
+
const loaded = loadProject(workspaceRoot);
|
|
3943
|
+
const state = projectEvents(loaded.definition, loaded.events);
|
|
3944
|
+
const existing = loaded.events.find(row => (
|
|
3945
|
+
row.event.type === "work_item_evidence_requirement_set"
|
|
3946
|
+
&& row.event.policyId === policyId
|
|
3947
|
+
))?.event ?? null;
|
|
3948
|
+
let event;
|
|
3949
|
+
if (existing) {
|
|
3950
|
+
const requested = {
|
|
3951
|
+
occurredAt: normalizeTimestamp(occurredAt),
|
|
3952
|
+
workItemId,
|
|
3953
|
+
ownerAgentId,
|
|
3954
|
+
requirement,
|
|
3955
|
+
reason,
|
|
3956
|
+
evidenceRefs,
|
|
3957
|
+
};
|
|
3958
|
+
for (const key of Object.keys(requested)) {
|
|
3959
|
+
if (canonical(existing[key]) !== canonical(requested[key])) {
|
|
3960
|
+
throw new Error(`Cannot resume WorkItem evidence policy ${policyId}; ${key} differs.`);
|
|
3961
|
+
}
|
|
3962
|
+
}
|
|
3963
|
+
event = existing;
|
|
3964
|
+
} else {
|
|
3965
|
+
const previous = state.evidenceRequirements.get(workItemId) ?? null;
|
|
3966
|
+
event = {
|
|
3967
|
+
schemaVersion: EVENT_SCHEMA_V7,
|
|
3968
|
+
eventId: `work-item-evidence-${policyId}`,
|
|
3969
|
+
type: "work_item_evidence_requirement_set",
|
|
3970
|
+
occurredAt,
|
|
3971
|
+
policyId,
|
|
3972
|
+
...(previous ? { supersedesPolicyId: previous.policyId } : {}),
|
|
3973
|
+
workItemId,
|
|
3974
|
+
ownerAgentId,
|
|
3975
|
+
requirement,
|
|
3976
|
+
reason,
|
|
3977
|
+
evidenceRefs,
|
|
3978
|
+
authorizationGranted: false,
|
|
3979
|
+
};
|
|
3980
|
+
}
|
|
3981
|
+
const recorded = appendTeamProjectEventV1({ workspaceRoot, event });
|
|
3982
|
+
const status = readTeamProjectStatusV4({ workspaceRoot });
|
|
3983
|
+
const workItem = status.workItems.find(row => row.workItemId === workItemId);
|
|
3984
|
+
if (workItem?.evidenceRequirement.policyId !== policyId) {
|
|
3985
|
+
throw new Error(`WorkItem evidence policy ${policyId} is not current.`);
|
|
3986
|
+
}
|
|
3987
|
+
return {
|
|
3988
|
+
status: "team_project_work_item_evidence_requirement",
|
|
3989
|
+
policyId,
|
|
3990
|
+
supersedesPolicyId: event.supersedesPolicyId ?? null,
|
|
3991
|
+
workItemId,
|
|
3992
|
+
requirement,
|
|
3993
|
+
eventId: recorded.eventId,
|
|
3994
|
+
eventPath: recorded.eventPath,
|
|
3995
|
+
eventSha256: recorded.eventSha256,
|
|
3996
|
+
resumed: recorded.resumed,
|
|
3997
|
+
projectTruthHash: status.projectTruthHash,
|
|
3998
|
+
authorizationGranted: false,
|
|
3999
|
+
exitCode: 0,
|
|
4000
|
+
};
|
|
4001
|
+
}
|
|
4002
|
+
|
|
4003
|
+
function attestQuickReceiptBinding(workspaceRoot, receiptPath) {
|
|
4004
|
+
const root = realpathSync(workspaceRoot);
|
|
4005
|
+
const { absolutePath, bytes } = readFileBytesBounded(receiptPath);
|
|
4006
|
+
const result = attestCoreBoundQuickReceiptDetailsFromBytes({
|
|
4007
|
+
receiptPath: absolutePath,
|
|
4008
|
+
receiptBytes: bytes,
|
|
4009
|
+
workspaceRoot: root,
|
|
4010
|
+
});
|
|
4011
|
+
if (result.attestation.decision !== "GO") {
|
|
4012
|
+
throw new Error(
|
|
4013
|
+
`Quick receipt attestation must be GO; got ${result.attestation.decision}: `
|
|
4014
|
+
+ `${result.attestation.issueCodes.join(", ") || "no issue code"}.`,
|
|
4015
|
+
);
|
|
4016
|
+
}
|
|
4017
|
+
return {
|
|
4018
|
+
kind: "quick",
|
|
4019
|
+
receiptId: result.attestation.subjectRef.receiptId,
|
|
4020
|
+
receiptRef: relativeToWorkspace(root, absolutePath),
|
|
4021
|
+
receiptSha256: result.attestation.subjectRef.receiptSha256,
|
|
4022
|
+
sourceFingerprint: result.sourceFingerprint,
|
|
4023
|
+
attestationDecision: "GO",
|
|
4024
|
+
issueCodes: [...result.attestation.issueCodes],
|
|
4025
|
+
};
|
|
4026
|
+
}
|
|
4027
|
+
|
|
4028
|
+
export function checkpointTeamProjectFromQuickReceiptV1({
|
|
4029
|
+
workspaceRoot,
|
|
4030
|
+
checkpointId,
|
|
4031
|
+
occurredAt,
|
|
4032
|
+
assignmentId,
|
|
4033
|
+
workItemId,
|
|
4034
|
+
state,
|
|
4035
|
+
summary,
|
|
4036
|
+
completedUnits,
|
|
4037
|
+
evidenceRefs = [],
|
|
4038
|
+
blockerRefs = [],
|
|
4039
|
+
decisionRefs = [],
|
|
4040
|
+
nextAction = null,
|
|
4041
|
+
sourceFingerprint,
|
|
4042
|
+
receiptPath,
|
|
4043
|
+
}) {
|
|
4044
|
+
safeIdentifier(checkpointId, "checkpointId");
|
|
4045
|
+
const receiptBinding = attestQuickReceiptBinding(workspaceRoot, receiptPath);
|
|
4046
|
+
if (sourceFingerprint !== undefined
|
|
4047
|
+
&& sourceFingerprint !== receiptBinding.sourceFingerprint) {
|
|
4048
|
+
throw new Error("Checkpoint sourceFingerprint differs from the Quick receipt.");
|
|
4049
|
+
}
|
|
4050
|
+
const event = {
|
|
4051
|
+
schemaVersion: EVENT_SCHEMA_V7,
|
|
4052
|
+
eventId: `checkpoint-receipt-${checkpointId}`,
|
|
4053
|
+
type: "checkpoint_receipt_bound",
|
|
4054
|
+
occurredAt,
|
|
4055
|
+
checkpointId,
|
|
4056
|
+
assignmentId,
|
|
4057
|
+
workItemId,
|
|
4058
|
+
state,
|
|
4059
|
+
summary,
|
|
4060
|
+
...(completedUnits === undefined ? {} : { completedUnits }),
|
|
4061
|
+
evidenceRefs: uniqueSorted([...evidenceRefs, receiptBinding.receiptRef]),
|
|
4062
|
+
blockerRefs,
|
|
4063
|
+
decisionRefs,
|
|
4064
|
+
nextAction,
|
|
4065
|
+
sourceFingerprint: receiptBinding.sourceFingerprint,
|
|
4066
|
+
receiptBinding,
|
|
4067
|
+
authorizationGranted: false,
|
|
4068
|
+
};
|
|
4069
|
+
const recorded = appendTeamProjectEventV1({ workspaceRoot, event });
|
|
4070
|
+
const status = readTeamProjectStatusV4({ workspaceRoot });
|
|
4071
|
+
const workItem = status.workItems.find(row => row.workItemId === workItemId);
|
|
4072
|
+
if (workItem?.boundVerificationReceipt?.checkpointId !== checkpointId) {
|
|
4073
|
+
throw new Error(`Receipt-bound checkpoint ${checkpointId} is not current.`);
|
|
4074
|
+
}
|
|
4075
|
+
return {
|
|
4076
|
+
status: "team_project_checkpoint",
|
|
4077
|
+
checkpointId,
|
|
4078
|
+
assignmentId,
|
|
4079
|
+
workItemId,
|
|
4080
|
+
state,
|
|
4081
|
+
receiptBinding,
|
|
4082
|
+
eventId: recorded.eventId,
|
|
4083
|
+
eventPath: recorded.eventPath,
|
|
4084
|
+
eventSha256: recorded.eventSha256,
|
|
4085
|
+
resumed: recorded.resumed,
|
|
4086
|
+
projectTruthHash: status.projectTruthHash,
|
|
4087
|
+
overall: status.overall,
|
|
4088
|
+
warnings: status.warnings.filter(row => row.workItemId === workItemId),
|
|
4089
|
+
nextAction: status.nextAction,
|
|
4090
|
+
nextActorId: status.nextActorId,
|
|
4091
|
+
authorizationGranted: false,
|
|
4092
|
+
exitCode: 0,
|
|
4093
|
+
};
|
|
4094
|
+
}
|
|
4095
|
+
|
|
3265
4096
|
export function checkpointTeamProjectV1({
|
|
3266
4097
|
workspaceRoot,
|
|
3267
4098
|
checkpointId,
|
|
@@ -3423,6 +4254,8 @@ function legacyWorkItemProjection(workItem) {
|
|
|
3423
4254
|
ownedPaths: _ownedPaths,
|
|
3424
4255
|
measurable: _measurable,
|
|
3425
4256
|
scopeRevision: _scopeRevision,
|
|
4257
|
+
evidenceRequirement: _evidenceRequirement,
|
|
4258
|
+
boundVerificationReceipt: _boundVerificationReceipt,
|
|
3426
4259
|
...legacy
|
|
3427
4260
|
} = workItem;
|
|
3428
4261
|
return legacy;
|
|
@@ -3434,11 +4267,23 @@ function statusV2WorkItemProjection(workItem) {
|
|
|
3434
4267
|
ownedPaths: _ownedPaths,
|
|
3435
4268
|
measurable: _measurable,
|
|
3436
4269
|
scopeRevision: _scopeRevision,
|
|
4270
|
+
evidenceRequirement: _evidenceRequirement,
|
|
4271
|
+
boundVerificationReceipt: _boundVerificationReceipt,
|
|
3437
4272
|
...v2
|
|
3438
4273
|
} = workItem;
|
|
3439
4274
|
return v2;
|
|
3440
4275
|
}
|
|
3441
4276
|
|
|
4277
|
+
function statusV3WorkItemProjection(workItem) {
|
|
4278
|
+
if (workItem === null) return null;
|
|
4279
|
+
const {
|
|
4280
|
+
evidenceRequirement: _evidenceRequirement,
|
|
4281
|
+
boundVerificationReceipt: _boundVerificationReceipt,
|
|
4282
|
+
...v3
|
|
4283
|
+
} = workItem;
|
|
4284
|
+
return v3;
|
|
4285
|
+
}
|
|
4286
|
+
|
|
3442
4287
|
function statusV3CompletionProjection(status) {
|
|
3443
4288
|
if (status.overall !== "completed") {
|
|
3444
4289
|
return { ...status, deliveryDisposition: "work_in_progress" };
|
|
@@ -3466,11 +4311,106 @@ function statusV3CompletionProjection(status) {
|
|
|
3466
4311
|
};
|
|
3467
4312
|
}
|
|
3468
4313
|
|
|
4314
|
+
function statusV4DeliveryProjection(status) {
|
|
4315
|
+
if (status.overall !== "completed") {
|
|
4316
|
+
return {
|
|
4317
|
+
...status,
|
|
4318
|
+
schemaVersion: STATUS_SCHEMA_V4,
|
|
4319
|
+
deliveryDisposition: status.deliveryLifecycle.currentStage,
|
|
4320
|
+
};
|
|
4321
|
+
}
|
|
4322
|
+
const dueGate = status.externalGates
|
|
4323
|
+
.filter(gate => gate.blocking)
|
|
4324
|
+
.sort((left, right) => (
|
|
4325
|
+
DELIVERY_STAGES.indexOf(left.requiredFor) - DELIVERY_STAGES.indexOf(right.requiredFor)
|
|
4326
|
+
|| compareIdentifiers(left.gateId, right.gateId)
|
|
4327
|
+
))
|
|
4328
|
+
.find(gate => status.deliveryLifecycle.blockingExternalGateIds.includes(gate.gateId))
|
|
4329
|
+
?? null;
|
|
4330
|
+
if (dueGate) {
|
|
4331
|
+
const dominantGap = {
|
|
4332
|
+
kind: "external_gate",
|
|
4333
|
+
id: dueGate.gateId,
|
|
4334
|
+
title: dueGate.title,
|
|
4335
|
+
workItemId: null,
|
|
4336
|
+
agentId: dueGate.ownerAgentId,
|
|
4337
|
+
reason: dueGate.summary,
|
|
4338
|
+
truthRefs: uniqueSorted(dueGate.truthRefs),
|
|
4339
|
+
};
|
|
4340
|
+
const nextAction = `Reconcile external Gate ${dueGate.gateId}.`;
|
|
4341
|
+
return {
|
|
4342
|
+
...status,
|
|
4343
|
+
schemaVersion: STATUS_SCHEMA_V4,
|
|
4344
|
+
deliveryDisposition: status.deliveryLifecycle.currentStage,
|
|
4345
|
+
headline: `Dominant gap: external Gate ${dueGate.gateId} (${dueGate.title}); `
|
|
4346
|
+
+ `owner: ${dueGate.ownerAgentId}; blocker: ${singleLine(dueGate.summary)}; `
|
|
4347
|
+
+ `next: ${nextAction}`,
|
|
4348
|
+
dominantGap,
|
|
4349
|
+
nextAction,
|
|
4350
|
+
nextActorId: dueGate.ownerAgentId,
|
|
4351
|
+
};
|
|
4352
|
+
}
|
|
4353
|
+
if (status.deliveryLifecycle.nextRequiredStage !== null) {
|
|
4354
|
+
const stage = status.deliveryLifecycle.nextRequiredStage;
|
|
4355
|
+
const stageProjection = status.deliveryLifecycle.stages.find(row => row.stage === stage);
|
|
4356
|
+
const dominantGap = {
|
|
4357
|
+
kind: "delivery_stage",
|
|
4358
|
+
id: stage,
|
|
4359
|
+
title: `Delivery stage ${stage}`,
|
|
4360
|
+
workItemId: null,
|
|
4361
|
+
agentId: stageProjection?.ownerAgentId ?? null,
|
|
4362
|
+
reason: stageProjection?.status === "pending"
|
|
4363
|
+
? `Delivery stage ${stage} has no evidence-bound record.`
|
|
4364
|
+
: stageProjection?.summary ?? `Delivery stage ${stage} is not accepted.`,
|
|
4365
|
+
truthRefs: uniqueSorted([
|
|
4366
|
+
"project/definition.json",
|
|
4367
|
+
...(stageProjection?.truthRef ? [stageProjection.truthRef] : []),
|
|
4368
|
+
]),
|
|
4369
|
+
};
|
|
4370
|
+
const nextAction = `Record delivery stage ${stage}.`;
|
|
4371
|
+
return {
|
|
4372
|
+
...status,
|
|
4373
|
+
schemaVersion: STATUS_SCHEMA_V4,
|
|
4374
|
+
deliveryDisposition: status.deliveryLifecycle.currentStage,
|
|
4375
|
+
headline: `Dominant gap: delivery stage ${stage}; owner: `
|
|
4376
|
+
+ `${dominantGap.agentId ?? "unassigned"}; blocker: ${singleLine(dominantGap.reason)}; `
|
|
4377
|
+
+ `next: ${nextAction}`,
|
|
4378
|
+
dominantGap,
|
|
4379
|
+
nextAction,
|
|
4380
|
+
nextActorId: dominantGap.agentId,
|
|
4381
|
+
};
|
|
4382
|
+
}
|
|
4383
|
+
return {
|
|
4384
|
+
...status,
|
|
4385
|
+
schemaVersion: STATUS_SCHEMA_V4,
|
|
4386
|
+
deliveryDisposition: status.deliveryLifecycle.currentStage,
|
|
4387
|
+
headline: "Delivery lifecycle complete through product acceptance; authorizationGranted=false.",
|
|
4388
|
+
dominantGap: {
|
|
4389
|
+
kind: "none",
|
|
4390
|
+
id: null,
|
|
4391
|
+
title: null,
|
|
4392
|
+
workItemId: null,
|
|
4393
|
+
agentId: null,
|
|
4394
|
+
reason: null,
|
|
4395
|
+
truthRefs: uniqueSorted([
|
|
4396
|
+
"project/definition.json",
|
|
4397
|
+
...status.deliveryLifecycle.stages
|
|
4398
|
+
.map(stage => stage.truthRef)
|
|
4399
|
+
.filter(Boolean),
|
|
4400
|
+
]),
|
|
4401
|
+
},
|
|
4402
|
+
nextAction: null,
|
|
4403
|
+
nextActorId: null,
|
|
4404
|
+
};
|
|
4405
|
+
}
|
|
4406
|
+
|
|
3469
4407
|
export function readTeamProjectStatusV1({ workspaceRoot }) {
|
|
3470
4408
|
const loaded = loadProject(workspaceRoot);
|
|
3471
4409
|
const {
|
|
3472
4410
|
sourceDeliveries: _sourceDeliveries,
|
|
3473
4411
|
warnings: _warnings,
|
|
4412
|
+
externalGates: _externalGates,
|
|
4413
|
+
deliveryLifecycle: _deliveryLifecycle,
|
|
3474
4414
|
...legacy
|
|
3475
4415
|
} = projectStatus(
|
|
3476
4416
|
loaded.definition,
|
|
@@ -3487,7 +4427,12 @@ export function readTeamProjectStatusV1({ workspaceRoot }) {
|
|
|
3487
4427
|
|
|
3488
4428
|
export function readTeamProjectStatusV2({ workspaceRoot }) {
|
|
3489
4429
|
const loaded = loadProject(workspaceRoot);
|
|
3490
|
-
const {
|
|
4430
|
+
const {
|
|
4431
|
+
warnings: _warnings,
|
|
4432
|
+
externalGates: _externalGates,
|
|
4433
|
+
deliveryLifecycle: _deliveryLifecycle,
|
|
4434
|
+
...v2
|
|
4435
|
+
} = projectStatus(
|
|
3491
4436
|
loaded.definition,
|
|
3492
4437
|
loaded.events,
|
|
3493
4438
|
loaded.root,
|
|
@@ -3501,9 +4446,27 @@ export function readTeamProjectStatusV2({ workspaceRoot }) {
|
|
|
3501
4446
|
}
|
|
3502
4447
|
|
|
3503
4448
|
export function readTeamProjectStatusV3({ workspaceRoot }) {
|
|
4449
|
+
const loaded = loadProject(workspaceRoot);
|
|
4450
|
+
const {
|
|
4451
|
+
externalGates: _externalGates,
|
|
4452
|
+
deliveryLifecycle: _deliveryLifecycle,
|
|
4453
|
+
...v3
|
|
4454
|
+
} = projectStatus(
|
|
4455
|
+
loaded.definition,
|
|
4456
|
+
loaded.events,
|
|
4457
|
+
loaded.root,
|
|
4458
|
+
);
|
|
4459
|
+
return {
|
|
4460
|
+
...statusV3CompletionProjection(v3),
|
|
4461
|
+
workItems: v3.workItems.map(statusV3WorkItemProjection),
|
|
4462
|
+
exitCode: 0,
|
|
4463
|
+
};
|
|
4464
|
+
}
|
|
4465
|
+
|
|
4466
|
+
export function readTeamProjectStatusV4({ workspaceRoot }) {
|
|
3504
4467
|
const loaded = loadProject(workspaceRoot);
|
|
3505
4468
|
return {
|
|
3506
|
-
...
|
|
4469
|
+
...statusV4DeliveryProjection(projectStatus(
|
|
3507
4470
|
loaded.definition,
|
|
3508
4471
|
loaded.events,
|
|
3509
4472
|
loaded.root,
|
|
@@ -3783,9 +4746,7 @@ export function formatTeamProjectStatusHumanV2(status) {
|
|
|
3783
4746
|
return [
|
|
3784
4747
|
status.headline,
|
|
3785
4748
|
`Project: ${status.projectId} — ${singleLine(status.objective)}`,
|
|
3786
|
-
`Work ledger: ${status.overall}${status.deliveryDisposition
|
|
3787
|
-
? `; delivery: ${status.deliveryDisposition}`
|
|
3788
|
-
: ""}`,
|
|
4749
|
+
`Work ledger: ${status.overall}; Delivery: ${status.deliveryDisposition}`,
|
|
3789
4750
|
`Work: ${status.counts.completed}/${status.counts.total} completed; ${status.counts.active} active; ${status.counts.waiting_dependency + status.counts.waiting_decision} waiting`,
|
|
3790
4751
|
`Agents: ${status.agents.map(agent => agent.agentId).join(", ") || "none assigned"}`,
|
|
3791
4752
|
`Open decisions: ${status.openDecisions.map(row => row.decisionId).join(", ") || "none"}`,
|
|
@@ -3801,7 +4762,7 @@ export function formatTeamProjectStatusHumanV2(status) {
|
|
|
3801
4762
|
...(status.warnings?.length > 0
|
|
3802
4763
|
? [`Warnings: ${status.warnings.map(row => `${row.code}:${row.workItemId}`).join(", ")}`]
|
|
3803
4764
|
: []),
|
|
3804
|
-
`Next: ${nextAction}${status.nextActorId ? ` (owner ${status.nextActorId})` : ""}
|
|
4765
|
+
`Next: ${nextAction}${status.nextActorId ? ` (owner ${status.nextActorId})` : ""}; Authority: not granted for Git, release, deployment, production, or business actions.`,
|
|
3805
4766
|
"",
|
|
3806
4767
|
].join("\n");
|
|
3807
4768
|
}
|