owlrunkit 0.18.1 → 0.18.3
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 +40 -0
- package/README.md +85 -19
- package/docs/architecture/OWLCODA_RUNKIT_018_PROJECT_DRIVER_PRODUCT_DEFINITION.md +16 -5
- package/docs/architecture/OWLCODA_RUNKIT_USAGE_MODE_SELECTION_PRODUCT_DECISION_20260811.md +78 -4
- package/docs/architecture/OWLCODA_RUN_KIT_CONTRACT_V0_2.md +7 -0
- package/integrations/codex/skills/owlcoda-runkit/SKILL.md +28 -9
- package/integrations/codex/skills/owlcoda-runkit/assets/templates/config.json +3 -3
- package/integrations/codex/skills/owlcoda-runkit/references/contract-v0.1.md +160 -0
- package/integrations/codex/skills/owlcoda-runkit/references/contract-v0.2.md +545 -0
- package/package.json +1 -1
- package/packages/attest/README.md +4 -3
- package/packages/attest/consumer-surface-v1.json +1 -0
- package/packages/attest/distribution-manifest-v1.json +12 -1
- package/packages/attest/schemas/quick-verification-receipt-v2.schema.json +232 -0
- package/packages/attest/src/index.mjs +4 -1
- package/packages/attest/src/quick-receipt-contract.mjs +13 -3
- package/packages/attest/src/quick.mjs +21 -2
- package/registry-release-policy-v1.json +5 -5
- package/sbom.spdx.json +5 -5
- package/scripts/runkit-contract/assurance-router.mjs +3 -1
- package/scripts/runkit-contract/core-contract.mjs +2 -1
- package/scripts/runkit-contract/onboarding-doctor.mjs +46 -18
- package/scripts/runkit-contract/profile-onboarding.mjs +4 -0
- package/scripts/runkit-contract/project-cli-resolver.mjs +3 -0
- package/scripts/runkit-contract/quick-metrics.mjs +3 -3
- package/scripts/runkit-contract/quick-receipt.mjs +110 -5
- package/scripts/runkit-contract/quick-verify.mjs +93 -11
- package/scripts/runkit-contract/runkit-cli.mjs +172 -24
- package/scripts/runkit-contract/schemas/profile-reconcile-plan-v1.schema.json +2 -0
- package/scripts/runkit-contract/schemas/team-project-event-v2.schema.json +128 -0
- package/scripts/runkit-contract/team-project.mjs +310 -45
- package/scripts/runkit-contract/usage-mode.mjs +11 -13
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"detectionSha256": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/hash" }] },
|
|
17
17
|
"currentProfilesSha256": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/hash" }] },
|
|
18
18
|
"proposedProfilesSha256": { "$ref": "#/$defs/hash" },
|
|
19
|
+
"normalizationOnly": { "type": "boolean" },
|
|
19
20
|
"addedProfileIds": { "$ref": "#/$defs/strings" },
|
|
20
21
|
"updatedProfiles": { "type": "array", "items": { "$ref": "#/$defs/update" } },
|
|
21
22
|
"proposedProfiles": { "$ref": "#/$defs/profiles" },
|
|
@@ -81,6 +82,7 @@
|
|
|
81
82
|
"detectionSha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
|
82
83
|
"currentProfilesSha256": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
|
|
83
84
|
"proposedProfilesSha256": "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
|
|
85
|
+
"normalizationOnly": false,
|
|
84
86
|
"addedProfileIds": [],
|
|
85
87
|
"updatedProfiles": [{
|
|
86
88
|
"profileId": "node-quality",
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://owlcoda.com/schemas/runkit/team-project-event-v2.schema.json",
|
|
4
|
+
"title": "OwlCoda RunKit Team Project Event V2",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": ["schemaVersion", "eventId", "type", "occurredAt"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"schemaVersion": { "const": "OwlCodaRunKitTeamProjectEventV2" },
|
|
10
|
+
"eventId": { "$ref": "#/$defs/id" },
|
|
11
|
+
"type": { "enum": ["agent_assigned", "checkpoint_recorded", "handoff_recorded", "rework_returned", "decision_opened", "decision_resolved", "integration_gate_passed", "verification_deferred", "verification_closed", "data_candidate_recorded"] },
|
|
12
|
+
"occurredAt": { "type": "string", "minLength": 24 },
|
|
13
|
+
"assignmentId": { "$ref": "#/$defs/id" },
|
|
14
|
+
"supersedesAssignmentId": { "$ref": "#/$defs/id" },
|
|
15
|
+
"failedAssignmentId": { "$ref": "#/$defs/id" },
|
|
16
|
+
"failedCheckpointEventId": { "$ref": "#/$defs/id" },
|
|
17
|
+
"reworkId": { "$ref": "#/$defs/id" },
|
|
18
|
+
"workItemId": { "$ref": "#/$defs/id" },
|
|
19
|
+
"agentId": { "$ref": "#/$defs/id" },
|
|
20
|
+
"executionRunId": { "$ref": "#/$defs/id" },
|
|
21
|
+
"executionWorkItemId": { "$ref": "#/$defs/id" },
|
|
22
|
+
"state": { "enum": ["active", "waiting_dependency", "waiting_decision", "verifying", "ready_to_integrate", "completed", "failed"] },
|
|
23
|
+
"summary": { "type": "string", "minLength": 1 },
|
|
24
|
+
"completedUnits": { "type": "integer", "minimum": 0 },
|
|
25
|
+
"evidenceRefs": { "$ref": "#/$defs/strings" },
|
|
26
|
+
"blockerRefs": { "$ref": "#/$defs/strings" },
|
|
27
|
+
"decisionRefs": { "$ref": "#/$defs/strings" },
|
|
28
|
+
"nextAction": { "oneOf": [{ "type": "string", "minLength": 1 }, { "type": "null" }] },
|
|
29
|
+
"sourceFingerprint": { "type": "string", "pattern": "^(sha256:)?[a-f0-9]{64}$" },
|
|
30
|
+
"fromAgentId": { "$ref": "#/$defs/id" },
|
|
31
|
+
"reviewerAgentId": { "$ref": "#/$defs/id" },
|
|
32
|
+
"toAgentId": { "$ref": "#/$defs/id" },
|
|
33
|
+
"reason": { "type": "string", "minLength": 1 },
|
|
34
|
+
"decisionId": { "$ref": "#/$defs/id" },
|
|
35
|
+
"title": { "type": "string", "minLength": 1 },
|
|
36
|
+
"question": { "type": "string", "minLength": 1 },
|
|
37
|
+
"ownerAgentId": { "$ref": "#/$defs/id" },
|
|
38
|
+
"blockingWorkItemIds": { "$ref": "#/$defs/strings" },
|
|
39
|
+
"options": { "$ref": "#/$defs/strings" },
|
|
40
|
+
"resolution": { "type": "string", "minLength": 1 },
|
|
41
|
+
"rationale": { "type": "string", "minLength": 1 },
|
|
42
|
+
"gateId": { "$ref": "#/$defs/id" },
|
|
43
|
+
"verificationId": { "$ref": "#/$defs/id" },
|
|
44
|
+
"checkIds": { "$ref": "#/$defs/ids" },
|
|
45
|
+
"dueGateId": { "$ref": "#/$defs/id" },
|
|
46
|
+
"disposition": { "enum": ["verified", "no_longer_required"] },
|
|
47
|
+
"decisionIds": { "$ref": "#/$defs/ids" },
|
|
48
|
+
"candidateId": { "$ref": "#/$defs/id" },
|
|
49
|
+
"sourceRef": { "type": "string", "minLength": 1 },
|
|
50
|
+
"rights": { "type": "string", "minLength": 1 },
|
|
51
|
+
"inputRef": { "type": "string", "minLength": 1 },
|
|
52
|
+
"outputRef": { "type": "string", "minLength": 1 },
|
|
53
|
+
"decisionRef": { "oneOf": [{ "type": "string", "minLength": 1 }, { "type": "null" }] },
|
|
54
|
+
"verificationRefs": { "$ref": "#/$defs/strings" },
|
|
55
|
+
"outcomeRef": { "oneOf": [{ "type": "string", "minLength": 1 }, { "type": "null" }] },
|
|
56
|
+
"version": { "type": "string", "minLength": 1 }
|
|
57
|
+
},
|
|
58
|
+
"allOf": [
|
|
59
|
+
{
|
|
60
|
+
"if": { "properties": { "type": { "const": "agent_assigned" } }, "required": ["type"] },
|
|
61
|
+
"then": {
|
|
62
|
+
"required": ["assignmentId", "workItemId", "agentId"],
|
|
63
|
+
"dependentRequired": {
|
|
64
|
+
"executionRunId": ["executionWorkItemId"],
|
|
65
|
+
"executionWorkItemId": ["executionRunId"]
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"if": { "properties": { "type": { "const": "checkpoint_recorded" } }, "required": ["type"] },
|
|
71
|
+
"then": { "required": ["assignmentId", "workItemId", "state", "summary", "evidenceRefs", "blockerRefs", "decisionRefs", "nextAction"] }
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"if": { "properties": { "type": { "const": "handoff_recorded" } }, "required": ["type"] },
|
|
75
|
+
"then": { "required": ["assignmentId", "workItemId", "fromAgentId", "toAgentId", "summary", "evidenceRefs", "nextAction"] }
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"if": { "properties": { "type": { "const": "rework_returned" } }, "required": ["type"] },
|
|
79
|
+
"then": { "required": ["reworkId", "assignmentId", "failedAssignmentId", "failedCheckpointEventId", "workItemId", "fromAgentId", "reviewerAgentId", "toAgentId", "reason", "evidenceRefs", "nextAction"] }
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"if": { "properties": { "type": { "const": "decision_opened" } }, "required": ["type"] },
|
|
83
|
+
"then": { "required": ["decisionId", "title", "question", "ownerAgentId", "blockingWorkItemIds", "options"] }
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"if": { "properties": { "type": { "const": "decision_resolved" } }, "required": ["type"] },
|
|
87
|
+
"then": { "required": ["decisionId", "resolution", "rationale", "evidenceRefs"] }
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"if": { "properties": { "type": { "const": "integration_gate_passed" } }, "required": ["type"] },
|
|
91
|
+
"then": { "required": ["gateId", "summary", "evidenceRefs"] }
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"if": { "properties": { "type": { "const": "verification_deferred" } }, "required": ["type"] },
|
|
95
|
+
"then": { "required": ["verificationId", "workItemId", "ownerAgentId", "checkIds", "reason", "dueGateId"] }
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"if": { "properties": { "type": { "const": "verification_closed" } }, "required": ["type"] },
|
|
99
|
+
"then": { "required": ["verificationId", "disposition", "summary", "evidenceRefs", "decisionIds"] }
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"if": { "properties": { "type": { "const": "data_candidate_recorded" } }, "required": ["type"] },
|
|
103
|
+
"then": { "required": ["candidateId", "sourceRef", "rights", "inputRef", "outputRef", "decisionRef", "verificationRefs", "outcomeRef", "version"] }
|
|
104
|
+
}
|
|
105
|
+
],
|
|
106
|
+
"$defs": {
|
|
107
|
+
"id": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*$" },
|
|
108
|
+
"ids": { "type": "array", "uniqueItems": true, "items": { "$ref": "#/$defs/id" } },
|
|
109
|
+
"strings": { "type": "array", "uniqueItems": true, "items": { "type": "string", "minLength": 1 } }
|
|
110
|
+
},
|
|
111
|
+
"examples": [{
|
|
112
|
+
"schemaVersion": "OwlCodaRunKitTeamProjectEventV2",
|
|
113
|
+
"eventId": "rework-returned-review-001",
|
|
114
|
+
"type": "rework_returned",
|
|
115
|
+
"occurredAt": "2026-08-12T00:00:00.000Z",
|
|
116
|
+
"reworkId": "review-001",
|
|
117
|
+
"assignmentId": "rework-review-001",
|
|
118
|
+
"failedAssignmentId": "assignment-001",
|
|
119
|
+
"failedCheckpointEventId": "checkpoint-failed-001",
|
|
120
|
+
"workItemId": "engine",
|
|
121
|
+
"fromAgentId": "agent-kimi",
|
|
122
|
+
"reviewerAgentId": "agent-control",
|
|
123
|
+
"toAgentId": "agent-kimi",
|
|
124
|
+
"reason": "The candidate violates its versioned contract.",
|
|
125
|
+
"evidenceRefs": ["receipt:quick-failed"],
|
|
126
|
+
"nextAction": "Repair the contract and rerun the focused check."
|
|
127
|
+
}]
|
|
128
|
+
}
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
const PROJECT_ROOT = ".owlcoda/runkit/project";
|
|
24
24
|
const DEFINITION_SCHEMA = "OwlCodaRunKitTeamProjectDefinitionV1";
|
|
25
25
|
const EVENT_SCHEMA = "OwlCodaRunKitTeamProjectEventV1";
|
|
26
|
+
const EVENT_SCHEMA_V2 = "OwlCodaRunKitTeamProjectEventV2";
|
|
26
27
|
const STATUS_SCHEMA = "OwlCodaRunKitTeamProjectStatusV1";
|
|
27
28
|
|
|
28
29
|
const DEFINITION_KEYS = [
|
|
@@ -60,6 +61,12 @@ const EVENT_KEYS = Object.freeze({
|
|
|
60
61
|
"workItemId", "fromAgentId", "toAgentId", "summary", "evidenceRefs",
|
|
61
62
|
"nextAction",
|
|
62
63
|
],
|
|
64
|
+
rework_returned: [
|
|
65
|
+
"schemaVersion", "eventId", "type", "occurredAt", "reworkId",
|
|
66
|
+
"assignmentId", "failedAssignmentId", "failedCheckpointEventId",
|
|
67
|
+
"workItemId", "fromAgentId", "reviewerAgentId", "toAgentId", "reason",
|
|
68
|
+
"evidenceRefs", "nextAction",
|
|
69
|
+
],
|
|
63
70
|
decision_opened: [
|
|
64
71
|
"schemaVersion", "eventId", "type", "occurredAt", "decisionId",
|
|
65
72
|
"title", "question", "ownerAgentId", "blockingWorkItemIds", "options",
|
|
@@ -391,6 +398,28 @@ function pendingHandoffProjection(handoff) {
|
|
|
391
398
|
: null;
|
|
392
399
|
}
|
|
393
400
|
|
|
401
|
+
function reworkProjection(rework, attempt) {
|
|
402
|
+
return rework
|
|
403
|
+
? {
|
|
404
|
+
eventId: rework.eventId,
|
|
405
|
+
truthRef: rework.ref,
|
|
406
|
+
reworkId: rework.reworkId,
|
|
407
|
+
attempt,
|
|
408
|
+
failedCheckpointEventId: rework.failedCheckpointEventId,
|
|
409
|
+
failedTruthRef: `project/events/${rework.failedCheckpointEventId}.json`,
|
|
410
|
+
previousAssignmentId: rework.failedAssignmentId,
|
|
411
|
+
previousAgentId: rework.fromAgentId,
|
|
412
|
+
reviewerAgentId: rework.reviewerAgentId,
|
|
413
|
+
assignmentId: rework.assignmentId,
|
|
414
|
+
agentId: rework.toAgentId,
|
|
415
|
+
reason: rework.reason,
|
|
416
|
+
evidenceRefs: [...rework.evidenceRefs],
|
|
417
|
+
nextAction: rework.nextAction,
|
|
418
|
+
occurredAt: rework.occurredAt,
|
|
419
|
+
}
|
|
420
|
+
: null;
|
|
421
|
+
}
|
|
422
|
+
|
|
394
423
|
function validateTimestamp(value) {
|
|
395
424
|
if (typeof value !== "string"
|
|
396
425
|
|| !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/u.test(value)
|
|
@@ -405,8 +434,13 @@ function validateEventShape(value, definition) {
|
|
|
405
434
|
throw new Error("Team project event type is invalid.");
|
|
406
435
|
}
|
|
407
436
|
assertAllowedKeys(value, `Team project event ${value.eventId ?? "unknown"}`, EVENT_KEYS[value.type]);
|
|
408
|
-
if (value.schemaVersion
|
|
409
|
-
throw new Error(
|
|
437
|
+
if (![EVENT_SCHEMA, EVENT_SCHEMA_V2].includes(value.schemaVersion)) {
|
|
438
|
+
throw new Error(
|
|
439
|
+
`Team project event schemaVersion must be ${EVENT_SCHEMA} or ${EVENT_SCHEMA_V2}.`,
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
if (value.type === "rework_returned" && value.schemaVersion !== EVENT_SCHEMA_V2) {
|
|
443
|
+
throw new Error(`rework_returned requires schemaVersion ${EVENT_SCHEMA_V2}.`);
|
|
410
444
|
}
|
|
411
445
|
safeIdentifier(value.eventId, "eventId");
|
|
412
446
|
validateTimestamp(value.occurredAt);
|
|
@@ -420,11 +454,17 @@ function validateEventShape(value, definition) {
|
|
|
420
454
|
"agent_assigned",
|
|
421
455
|
"checkpoint_recorded",
|
|
422
456
|
"handoff_recorded",
|
|
457
|
+
"rework_returned",
|
|
423
458
|
"verification_deferred",
|
|
424
459
|
].includes(value.type)) {
|
|
425
460
|
assertWorkItem(value.workItemId);
|
|
426
461
|
}
|
|
427
|
-
if ([
|
|
462
|
+
if ([
|
|
463
|
+
"agent_assigned",
|
|
464
|
+
"checkpoint_recorded",
|
|
465
|
+
"handoff_recorded",
|
|
466
|
+
"rework_returned",
|
|
467
|
+
].includes(value.type)) {
|
|
428
468
|
safeIdentifier(value.assignmentId, "assignmentId");
|
|
429
469
|
}
|
|
430
470
|
if (value.type === "agent_assigned") {
|
|
@@ -466,6 +506,16 @@ function validateEventShape(value, definition) {
|
|
|
466
506
|
nonEmptyString(value.summary, "Handoff summary");
|
|
467
507
|
stringArray(value.evidenceRefs, "Handoff evidenceRefs", { allowEmpty: false });
|
|
468
508
|
nonEmptyString(value.nextAction, "Handoff nextAction");
|
|
509
|
+
} else if (value.type === "rework_returned") {
|
|
510
|
+
safeIdentifier(value.reworkId, "reworkId");
|
|
511
|
+
safeIdentifier(value.failedAssignmentId, "failedAssignmentId");
|
|
512
|
+
safeIdentifier(value.failedCheckpointEventId, "failedCheckpointEventId");
|
|
513
|
+
safeIdentifier(value.fromAgentId, "fromAgentId");
|
|
514
|
+
safeIdentifier(value.reviewerAgentId, "reviewerAgentId");
|
|
515
|
+
safeIdentifier(value.toAgentId, "toAgentId");
|
|
516
|
+
nonEmptyString(value.reason, "Rework reason");
|
|
517
|
+
stringArray(value.evidenceRefs, "Rework evidenceRefs", { allowEmpty: false });
|
|
518
|
+
nonEmptyString(value.nextAction, "Rework nextAction");
|
|
469
519
|
} else if (value.type === "decision_opened") {
|
|
470
520
|
safeIdentifier(value.decisionId, "decisionId");
|
|
471
521
|
safeIdentifier(value.ownerAgentId, "ownerAgentId");
|
|
@@ -541,11 +591,13 @@ function projectEvents(definition, records) {
|
|
|
541
591
|
const assignmentHistory = new Map(definition.workItems.map(row => [row.id, []]));
|
|
542
592
|
const checkpoints = new Map();
|
|
543
593
|
const handoffs = new Map();
|
|
594
|
+
const reworks = new Map();
|
|
544
595
|
const decisions = new Map();
|
|
545
596
|
const gatePasses = new Map();
|
|
546
597
|
const verifications = new Map();
|
|
547
598
|
const dataCandidates = [];
|
|
548
599
|
const seenAssignments = new Set();
|
|
600
|
+
const seenReworks = new Set();
|
|
549
601
|
const seenDecisions = new Set();
|
|
550
602
|
|
|
551
603
|
for (const { event, ref } of records) {
|
|
@@ -561,6 +613,38 @@ function projectEvents(definition, records) {
|
|
|
561
613
|
checkpoints.set(event.workItemId, { ...event, ref });
|
|
562
614
|
} else if (event.type === "handoff_recorded") {
|
|
563
615
|
handoffs.set(event.workItemId, { ...event, ref });
|
|
616
|
+
} else if (event.type === "rework_returned") {
|
|
617
|
+
if (seenReworks.has(event.reworkId)) {
|
|
618
|
+
throw new Error(`Duplicate reworkId ${event.reworkId}.`);
|
|
619
|
+
}
|
|
620
|
+
if (seenAssignments.has(event.assignmentId)) {
|
|
621
|
+
throw new Error(`Duplicate assignmentId ${event.assignmentId}.`);
|
|
622
|
+
}
|
|
623
|
+
const current = assignments.get(event.workItemId) ?? null;
|
|
624
|
+
const failure = checkpoints.get(event.workItemId) ?? null;
|
|
625
|
+
const latestFact = latestEventFact(failure, handoffs.get(event.workItemId) ?? null);
|
|
626
|
+
if (!current
|
|
627
|
+
|| current.assignmentId !== event.failedAssignmentId
|
|
628
|
+
|| current.agentId !== event.fromAgentId
|
|
629
|
+
|| failure?.eventId !== event.failedCheckpointEventId
|
|
630
|
+
|| failure.assignmentId !== event.failedAssignmentId
|
|
631
|
+
|| failure.state !== "failed"
|
|
632
|
+
|| latestFact?.eventId !== failure.eventId) {
|
|
633
|
+
throw new Error(
|
|
634
|
+
`Rework ${event.reworkId} does not bind the current failed checkpoint and assignment.`,
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
seenReworks.add(event.reworkId);
|
|
638
|
+
seenAssignments.add(event.assignmentId);
|
|
639
|
+
const assignment = {
|
|
640
|
+
...event,
|
|
641
|
+
agentId: event.toAgentId,
|
|
642
|
+
supersedesAssignmentId: event.failedAssignmentId,
|
|
643
|
+
ref,
|
|
644
|
+
};
|
|
645
|
+
assignments.set(event.workItemId, assignment);
|
|
646
|
+
assignmentHistory.get(event.workItemId).push(assignment);
|
|
647
|
+
reworks.set(event.workItemId, { ...event, ref });
|
|
564
648
|
} else if (event.type === "decision_opened") {
|
|
565
649
|
if (seenDecisions.has(event.decisionId)) {
|
|
566
650
|
throw new Error(`Duplicate decision_opened event for ${event.decisionId}.`);
|
|
@@ -615,6 +699,7 @@ function projectEvents(definition, records) {
|
|
|
615
699
|
assignmentHistory,
|
|
616
700
|
checkpoints,
|
|
617
701
|
handoffs,
|
|
702
|
+
reworks,
|
|
618
703
|
decisions,
|
|
619
704
|
gatePasses,
|
|
620
705
|
verifications,
|
|
@@ -945,7 +1030,9 @@ function projectStatus(definition, records, workspaceRoot = null) {
|
|
|
945
1030
|
const assignment = state.assignments.get(item.id) ?? null;
|
|
946
1031
|
const checkpoint = state.checkpoints.get(item.id) ?? null;
|
|
947
1032
|
const handoff = state.handoffs.get(item.id) ?? null;
|
|
948
|
-
const
|
|
1033
|
+
const rework = state.reworks.get(item.id) ?? null;
|
|
1034
|
+
const currentRework = rework?.assignmentId === assignment?.assignmentId ? rework : null;
|
|
1035
|
+
const latestFact = latestEventFact(latestEventFact(checkpoint, handoff), currentRework);
|
|
949
1036
|
const activeHandoff = handoff
|
|
950
1037
|
&& handoff.assignmentId === assignment?.assignmentId
|
|
951
1038
|
&& handoff.fromAgentId === assignment?.agentId
|
|
@@ -973,7 +1060,8 @@ function projectStatus(definition, records, workspaceRoot = null) {
|
|
|
973
1060
|
total: item.measurable.total,
|
|
974
1061
|
}
|
|
975
1062
|
: { kind: "state_only" };
|
|
976
|
-
const
|
|
1063
|
+
const assignmentEvents = state.assignmentHistory.get(item.id);
|
|
1064
|
+
const history = assignmentEvents.map(row => ({
|
|
977
1065
|
assignmentId: row.assignmentId,
|
|
978
1066
|
agentId: row.agentId,
|
|
979
1067
|
occurredAt: row.occurredAt,
|
|
@@ -986,7 +1074,17 @@ function projectStatus(definition, records, workspaceRoot = null) {
|
|
|
986
1074
|
...history.map(row => row.truthRef),
|
|
987
1075
|
...(checkpoint ? [checkpoint.ref] : []),
|
|
988
1076
|
...(handoff ? [handoff.ref] : []),
|
|
1077
|
+
...(rework ? [rework.ref] : []),
|
|
989
1078
|
];
|
|
1079
|
+
const projectedRework = reworkProjection(
|
|
1080
|
+
currentRework,
|
|
1081
|
+
currentRework
|
|
1082
|
+
? assignmentEvents.findIndex(row => row.assignmentId === currentRework.assignmentId) + 1
|
|
1083
|
+
: null,
|
|
1084
|
+
);
|
|
1085
|
+
const latestSummary = latestFact?.type === "rework_returned"
|
|
1086
|
+
? latestFact.reason
|
|
1087
|
+
: latestFact?.summary ?? null;
|
|
990
1088
|
return {
|
|
991
1089
|
workItemId: item.id,
|
|
992
1090
|
title: item.title,
|
|
@@ -1005,9 +1103,10 @@ function projectStatus(definition, records, workspaceRoot = null) {
|
|
|
1005
1103
|
: unresolvedDependencies.map(id => `work-item:${id}`),
|
|
1006
1104
|
decisionRefs: latestFact?.type === "checkpoint_recorded" ? checkpoint.decisionRefs : [],
|
|
1007
1105
|
progress: measured,
|
|
1008
|
-
summary:
|
|
1106
|
+
summary: latestSummary,
|
|
1009
1107
|
nextAction: latestFact?.nextAction ?? null,
|
|
1010
1108
|
pendingHandoff,
|
|
1109
|
+
rework: projectedRework,
|
|
1011
1110
|
lastCheckpointAt: checkpoint?.occurredAt ?? null,
|
|
1012
1111
|
evidenceRefs: latestFact?.evidenceRefs ?? [],
|
|
1013
1112
|
assignmentHistory: history,
|
|
@@ -1339,6 +1438,42 @@ function validateEventAgainstState(event, definition, records, workspaceRoot) {
|
|
|
1339
1438
|
|| current.agentId !== normalized.fromAgentId) {
|
|
1340
1439
|
throw new Error("Handoff must bind the current assignment and Agent.");
|
|
1341
1440
|
}
|
|
1441
|
+
} else if (normalized.type === "rework_returned") {
|
|
1442
|
+
const current = state.assignments.get(normalized.workItemId) ?? null;
|
|
1443
|
+
const failure = state.checkpoints.get(normalized.workItemId) ?? null;
|
|
1444
|
+
const projected = status.workItems.find(row => row.workItemId === normalized.workItemId);
|
|
1445
|
+
const latestFact = latestEventFact(
|
|
1446
|
+
failure,
|
|
1447
|
+
state.handoffs.get(normalized.workItemId) ?? null,
|
|
1448
|
+
);
|
|
1449
|
+
if (projected?.status !== "failed"
|
|
1450
|
+
|| !current
|
|
1451
|
+
|| current.assignmentId !== normalized.failedAssignmentId
|
|
1452
|
+
|| current.agentId !== normalized.fromAgentId
|
|
1453
|
+
|| failure?.eventId !== normalized.failedCheckpointEventId
|
|
1454
|
+
|| failure.assignmentId !== normalized.failedAssignmentId
|
|
1455
|
+
|| failure.state !== "failed"
|
|
1456
|
+
|| latestFact?.eventId !== failure.eventId) {
|
|
1457
|
+
throw new Error(
|
|
1458
|
+
"reject-and-return requires one current failed checkpoint bound to the current assignment.",
|
|
1459
|
+
);
|
|
1460
|
+
}
|
|
1461
|
+
if (state.assignmentHistory.get(normalized.workItemId).some(
|
|
1462
|
+
row => row.assignmentId === normalized.assignmentId,
|
|
1463
|
+
)) {
|
|
1464
|
+
throw new Error(`Assignment ${normalized.assignmentId} already exists.`);
|
|
1465
|
+
}
|
|
1466
|
+
for (const otherProjection of status.workItems) {
|
|
1467
|
+
if (otherProjection.workItemId === normalized.workItemId
|
|
1468
|
+
|| otherProjection.assignmentId === null
|
|
1469
|
+
|| ["completed", "failed"].includes(otherProjection.status)) continue;
|
|
1470
|
+
const other = definition.workItems.find(row => row.id === otherProjection.workItemId);
|
|
1471
|
+
if (pathsOverlap(workItem.ownedPaths, other.ownedPaths)) {
|
|
1472
|
+
throw new Error(
|
|
1473
|
+
`Owned paths overlap active assignment ${otherProjection.assignmentId}: ${normalized.workItemId} <> ${otherProjection.workItemId}.`,
|
|
1474
|
+
);
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1342
1477
|
} else if (normalized.type === "decision_opened") {
|
|
1343
1478
|
if (state.decisions.has(normalized.decisionId)) throw new Error(`Decision ${normalized.decisionId} already exists.`);
|
|
1344
1479
|
} else if (normalized.type === "decision_resolved") {
|
|
@@ -1416,52 +1551,55 @@ export function initializeTeamProjectV1({ workspaceRoot, definition }) {
|
|
|
1416
1551
|
};
|
|
1417
1552
|
}
|
|
1418
1553
|
|
|
1419
|
-
|
|
1420
|
-
const
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
const
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
const existing = readRegularJson(eventPath, `Team project event ${shaped.eventId}`);
|
|
1427
|
-
if (canonical(existing) !== canonical(shaped)) {
|
|
1428
|
-
throw new Error(`Team project immutable event differs: ${shaped.eventId}.`);
|
|
1429
|
-
}
|
|
1430
|
-
return {
|
|
1431
|
-
status: "team_project_event_recorded",
|
|
1432
|
-
exitCode: 0,
|
|
1433
|
-
eventId: shaped.eventId,
|
|
1434
|
-
eventPath: relativeToWorkspace(refreshed.root, eventPath),
|
|
1435
|
-
eventSha256: sha256(canonical(shaped)),
|
|
1436
|
-
resumed: true,
|
|
1437
|
-
authorizationGranted: false,
|
|
1438
|
-
};
|
|
1439
|
-
}
|
|
1440
|
-
const latest = refreshed.events.at(-1)?.event ?? null;
|
|
1441
|
-
if (latest && (shaped.occurredAt < latest.occurredAt
|
|
1442
|
-
|| (shaped.occurredAt === latest.occurredAt
|
|
1443
|
-
&& shaped.eventId.localeCompare(latest.eventId) <= 0))) {
|
|
1444
|
-
throw new Error(
|
|
1445
|
-
`Team project event must sort after the latest event ${latest.eventId}.`,
|
|
1446
|
-
);
|
|
1554
|
+
function appendTeamProjectEventWithinLock(refreshed, event) {
|
|
1555
|
+
const shaped = validateEventShape(event, refreshed.definition);
|
|
1556
|
+
const eventPath = path.join(refreshed.projectRoot, "events", `${shaped.eventId}.json`);
|
|
1557
|
+
if (existsSync(eventPath)) {
|
|
1558
|
+
const existing = readRegularJson(eventPath, `Team project event ${shaped.eventId}`);
|
|
1559
|
+
if (canonical(existing) !== canonical(shaped)) {
|
|
1560
|
+
throw new Error(`Team project immutable event differs: ${shaped.eventId}.`);
|
|
1447
1561
|
}
|
|
1448
|
-
const normalized = validateEventAgainstState(
|
|
1449
|
-
shaped,
|
|
1450
|
-
refreshed.definition,
|
|
1451
|
-
refreshed.events,
|
|
1452
|
-
refreshed.root,
|
|
1453
|
-
);
|
|
1454
|
-
writeJsonExclusiveAtomically(eventPath, normalized);
|
|
1455
1562
|
return {
|
|
1456
1563
|
status: "team_project_event_recorded",
|
|
1457
1564
|
exitCode: 0,
|
|
1458
|
-
eventId:
|
|
1565
|
+
eventId: shaped.eventId,
|
|
1459
1566
|
eventPath: relativeToWorkspace(refreshed.root, eventPath),
|
|
1460
|
-
eventSha256: sha256(canonical(
|
|
1461
|
-
resumed:
|
|
1567
|
+
eventSha256: sha256(canonical(shaped)),
|
|
1568
|
+
resumed: true,
|
|
1462
1569
|
authorizationGranted: false,
|
|
1463
1570
|
};
|
|
1464
|
-
}
|
|
1571
|
+
}
|
|
1572
|
+
const latest = refreshed.events.at(-1)?.event ?? null;
|
|
1573
|
+
if (latest && (shaped.occurredAt < latest.occurredAt
|
|
1574
|
+
|| (shaped.occurredAt === latest.occurredAt
|
|
1575
|
+
&& shaped.eventId.localeCompare(latest.eventId) <= 0))) {
|
|
1576
|
+
throw new Error(
|
|
1577
|
+
`Team project event must sort after the latest event ${latest.eventId}.`,
|
|
1578
|
+
);
|
|
1579
|
+
}
|
|
1580
|
+
const normalized = validateEventAgainstState(
|
|
1581
|
+
shaped,
|
|
1582
|
+
refreshed.definition,
|
|
1583
|
+
refreshed.events,
|
|
1584
|
+
refreshed.root,
|
|
1585
|
+
);
|
|
1586
|
+
writeJsonExclusiveAtomically(eventPath, normalized);
|
|
1587
|
+
return {
|
|
1588
|
+
status: "team_project_event_recorded",
|
|
1589
|
+
exitCode: 0,
|
|
1590
|
+
eventId: normalized.eventId,
|
|
1591
|
+
eventPath: relativeToWorkspace(refreshed.root, eventPath),
|
|
1592
|
+
eventSha256: sha256(canonical(normalized)),
|
|
1593
|
+
resumed: false,
|
|
1594
|
+
authorizationGranted: false,
|
|
1595
|
+
};
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
export function appendTeamProjectEventV1({ workspaceRoot, event }) {
|
|
1599
|
+
const loaded = loadProject(workspaceRoot);
|
|
1600
|
+
return withProjectLock(loaded.projectRoot, () => (
|
|
1601
|
+
appendTeamProjectEventWithinLock(loadProject(workspaceRoot), event)
|
|
1602
|
+
));
|
|
1465
1603
|
}
|
|
1466
1604
|
|
|
1467
1605
|
export function assignTeamProjectV1({
|
|
@@ -1584,6 +1722,133 @@ export function handoffTeamProjectV1({
|
|
|
1584
1722
|
};
|
|
1585
1723
|
}
|
|
1586
1724
|
|
|
1725
|
+
export function rejectAndReturnTeamProjectV1({
|
|
1726
|
+
workspaceRoot,
|
|
1727
|
+
reworkId,
|
|
1728
|
+
occurredAt,
|
|
1729
|
+
workItemId,
|
|
1730
|
+
reviewerAgentId,
|
|
1731
|
+
toAgentId,
|
|
1732
|
+
reason,
|
|
1733
|
+
nextAction,
|
|
1734
|
+
evidenceRefs,
|
|
1735
|
+
}) {
|
|
1736
|
+
safeIdentifier(reworkId, "reworkId");
|
|
1737
|
+
const eventId = `rework-returned-${reworkId}`;
|
|
1738
|
+
const assignmentId = `rework-${reworkId}`;
|
|
1739
|
+
const loaded = loadProject(workspaceRoot);
|
|
1740
|
+
return withProjectLock(loaded.projectRoot, () => {
|
|
1741
|
+
const refreshed = loadProject(workspaceRoot);
|
|
1742
|
+
const existing = refreshed.events.find(row => row.event.eventId === eventId)?.event ?? null;
|
|
1743
|
+
let event;
|
|
1744
|
+
if (existing) {
|
|
1745
|
+
event = {
|
|
1746
|
+
schemaVersion: EVENT_SCHEMA_V2,
|
|
1747
|
+
eventId,
|
|
1748
|
+
type: "rework_returned",
|
|
1749
|
+
occurredAt,
|
|
1750
|
+
reworkId,
|
|
1751
|
+
assignmentId,
|
|
1752
|
+
failedAssignmentId: existing.failedAssignmentId,
|
|
1753
|
+
failedCheckpointEventId: existing.failedCheckpointEventId,
|
|
1754
|
+
workItemId,
|
|
1755
|
+
fromAgentId: existing.fromAgentId,
|
|
1756
|
+
reviewerAgentId,
|
|
1757
|
+
toAgentId,
|
|
1758
|
+
reason,
|
|
1759
|
+
evidenceRefs,
|
|
1760
|
+
nextAction,
|
|
1761
|
+
};
|
|
1762
|
+
} else {
|
|
1763
|
+
const state = projectEvents(refreshed.definition, refreshed.events);
|
|
1764
|
+
const status = projectStatus(refreshed.definition, refreshed.events, refreshed.root);
|
|
1765
|
+
const current = state.assignments.get(workItemId) ?? null;
|
|
1766
|
+
const failure = state.checkpoints.get(workItemId) ?? null;
|
|
1767
|
+
const projected = status.workItems.find(row => row.workItemId === workItemId) ?? null;
|
|
1768
|
+
const latestFact = latestEventFact(failure, state.handoffs.get(workItemId) ?? null);
|
|
1769
|
+
if (projected?.status !== "failed"
|
|
1770
|
+
|| !current
|
|
1771
|
+
|| failure?.state !== "failed"
|
|
1772
|
+
|| failure.assignmentId !== current.assignmentId
|
|
1773
|
+
|| latestFact?.eventId !== failure.eventId) {
|
|
1774
|
+
throw new Error(
|
|
1775
|
+
"reject-and-return requires one current failed checkpoint bound to the current assignment.",
|
|
1776
|
+
);
|
|
1777
|
+
}
|
|
1778
|
+
event = {
|
|
1779
|
+
schemaVersion: EVENT_SCHEMA_V2,
|
|
1780
|
+
eventId,
|
|
1781
|
+
type: "rework_returned",
|
|
1782
|
+
occurredAt,
|
|
1783
|
+
reworkId,
|
|
1784
|
+
assignmentId,
|
|
1785
|
+
failedAssignmentId: current.assignmentId,
|
|
1786
|
+
failedCheckpointEventId: failure.eventId,
|
|
1787
|
+
workItemId,
|
|
1788
|
+
fromAgentId: current.agentId,
|
|
1789
|
+
reviewerAgentId,
|
|
1790
|
+
toAgentId,
|
|
1791
|
+
reason,
|
|
1792
|
+
evidenceRefs,
|
|
1793
|
+
nextAction,
|
|
1794
|
+
};
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
const recorded = appendTeamProjectEventWithinLock(refreshed, event);
|
|
1798
|
+
const projectedEvents = recorded.resumed
|
|
1799
|
+
? refreshed.events
|
|
1800
|
+
: [
|
|
1801
|
+
...refreshed.events,
|
|
1802
|
+
{ event, ref: `project/events/${event.eventId}.json` },
|
|
1803
|
+
];
|
|
1804
|
+
const status = projectStatus(refreshed.definition, projectedEvents, refreshed.root);
|
|
1805
|
+
const projectedState = projectEvents(refreshed.definition, projectedEvents);
|
|
1806
|
+
const current = status.workItems.find(row => row.workItemId === workItemId) ?? null;
|
|
1807
|
+
const latestFact = latestEventFact(
|
|
1808
|
+
latestEventFact(
|
|
1809
|
+
projectedState.checkpoints.get(workItemId) ?? null,
|
|
1810
|
+
projectedState.handoffs.get(workItemId) ?? null,
|
|
1811
|
+
),
|
|
1812
|
+
projectedState.reworks.get(workItemId) ?? null,
|
|
1813
|
+
);
|
|
1814
|
+
if (current?.assignmentId !== assignmentId
|
|
1815
|
+
|| current?.rework?.eventId !== eventId
|
|
1816
|
+
|| latestFact?.eventId !== eventId) {
|
|
1817
|
+
throw new Error(
|
|
1818
|
+
`Cannot resume historical/superseded rework ${reworkId}; `
|
|
1819
|
+
+ `current assignment=${current?.assignmentId ?? "none"}, `
|
|
1820
|
+
+ `current owner=${current?.agentId ?? "none"}.`,
|
|
1821
|
+
);
|
|
1822
|
+
}
|
|
1823
|
+
return {
|
|
1824
|
+
status: "team_project_rework",
|
|
1825
|
+
reworkId,
|
|
1826
|
+
assignmentId,
|
|
1827
|
+
failedAssignmentId: event.failedAssignmentId,
|
|
1828
|
+
failedCheckpointEventId: event.failedCheckpointEventId,
|
|
1829
|
+
workItemId,
|
|
1830
|
+
fromAgentId: event.fromAgentId,
|
|
1831
|
+
reviewerAgentId,
|
|
1832
|
+
toAgentId,
|
|
1833
|
+
reason,
|
|
1834
|
+
nextAction: current.nextAction,
|
|
1835
|
+
eventId: recorded.eventId,
|
|
1836
|
+
eventPath: recorded.eventPath,
|
|
1837
|
+
eventSha256: recorded.eventSha256,
|
|
1838
|
+
resumed: recorded.resumed,
|
|
1839
|
+
reworkAttempt: current.rework.attempt,
|
|
1840
|
+
projectTruthHash: status.projectTruthHash,
|
|
1841
|
+
overall: status.overall,
|
|
1842
|
+
projectNextAction: status.nextAction,
|
|
1843
|
+
projectNextActorId: status.nextActorId,
|
|
1844
|
+
gitWritePerformed: false,
|
|
1845
|
+
releaseWritePerformed: false,
|
|
1846
|
+
authorizationGranted: false,
|
|
1847
|
+
exitCode: 0,
|
|
1848
|
+
};
|
|
1849
|
+
});
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1587
1852
|
export function openTeamProjectDecisionV1({
|
|
1588
1853
|
workspaceRoot,
|
|
1589
1854
|
decisionId,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { RUNKIT_RISK_CATEGORIES } from "./assurance-router.mjs";
|
|
2
|
+
|
|
1
3
|
const USER_MODES = new Set(["auto", "off", "light", "managed", "formal"]);
|
|
2
4
|
|
|
3
5
|
const BOOLEAN_FACTS = [
|
|
@@ -24,17 +26,7 @@ const MATERIAL_RISK_CATEGORIES = new Map([
|
|
|
24
26
|
["migration", "schema_or_data_migration"],
|
|
25
27
|
]);
|
|
26
28
|
|
|
27
|
-
const KNOWN_RISK_CATEGORIES = new Set(
|
|
28
|
-
"backtest",
|
|
29
|
-
"deployment",
|
|
30
|
-
"destructive",
|
|
31
|
-
"foreign_write",
|
|
32
|
-
"funds",
|
|
33
|
-
"integration",
|
|
34
|
-
"migration",
|
|
35
|
-
"production",
|
|
36
|
-
"release",
|
|
37
|
-
]);
|
|
29
|
+
const KNOWN_RISK_CATEGORIES = new Set(RUNKIT_RISK_CATEGORIES);
|
|
38
30
|
|
|
39
31
|
const AUTHORITY_BOUNDARY = Object.freeze({
|
|
40
32
|
authorizationGranted: false,
|
|
@@ -65,8 +57,14 @@ function requireFacts(input) {
|
|
|
65
57
|
|| !input.riskCategories.every((value) => typeof value === "string")) {
|
|
66
58
|
throw new Error("riskCategories must be an array of strings");
|
|
67
59
|
}
|
|
68
|
-
|
|
69
|
-
|
|
60
|
+
const unknownCategories = uniqueSorted(
|
|
61
|
+
input.riskCategories.filter((value) => !KNOWN_RISK_CATEGORIES.has(value)),
|
|
62
|
+
);
|
|
63
|
+
if (unknownCategories.length > 0) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
`riskCategories contains an unknown category: ${unknownCategories.join(", ")}. `
|
|
66
|
+
+ `Allowed: ${RUNKIT_RISK_CATEGORIES.join(", ")}`,
|
|
67
|
+
);
|
|
70
68
|
}
|
|
71
69
|
}
|
|
72
70
|
|