codex-workflow-v2 2.0.0-beta.14 → 2.0.0-beta.14.1
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/README.md +3 -3
- package/dist/reviewer-runtime-build.json +24 -20
- package/dist/src/alpha6/adoption.d.ts +1 -1
- package/dist/src/alpha6/adoption.js +26 -8
- package/dist/src/alpha6/adoption.js.map +1 -1
- package/dist/src/alpha6/corrective-decision-boundary.js +9 -3
- package/dist/src/alpha6/corrective-decision-boundary.js.map +1 -1
- package/dist/src/alpha6/remediation.d.ts +2 -0
- package/dist/src/alpha6/remediation.js +68 -2
- package/dist/src/alpha6/remediation.js.map +1 -1
- package/dist/src/alpha7/autonomy.d.ts +9 -0
- package/dist/src/alpha7/autonomy.js +40 -1
- package/dist/src/alpha7/autonomy.js.map +1 -1
- package/dist/src/beta1/project-transaction.js +14 -4
- package/dist/src/beta1/project-transaction.js.map +1 -1
- package/dist/src/cli-actions.d.ts +3 -3
- package/dist/src/cli-actions.js +5 -0
- package/dist/src/cli-actions.js.map +1 -1
- package/dist/src/cli.js +31 -1
- package/dist/src/cli.js.map +1 -1
- package/dist/src/contracts.d.ts +19 -2
- package/dist/src/delegation.d.ts +5 -1
- package/dist/src/delegation.js +4 -0
- package/dist/src/delegation.js.map +1 -1
- package/dist/src/failed-checkpoint-update.d.ts +69 -0
- package/dist/src/failed-checkpoint-update.js +171 -0
- package/dist/src/failed-checkpoint-update.js.map +1 -0
- package/dist/src/lifecycle/corrective-replan-credential-core.d.ts +2 -0
- package/dist/src/lifecycle/corrective-replan-credential-core.js +45 -9
- package/dist/src/lifecycle/corrective-replan-credential-core.js.map +1 -1
- package/dist/src/lifecycle/corrective-replan.js +12 -8
- package/dist/src/lifecycle/corrective-replan.js.map +1 -1
- package/dist/src/lifecycle/semantic-registry.js +4 -2
- package/dist/src/lifecycle/semantic-registry.js.map +1 -1
- package/dist/src/observed-routes.js +5 -0
- package/dist/src/observed-routes.js.map +1 -1
- package/dist/src/state/corrective-replan-executor.js +1 -0
- package/dist/src/state/corrective-replan-executor.js.map +1 -1
- package/dist/src/state/corrective-replan-public.d.ts +9 -1
- package/dist/src/state/corrective-replan-public.js +49 -7
- package/dist/src/state/corrective-replan-public.js.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/dist/src/version.js.map +1 -1
- package/dist/src/workflow-blocker-route.js +1 -1
- package/dist/src/workflow-blocker-route.js.map +1 -1
- package/dist/src/workflow.d.ts +40 -1
- package/dist/src/workflow.js +203 -33
- package/dist/src/workflow.js.map +1 -1
- package/docs/autonomy-guardrails.md +4 -4
- package/docs/development-flow.md +8 -6
- package/docs/project-memory.md +2 -2
- package/docs/release.md +7 -1
- package/docs/updating-existing-project.md +37 -0
- package/package.json +1 -1
- package/plugins/codex-workflow-gateway/.codex-plugin/plugin.json +1 -1
- package/plugins/codex-workflow-gateway/references/chat-dispatch.md +17 -5
- package/plugins/codex-workflow-gateway/references/protocol.md +39 -43
- package/plugins/codex-workflow-gateway/scripts/chat-dispatch.mjs +13 -3
- package/plugins/codex-workflow-gateway/scripts/chat-model-policy.mjs +18 -33
- package/plugins/codex-workflow-gateway/skills/codex-workflow-gateway/SKILL.md +15 -10
- package/references/state-machine.md +3 -1
- package/roles/delivery-coordinator.md +31 -11
- package/roles/scope-lead.md +1 -1
- package/roles/technical-planner.md +4 -1
- package/schemas/adoption-posture-event.schema.json +11 -0
- package/schemas/authorization-event.schema.json +1 -1
- package/schemas/corrective-decision-event.schema.json +23 -3
- package/schemas/corrective-replan-credentials.private.schema.json +8 -5
- package/schemas/corrective-replan-execution-event.private.schema.json +9 -0
- package/schemas/corrective-replan-public.schema.json +2 -1
- package/schemas/delegation-grant.schema.json +1 -1
- package/schemas/task.schema.json +18 -0
- package/schemas/transition-definition.schema.json +4 -0
- package/src/alpha6/adoption.ts +30 -7
- package/src/alpha6/corrective-decision-boundary.ts +9 -3
- package/src/alpha6/remediation.ts +69 -2
- package/src/alpha7/autonomy.ts +39 -1
- package/src/beta1/project-transaction.ts +13 -4
- package/src/cli-actions.ts +5 -0
- package/src/cli.ts +33 -0
- package/src/contracts.ts +19 -1
- package/src/delegation.ts +5 -1
- package/src/failed-checkpoint-update.ts +203 -0
- package/src/lifecycle/corrective-replan-credential-core.ts +39 -10
- package/src/lifecycle/corrective-replan.ts +12 -8
- package/src/lifecycle/semantic-registry.ts +4 -2
- package/src/observed-routes.ts +5 -0
- package/src/state/corrective-replan-executor.ts +1 -0
- package/src/state/corrective-replan-public.ts +50 -6
- package/src/version.ts +1 -1
- package/src/workflow-blocker-route.ts +1 -1
- package/src/workflow.ts +192 -22
|
@@ -25,6 +25,17 @@
|
|
|
25
25
|
"eventHash"
|
|
26
26
|
],
|
|
27
27
|
"properties": {
|
|
28
|
+
"delegatedAuthorization": {
|
|
29
|
+
"type": "object", "additionalProperties": false,
|
|
30
|
+
"required": ["grantId", "policyHash", "principal", "delegate", "transition"],
|
|
31
|
+
"properties": {
|
|
32
|
+
"grantId": { "type": "string", "pattern": "^DGR-[0-9A-HJKMNP-TV-Z]{26}$" },
|
|
33
|
+
"policyHash": { "$ref": "#/$defs/hash64" },
|
|
34
|
+
"principal": { "type": "string", "minLength": 1 },
|
|
35
|
+
"delegate": { "type": "string", "pattern": "^agent:.+" },
|
|
36
|
+
"transition": { "const": "project.adopt" }
|
|
37
|
+
}
|
|
38
|
+
},
|
|
28
39
|
"eventId": { "type": "string", "minLength": 1 },
|
|
29
40
|
"projectId": { "type": "string", "pattern": "^[a-f0-9]{24}$" },
|
|
30
41
|
"eventType": { "enum": ["project-registration", "adoption-apply"] },
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"delegate": { "type": "string", "minLength": 1 },
|
|
53
53
|
"transition": {
|
|
54
54
|
"enum": [
|
|
55
|
-
"project_memory.approve",
|
|
55
|
+
"project.adopt", "project_memory.approve",
|
|
56
56
|
"task.execution_authorize", "task.final_accept",
|
|
57
57
|
"milestone.execution_authorize", "milestone.final_accept"
|
|
58
58
|
]
|
|
@@ -38,8 +38,9 @@
|
|
|
38
38
|
"items": { "type": "string", "minLength": 1 }
|
|
39
39
|
},
|
|
40
40
|
"planHash": { "$ref": "#/$defs/hash64" },
|
|
41
|
-
"recoveryBasis": { "
|
|
41
|
+
"recoveryBasis": { "enum": ["plan-integrity", "planning-obstruction"] },
|
|
42
42
|
"planIntegrityEvidence": { "$ref": "#/$defs/planIntegrityEvidence" },
|
|
43
|
+
"planningObstructionEvidence": { "$ref": "#/$defs/planningObstructionEvidence" },
|
|
43
44
|
"previousEventHash": { "$ref": "#/$defs/nullableHash64" },
|
|
44
45
|
"recordedAt": { "type": "string", "format": "date-time" },
|
|
45
46
|
"eventHash": { "$ref": "#/$defs/hash64" }
|
|
@@ -48,17 +49,22 @@
|
|
|
48
49
|
{
|
|
49
50
|
"if": { "properties": { "triggeringAttemptCount": { "const": 2 } }, "required": ["triggeringAttemptCount"] },
|
|
50
51
|
"then": {
|
|
51
|
-
"required": ["recoveryBasis"
|
|
52
|
+
"required": ["recoveryBasis"],
|
|
52
53
|
"properties": { "decision": { "enum": ["continue-fix", "replan-required"] } }
|
|
53
54
|
}
|
|
54
55
|
},
|
|
55
56
|
{
|
|
56
|
-
"if": { "required": ["recoveryBasis"] },
|
|
57
|
+
"if": { "properties": { "recoveryBasis": { "const": "plan-integrity" } }, "required": ["recoveryBasis"] },
|
|
57
58
|
"then": {
|
|
58
59
|
"required": ["planIntegrityEvidence"]
|
|
59
60
|
},
|
|
60
61
|
"else": { "not": { "required": ["planIntegrityEvidence"] } }
|
|
61
62
|
},
|
|
63
|
+
{
|
|
64
|
+
"if": { "properties": { "recoveryBasis": { "const": "planning-obstruction" } }, "required": ["recoveryBasis"] },
|
|
65
|
+
"then": { "required": ["planningObstructionEvidence"], "properties": { "triggeringAttemptCount": { "const": 2 }, "decision": { "const": "replan-required" } } },
|
|
66
|
+
"else": { "not": { "required": ["planningObstructionEvidence"] } }
|
|
67
|
+
},
|
|
62
68
|
{
|
|
63
69
|
"if": {
|
|
64
70
|
"properties": {
|
|
@@ -83,6 +89,20 @@
|
|
|
83
89
|
}
|
|
84
90
|
],
|
|
85
91
|
"$defs": {
|
|
92
|
+
"planningObstructionEvidence": {
|
|
93
|
+
"type": "object", "additionalProperties": false,
|
|
94
|
+
"required": ["kind", "taskRevision", "sourcePlanHash", "baselinePlanHash", "baselineAuditEventId", "baselineAuditEventHash", "obstructionAuditEventId", "obstructionAuditEventHash", "priorAuthorizationHash", "remediationEventId", "remediationEventHash", "knowledgeRebindCount", "knowledgeRebindHash"],
|
|
95
|
+
"properties": {
|
|
96
|
+
"kind": { "const": "first-failure-rebound-planning-obstruction" },
|
|
97
|
+
"taskRevision": { "type": "integer", "minimum": 1 },
|
|
98
|
+
"sourcePlanHash": { "$ref": "#/$defs/hash64" }, "baselinePlanHash": { "$ref": "#/$defs/hash64" },
|
|
99
|
+
"baselineAuditEventId": { "type": "string", "minLength": 1 }, "baselineAuditEventHash": { "$ref": "#/$defs/hash64" },
|
|
100
|
+
"obstructionAuditEventId": { "type": "string", "minLength": 1 }, "obstructionAuditEventHash": { "$ref": "#/$defs/hash64" },
|
|
101
|
+
"priorAuthorizationHash": { "$ref": "#/$defs/hash64" },
|
|
102
|
+
"remediationEventId": { "type": "string", "minLength": 1 }, "remediationEventHash": { "$ref": "#/$defs/hash64" },
|
|
103
|
+
"knowledgeRebindCount": { "type": "integer", "minimum": 1 }, "knowledgeRebindHash": { "$ref": "#/$defs/hash64" }
|
|
104
|
+
}
|
|
105
|
+
},
|
|
86
106
|
"taskId": { "type": "string", "pattern": "^TASK-[0-9A-HJKMNP-TV-Z]{26}$" },
|
|
87
107
|
"stepId": { "type": "string", "pattern": "^STEP-[0-9]{3}(?:-[A-Z0-9-]+)?$" },
|
|
88
108
|
"hash64": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"required": ["id", "version"],
|
|
11
11
|
"properties": { "id": { "$ref": "#/$defs/id" }, "version": { "const": 1 } }
|
|
12
12
|
},
|
|
13
|
+
"delegatedAuthority": {"type": "object", "additionalProperties": false, "required": ["grantId", "policyHash", "principal", "delegate", "transition", "expiresAt"], "properties": {"grantId": {"$ref": "#/$defs/id"}, "policyHash": {"$ref": "#/$defs/hash"}, "principal": {"$ref": "#/$defs/id"}, "delegate": {"$ref": "#/$defs/id"}, "transition": {"const": "task.execution_authorize"}, "expiresAt": {"type": "string", "format": "date-time"}}},
|
|
13
14
|
"bindingValues": {
|
|
14
15
|
"type": "object", "additionalProperties": false,
|
|
15
16
|
"required": ["transitionId", "runtimeFingerprint", "catalogHash", "lifecycleEpoch", "taskId", "revision", "currentPlanHash", "briefHash", "knowledgeRevision", "knowledgeHash", "targetKnowledgeRevision", "targetKnowledgeHash", "gitHead", "guardedStepId", "correctiveDecisionEventId", "correctiveDecisionHash", "attemptCount", "remediationTailHash", "candidatePlanHash", "candidateStepSetHash", "planAuthorIdentityHash", "planValidationHash", "planValidatorIdentityHash", "riskAuditHash", "riskAuditorIdentityHash", "actorIdentityHash", "actorRole", "humanPrincipalIdentityHash", "capabilityId", "capabilityVersion"],
|
|
@@ -23,7 +24,7 @@
|
|
|
23
24
|
"targetKnowledgeRevision": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 },
|
|
24
25
|
"targetKnowledgeHash": { "$ref": "#/$defs/hash" }, "gitHead": { "type": "string", "pattern": "^(?:[a-f0-9]{40}|[a-f0-9]{64})$" },
|
|
25
26
|
"guardedStepId": { "$ref": "#/$defs/id" }, "correctiveDecisionEventId": { "$ref": "#/$defs/id" },
|
|
26
|
-
"correctiveDecisionHash": { "$ref": "#/$defs/hash" }, "attemptCount": { "type": "integer", "minimum":
|
|
27
|
+
"correctiveDecisionHash": { "$ref": "#/$defs/hash" }, "attemptCount": { "type": "integer", "minimum": 1, "maximum": 9007199254740991 },
|
|
27
28
|
"remediationTailHash": { "$ref": "#/$defs/hash" }, "candidatePlanHash": { "$ref": "#/$defs/hash" },
|
|
28
29
|
"candidateStepSetHash": { "$ref": "#/$defs/hash" }, "planAuthorIdentityHash": { "$ref": "#/$defs/hash" },
|
|
29
30
|
"planValidationHash": { "$ref": "#/$defs/hash" }, "planValidatorIdentityHash": { "$ref": "#/$defs/hash" },
|
|
@@ -45,10 +46,10 @@
|
|
|
45
46
|
"required": ["id", "version", "issuer", "source", "scope", "delegation"],
|
|
46
47
|
"properties": {
|
|
47
48
|
"id": { "const": "capability.task.corrective-replan" }, "version": { "const": 1 },
|
|
48
|
-
"issuer": { "type": "object", "additionalProperties": false, "required": ["kind", "identityHash"], "properties": { "kind": { "
|
|
49
|
-
"source": { "
|
|
49
|
+
"issuer": { "type": "object", "additionalProperties": false, "required": ["kind", "identityHash"], "properties": { "kind": { "enum": ["human", "delegate"] }, "identityHash": { "$ref": "#/$defs/hash" } } },
|
|
50
|
+
"source": { "enum": ["human-confirmation", "delegation-grant"] },
|
|
50
51
|
"scope": { "type": "object", "additionalProperties": false, "required": ["transitionId", "taskId", "candidatePlanHash", "manifestSha256"], "properties": { "transitionId": { "const": "task.plan.corrective-replan" }, "taskId": { "$ref": "#/$defs/id" }, "candidatePlanHash": { "$ref": "#/$defs/hash" }, "manifestSha256": { "$ref": "#/$defs/hash" } } },
|
|
51
|
-
"delegation": { "const": "forbidden" }
|
|
52
|
+
"delegation": { "oneOf": [{ "const": "forbidden" }, { "$ref": "#/$defs/delegatedAuthority" }] }
|
|
52
53
|
}
|
|
53
54
|
},
|
|
54
55
|
"bindingSha256": { "$ref": "#/$defs/hash" }
|
|
@@ -82,10 +83,12 @@
|
|
|
82
83
|
"receiptValidation": { "type": "object", "additionalProperties": false, "required": ["attestationId", "attestationHash", "planValidationHash", "snapshotBindingHash", "planAuthorIdentityHash", "planValidatorIdentityHash", "findingsHash", "dispositionsHash", "validatedAtMs", "expiresAtMs"], "properties": { "attestationId": { "$ref": "#/$defs/id" }, "attestationHash": { "$ref": "#/$defs/hash" }, "planValidationHash": { "$ref": "#/$defs/hash" }, "snapshotBindingHash": { "$ref": "#/$defs/hash" }, "planAuthorIdentityHash": { "$ref": "#/$defs/hash" }, "planValidatorIdentityHash": { "$ref": "#/$defs/hash" }, "findingsHash": { "$ref": "#/$defs/hash" }, "dispositionsHash": { "$ref": "#/$defs/hash" }, "validatedAtMs": { "$ref": "#/$defs/time" }, "expiresAtMs": { "$ref": "#/$defs/time" } } },
|
|
83
84
|
"correctiveReplanReceiptV1": {
|
|
84
85
|
"type": "object", "additionalProperties": false,
|
|
85
|
-
"
|
|
86
|
+
"oneOf": [{ "required": ["human"], "properties": { "human": {}, "delegated": false } }, { "required": ["delegated"], "properties": { "delegated": {}, "human": false } }],
|
|
87
|
+
"required": ["schemaVersion", "receiptId", "domain", "binding", "validation", "audit", "supersession", "issuedAtMs", "expiresAtMs", "domainHash", "receiptHash"],
|
|
86
88
|
"properties": {
|
|
87
89
|
"schemaVersion": { "const": 1 }, "receiptId": { "$ref": "#/$defs/id" }, "domain": { "const": "codex-workflow/corrective-replan-receipt/v1" }, "binding": { "$ref": "#/$defs/correctiveReplanBindingV1" }, "validation": { "$ref": "#/$defs/receiptValidation" },
|
|
88
90
|
"audit": { "type": "object", "additionalProperties": false, "required": ["riskAuditHash", "riskAuditorIdentityHash"], "properties": { "riskAuditHash": { "$ref": "#/$defs/hash" }, "riskAuditorIdentityHash": { "$ref": "#/$defs/hash" } } },
|
|
91
|
+
"delegated": { "type": "object", "additionalProperties": false, "required": ["authority", "authorizedAtMs"], "properties": { "authority": { "$ref": "#/$defs/delegatedAuthority" }, "authorizedAtMs": { "$ref": "#/$defs/time" } } },
|
|
89
92
|
"human": { "type": "object", "additionalProperties": false, "required": ["challengeId", "challengeHash", "humanPrincipalIdentityHash", "confirmedAtMs"], "properties": { "challengeId": { "$ref": "#/$defs/id" }, "challengeHash": { "$ref": "#/$defs/hash" }, "humanPrincipalIdentityHash": { "$ref": "#/$defs/hash" }, "confirmedAtMs": { "$ref": "#/$defs/time" } } },
|
|
90
93
|
"supersession": { "type": "object", "additionalProperties": false, "required": ["priorGrantHash", "disposition"], "properties": { "priorGrantHash": { "$ref": "#/$defs/hash" }, "disposition": { "const": "superseded" } } },
|
|
91
94
|
"issuedAtMs": { "$ref": "#/$defs/time" }, "expiresAtMs": { "$ref": "#/$defs/time" }, "domainHash": { "$ref": "#/$defs/hash" }, "receiptHash": { "$ref": "#/$defs/hash" }
|
|
@@ -42,6 +42,15 @@
|
|
|
42
42
|
"planRiskAuditEventHash": { "$ref": "#/$defs/hash" },
|
|
43
43
|
"actorIdentityHash": { "$ref": "#/$defs/hash" },
|
|
44
44
|
"humanPrincipalIdentityHash": { "$ref": "#/$defs/hash" },
|
|
45
|
+
"delegatedAuthority": {
|
|
46
|
+
"type": "object", "additionalProperties": false,
|
|
47
|
+
"required": ["grantId", "policyHash", "principal", "delegate", "transition", "expiresAt"],
|
|
48
|
+
"properties": {
|
|
49
|
+
"grantId": { "type": "string", "minLength": 1 }, "policyHash": { "$ref": "#/$defs/hash" },
|
|
50
|
+
"principal": { "type": "string", "minLength": 1 }, "delegate": { "type": "string", "minLength": 1 },
|
|
51
|
+
"transition": { "const": "task.execution_authorize" }, "expiresAt": { "type": "string", "format": "date-time" }
|
|
52
|
+
}
|
|
53
|
+
},
|
|
45
54
|
"priorGrantHash": { "$ref": "#/$defs/hash" },
|
|
46
55
|
"taskStateSha256": { "$ref": "#/$defs/hash" },
|
|
47
56
|
"planBytesSha256": { "$ref": "#/$defs/hash" },
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"additionalProperties": false,
|
|
11
11
|
"required": ["plan", "validation", "riskAudit", "authority"],
|
|
12
12
|
"properties": {
|
|
13
|
+
"delegation": { "type": "object", "additionalProperties": false, "required": ["grantId", "actor"], "properties": { "grantId": { "$ref": "#/$defs/id" }, "actor": { "$ref": "#/$defs/id" } } },
|
|
13
14
|
"plan": { "$ref": "https://codex-workflow.local/schemas/transition-payloads.schema.json#/$defs/taskPlanInput" },
|
|
14
15
|
"validation": {
|
|
15
16
|
"type": "object",
|
|
@@ -69,7 +70,7 @@
|
|
|
69
70
|
"additionalProperties": false,
|
|
70
71
|
"required": ["kind", "version", "taskId", "preparedAtMs", "expiresAtMs", "candidatePlanHash", "bindingSha256", "attestationId", "attestationHash", "challengeId", "runtimeFingerprint", "catalogHash", "lifecycleEpoch", "preparationSha256"],
|
|
71
72
|
"properties": {
|
|
72
|
-
"kind": { "
|
|
73
|
+
"kind": { "enum": ["corrective-replan-human-gate", "corrective-replan-delegated-binding"] },
|
|
73
74
|
"version": { "const": 1 },
|
|
74
75
|
"taskId": { "$ref": "#/$defs/id" },
|
|
75
76
|
"preparedAtMs": { "$ref": "#/$defs/time" },
|
package/schemas/task.schema.json
CHANGED
|
@@ -233,6 +233,24 @@
|
|
|
233
233
|
},
|
|
234
234
|
"additionalProperties": false
|
|
235
235
|
},
|
|
236
|
+
"failedCheckpoint": {
|
|
237
|
+
"type": "object",
|
|
238
|
+
"required": ["protocol", "sourceHead", "sourcePlanHash", "stepId", "sourceDirtyHash", "targetDirtyHash", "dirtyFiles", "remediationEventId", "remediationEventHash", "sourceReceiptHash", "artifactHash"],
|
|
239
|
+
"properties": {
|
|
240
|
+
"protocol": { "const": "failed-checkpoint-update-v1" },
|
|
241
|
+
"sourceHead": { "type": "string", "pattern": "^[a-f0-9]{40}$" },
|
|
242
|
+
"sourcePlanHash": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
|
|
243
|
+
"stepId": { "type": "string", "pattern": "^STEP-[A-Z0-9][A-Z0-9-]{2,63}$" },
|
|
244
|
+
"sourceDirtyHash": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
|
|
245
|
+
"targetDirtyHash": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
|
|
246
|
+
"dirtyFiles": { "type": "array", "minItems": 1, "uniqueItems": true, "items": { "type": "string", "minLength": 1 } },
|
|
247
|
+
"remediationEventId": { "type": "string", "pattern": "^RME-[0-9A-HJKMNP-TV-Z]{26}$" },
|
|
248
|
+
"remediationEventHash": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
|
|
249
|
+
"sourceReceiptHash": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
|
|
250
|
+
"artifactHash": { "type": "string", "pattern": "^[a-f0-9]{64}$" }
|
|
251
|
+
},
|
|
252
|
+
"additionalProperties": false
|
|
253
|
+
},
|
|
236
254
|
"rootCauseReplan": {
|
|
237
255
|
"type": "object",
|
|
238
256
|
"required": [
|
|
@@ -296,6 +296,7 @@
|
|
|
296
296
|
"credential-verifier.corrective-replan.receipt@1"
|
|
297
297
|
],
|
|
298
298
|
"delegationPolicies": [
|
|
299
|
+
"delegation.corrective-execution-exact@1",
|
|
299
300
|
"delegation.forbidden@1"
|
|
300
301
|
],
|
|
301
302
|
"effectAdapters": [
|
|
@@ -360,6 +361,7 @@
|
|
|
360
361
|
"ownership.corrective-replan-clear@1"
|
|
361
362
|
],
|
|
362
363
|
"principalBases": [
|
|
364
|
+
"principal-basis.bound-corrective-authority@1",
|
|
363
365
|
"principal-basis.human-confirmation@1"
|
|
364
366
|
],
|
|
365
367
|
"projections": [
|
|
@@ -763,6 +765,7 @@
|
|
|
763
765
|
"properties": {
|
|
764
766
|
"id": {
|
|
765
767
|
"enum": [
|
|
768
|
+
"principal-basis.bound-corrective-authority",
|
|
766
769
|
"principal-basis.human-confirmation"
|
|
767
770
|
]
|
|
768
771
|
},
|
|
@@ -835,6 +838,7 @@
|
|
|
835
838
|
"properties": {
|
|
836
839
|
"id": {
|
|
837
840
|
"enum": [
|
|
841
|
+
"delegation.corrective-execution-exact",
|
|
838
842
|
"delegation.forbidden"
|
|
839
843
|
]
|
|
840
844
|
},
|
package/src/alpha6/adoption.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
AdoptionPostureEvent,
|
|
3
|
+
DelegatedAuthorizationEvidence,
|
|
3
4
|
AdoptionRepositoryIdentity,
|
|
4
5
|
AdoptionTaskBaseline,
|
|
5
6
|
AdoptionTaskStepSnapshot,
|
|
@@ -8,6 +9,7 @@ import type {
|
|
|
8
9
|
TaskStatus,
|
|
9
10
|
} from '../contracts.js';
|
|
10
11
|
import { PROTOCOL_VERSION, STATE_SCHEMA_VERSION } from '../contracts.js';
|
|
12
|
+
import { assertDelegationCanAuthorize } from '../delegation.js';
|
|
11
13
|
import { WorkflowError } from '../errors.js';
|
|
12
14
|
import type { RepositoryIdentity } from '../repository.js';
|
|
13
15
|
import type { FileStateStore } from '../state/store.js';
|
|
@@ -243,8 +245,15 @@ export function applyAdoptionPosture(
|
|
|
243
245
|
input: AdoptionPreparationInput,
|
|
244
246
|
actor: string,
|
|
245
247
|
confirmationCode: string,
|
|
248
|
+
grantId: string | null = null,
|
|
246
249
|
): AdoptionPostureEvent {
|
|
247
|
-
|
|
250
|
+
let delegatedAuthorization: DelegatedAuthorizationEvidence | undefined;
|
|
251
|
+
if (grantId) {
|
|
252
|
+
const grant = store.readDelegation(input.projectId, grantId);
|
|
253
|
+
delegatedAuthorization = assertDelegationCanAuthorize(grant, 'project.adopt', actor,
|
|
254
|
+
{ kind: 'project', id: input.projectId }, input.now);
|
|
255
|
+
}
|
|
256
|
+
const normalizedActor = delegatedAuthorization ? actor.trim() : normalizeHumanActor(actor, 'Adoption apply actor');
|
|
248
257
|
const currentPosture = readCurrentAdoptionPosture(store, input.projectId);
|
|
249
258
|
if (currentPosture) {
|
|
250
259
|
throw new WorkflowError('TRANSITION_BLOCKED', `Project ${input.projectId} already has an adoption posture.`, {
|
|
@@ -279,6 +288,7 @@ export function applyAdoptionPosture(
|
|
|
279
288
|
adoptionBoundaryAt: input.now.toISOString(),
|
|
280
289
|
recordedAt: input.now.toISOString(),
|
|
281
290
|
actor: normalizedActor,
|
|
291
|
+
...(delegatedAuthorization ? { delegatedAuthorization } : {}),
|
|
282
292
|
confirmationCodeBindingHash: prepared.confirmationCodeBindingHash,
|
|
283
293
|
};
|
|
284
294
|
event.eventHash = computeSidecarEventHash(event);
|
|
@@ -403,6 +413,7 @@ function validateAdoptionPostureEvent(
|
|
|
403
413
|
'projectId',
|
|
404
414
|
'eventType',
|
|
405
415
|
'actor',
|
|
416
|
+
'delegatedAuthorization',
|
|
406
417
|
'packageVersion',
|
|
407
418
|
'protocolVersion',
|
|
408
419
|
'stateSchemaVersion',
|
|
@@ -504,7 +515,7 @@ function validateAdoptionPostureEvent(
|
|
|
504
515
|
validateTerminalTasks(event, file, line);
|
|
505
516
|
validateActiveTasks(event, file, line);
|
|
506
517
|
if (event.eventType === 'project-registration') {
|
|
507
|
-
if (event.actor !== null || event.confirmationCodeBindingHash !== null) {
|
|
518
|
+
if (event.actor !== null || event.confirmationCodeBindingHash !== null || event.delegatedAuthorization !== undefined) {
|
|
508
519
|
throw new WorkflowError('STATE_CORRUPT', 'Project registration adoption posture must not record actor or confirmation binding.', {
|
|
509
520
|
file,
|
|
510
521
|
line,
|
|
@@ -518,11 +529,23 @@ function validateAdoptionPostureEvent(
|
|
|
518
529
|
eventId: event.eventId,
|
|
519
530
|
});
|
|
520
531
|
} else {
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
line
|
|
524
|
-
|
|
525
|
-
|
|
532
|
+
if (event.delegatedAuthorization !== undefined) {
|
|
533
|
+
const evidence = event.delegatedAuthorization;
|
|
534
|
+
assertPlainObject(evidence, 'Adoption delegated authority', file, line);
|
|
535
|
+
assertExactKeys(evidence as unknown as Record<string, unknown>,
|
|
536
|
+
['grantId', 'transition', 'principal', 'delegate', 'policyHash'],
|
|
537
|
+
'Adoption delegated authority contains unexpected fields.', file, line);
|
|
538
|
+
if (['grantId', 'transition', 'principal', 'delegate', 'policyHash'].some((key) =>
|
|
539
|
+
typeof (evidence as unknown as Record<string, unknown>)[key] !== 'string')) {
|
|
540
|
+
throw new WorkflowError('STATE_CORRUPT', 'Adoption delegated authority fields must be strings.', { file, line });
|
|
541
|
+
}
|
|
542
|
+
if (!event.actor.startsWith('agent:') || evidence.delegate !== event.actor
|
|
543
|
+
|| evidence.transition !== 'project.adopt' || !/^DGR-[0-9A-HJKMNP-TV-Z]{26}$/.test(evidence.grantId)
|
|
544
|
+
|| !/^[a-f0-9]{64}$/.test(evidence.policyHash) || !evidence.principal || evidence.principal.startsWith('agent:'))
|
|
545
|
+
throw new WorkflowError('STATE_CORRUPT', 'Adoption delegated authority is invalid.');
|
|
546
|
+
} else {
|
|
547
|
+
validateStoredHumanActor(event.actor, 'Adoption apply actor', { file, line, eventId: event.eventId });
|
|
548
|
+
}
|
|
526
549
|
if (event.confirmationCodeBindingHash === null) {
|
|
527
550
|
throw new WorkflowError('STATE_CORRUPT', 'Adoption apply posture must record confirmationCodeBindingHash.', {
|
|
528
551
|
file,
|
|
@@ -7,6 +7,8 @@ import { changedFiles, currentBranch, headCommit } from '../repository.js';
|
|
|
7
7
|
import type { FileStateStore } from '../state/store.js';
|
|
8
8
|
import { hashDirtyWorktree } from './downstream-proof.js';
|
|
9
9
|
import { effectiveStepAllowedWrites, readRemediationEvents } from './remediation.js';
|
|
10
|
+
import { readTaskC1Posture } from './handoff.js';
|
|
11
|
+
import { readFailedCheckpointDirtyTransport } from '../failed-checkpoint-update.js';
|
|
10
12
|
|
|
11
13
|
/** Checkout eligibility only: never an audit, writer credential, or permission to widen scope. */
|
|
12
14
|
export function correctiveDecisionCheckoutEligible(
|
|
@@ -18,13 +20,17 @@ export function correctiveDecisionCheckoutEligible(
|
|
|
18
20
|
validateTaskHistory(repository, task);
|
|
19
21
|
const dirty = changedFiles(repository).sort();
|
|
20
22
|
if (dirty.length === 0) return true;
|
|
21
|
-
if (task.workspaceOwner
|
|
23
|
+
if (!['local', 'external'].includes(task.workspaceOwner ?? '') || task.steps.some(step => step.status === 'in_progress')) return false;
|
|
24
|
+
if (task.workspaceOwner === 'external' && readTaskC1Posture(store, task).state !== 'claimed') return false;
|
|
22
25
|
const step = task.steps.find(candidate => candidate.id === stepId);
|
|
23
26
|
const pause = step?.failurePause;
|
|
24
27
|
if (step?.status !== 'failed' || !pause) return false;
|
|
25
28
|
const terminal = readRemediationEvents(store, task).filter(event => event.stepId === stepId).at(-1);
|
|
29
|
+
const transport = terminal?.failureEvidence.dirtyWorktreeHash
|
|
30
|
+
? readFailedCheckpointDirtyTransport(store, repository, task, stepId,
|
|
31
|
+
terminal.failureEvidence.completionCommit, terminal.failureEvidence.dirtyWorktreeHash) : null;
|
|
26
32
|
if (!terminal || terminal.failureKind !== 'checks-failed'
|
|
27
|
-
|| terminal.failureEvidence.completionCommit !== headCommit(repository)
|
|
33
|
+
|| (transport?.targetHead ?? terminal.failureEvidence.completionCommit) !== headCommit(repository)
|
|
28
34
|
|| terminal.failureEvidence.checksHash !== pause.checksHash
|
|
29
35
|
|| terminal.failureEvidence.dirtyWorktreeHash !== pause.dirtyWorktreeHash
|
|
30
36
|
|| JSON.stringify(dirty) !== JSON.stringify([...pause.dirtyFiles].sort())) return false;
|
|
@@ -35,5 +41,5 @@ export function correctiveDecisionCheckoutEligible(
|
|
|
35
41
|
try { if (!lstatSync(path.join(repository, file)).isFile()) return false; }
|
|
36
42
|
catch (error) { if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error; }
|
|
37
43
|
}
|
|
38
|
-
return hashDirtyWorktree(repository, dirty) === pause.dirtyWorktreeHash;
|
|
44
|
+
return hashDirtyWorktree(repository, dirty) === (transport?.targetDirtyHash ?? pause.dirtyWorktreeHash);
|
|
39
45
|
}
|
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
import { correctiveReplanBoundaryMatchesStep, readCompletedCorrectiveReplanHistory } from '../state/corrective-replan-executor.js';
|
|
39
39
|
import { semanticBoundaryHash, validLiteralTestInvocationEvidence } from './component-owner.js';
|
|
40
40
|
import { reviewerCauseFingerprint } from './remediation-cause.js';
|
|
41
|
+
import { readFailedStepPlanningRecovery } from './failed-step-planning-recovery.js';
|
|
41
42
|
|
|
42
43
|
export const REMEDIATION_EVENTS_SIDECAR = 'remediation-events.jsonl';
|
|
43
44
|
export const CORRECTIVE_DECISIONS_SIDECAR = 'corrective-decisions.jsonl';
|
|
@@ -419,6 +420,27 @@ function findCorrectiveRecoveryDecisionIdForFailure(
|
|
|
419
420
|
return recoveries[0]?.correctiveDecisionEventId ?? null;
|
|
420
421
|
}
|
|
421
422
|
|
|
423
|
+
/** A first failed execution plus immutable rebound obstruction is planning evidence, not permission to retry. */
|
|
424
|
+
export function firstFailurePlanningObstructionEvidence(
|
|
425
|
+
store: FileStateStore,
|
|
426
|
+
task: TaskState,
|
|
427
|
+
stepId: string,
|
|
428
|
+
): NonNullable<CorrectiveDecisionEvent['planningObstructionEvidence']> | null {
|
|
429
|
+
const failures = readRemediationEvents(store, task).filter(event => event.stepId === stepId);
|
|
430
|
+
if (failures.length !== 1 || task.steps.find(step => step.id === stepId)?.status !== 'failed') return null;
|
|
431
|
+
const recovery = readFailedStepPlanningRecovery(store, task);
|
|
432
|
+
if (!recovery || !recovery.current.reviewRequiredStepIds.includes(stepId)) return null;
|
|
433
|
+
return {
|
|
434
|
+
kind: 'first-failure-rebound-planning-obstruction', taskRevision: task.revision,
|
|
435
|
+
sourcePlanHash: task.planHash!, baselinePlanHash: recovery.baseline.planHash,
|
|
436
|
+
baselineAuditEventId: recovery.baseline.eventId, baselineAuditEventHash: recovery.baseline.eventHash,
|
|
437
|
+
obstructionAuditEventId: recovery.current.audit.eventId, obstructionAuditEventHash: recovery.current.audit.eventHash,
|
|
438
|
+
priorAuthorizationHash: hashCanonical(recovery.priorAuthorization),
|
|
439
|
+
remediationEventId: failures[0]!.eventId, remediationEventHash: failures[0]!.eventHash,
|
|
440
|
+
knowledgeRebindCount: task.knowledgeRebinds.length, knowledgeRebindHash: hashCanonical(task.knowledgeRebinds),
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
|
|
422
444
|
export function appendCorrectiveDecisionEvent(
|
|
423
445
|
store: FileStateStore,
|
|
424
446
|
task: TaskState,
|
|
@@ -431,7 +453,9 @@ export function appendCorrectiveDecisionEvent(
|
|
|
431
453
|
validateCorrectiveDecisionAudit(correctiveAudit);
|
|
432
454
|
const remediationEvents = readRemediationEvents(store, task).filter((event) => event.stepId === stepId);
|
|
433
455
|
const triggeringAttemptCount = remediationEvents.length + 1;
|
|
434
|
-
|
|
456
|
+
const planningObstructionEvidence = triggeringAttemptCount === 2 && correctiveAudit.decision === 'replan-required'
|
|
457
|
+
? firstFailurePlanningObstructionEvidence(store, task, stepId) : null;
|
|
458
|
+
if (triggeringAttemptCount < 3 && !planningObstructionEvidence) {
|
|
435
459
|
throw new WorkflowError(
|
|
436
460
|
'TRANSITION_BLOCKED',
|
|
437
461
|
`Corrective decision is not available for ${stepId} before two guarded remediation failures exist.`,
|
|
@@ -500,6 +524,7 @@ export function appendCorrectiveDecisionEvent(
|
|
|
500
524
|
stepId,
|
|
501
525
|
triggeringAttemptCount,
|
|
502
526
|
decision: correctiveAudit.decision,
|
|
527
|
+
...(planningObstructionEvidence ? { recoveryBasis: 'planning-obstruction' as const, planningObstructionEvidence } : {}),
|
|
503
528
|
auditor: correctiveAudit.auditor.trim(),
|
|
504
529
|
coveredFindingIds,
|
|
505
530
|
coveredEventIds,
|
|
@@ -645,6 +670,11 @@ export function readGuardedRemediationPostureForStep(
|
|
|
645
670
|
? findCurrentCorrectiveDecision(task, stepId, 2, history.correctiveDecisions)
|
|
646
671
|
: null;
|
|
647
672
|
const currentDecision = findCurrentCorrectiveDecision(task, stepId, history.nextAttemptOrdinal, history.correctiveDecisions);
|
|
673
|
+
if (remediationEvents.length === 1 && currentDecision === null
|
|
674
|
+
&& firstFailurePlanningObstructionEvidence(store, task, stepId)) {
|
|
675
|
+
return { stepId, guardedCategory: requireGuardedCategory(planRiskAudit, stepId), attemptCount: 1,
|
|
676
|
+
nextAttemptOrdinal: history.nextAttemptOrdinal, mode: 'corrective', correctiveDecision: null, posture: 'decision-required' };
|
|
677
|
+
}
|
|
648
678
|
if (remediationEvents.length < 2 && earlyPlanIntegrityDecision?.recoveryBasis !== 'plan-integrity'
|
|
649
679
|
&& currentDecision === null
|
|
650
680
|
&& deriveCurrentCauseDecision(remediationEvents, history.priorRemediationEvents) !== 'root-cause-replan') return null;
|
|
@@ -908,6 +938,11 @@ function deriveAttemptGateFromHistory(
|
|
|
908
938
|
nextAttemptOrdinal,
|
|
909
939
|
correctiveDecisions,
|
|
910
940
|
);
|
|
941
|
+
if (currentCorrectiveDecision?.recoveryBasis === 'planning-obstruction') {
|
|
942
|
+
return { stepId, guardedCategory, attemptCount, nextAttemptOrdinal, mode: 'ordinary',
|
|
943
|
+
correctiveDecision: currentCorrectiveDecision,
|
|
944
|
+
posture: currentCorrectiveDecision.planHash === task.planHash ? 'replan-required' : 'replan-ready' };
|
|
945
|
+
}
|
|
911
946
|
const planIntegrityDecision = currentCorrectiveDecision;
|
|
912
947
|
if (planIntegrityDecision?.recoveryBasis === 'plan-integrity'
|
|
913
948
|
&& (planIntegrityDecision.planHash === task.planHash || planIntegrityDecision.decision === 'replan-required'
|
|
@@ -1414,7 +1449,19 @@ function validateStoredCorrectiveDecisionEvent(
|
|
|
1414
1449
|
normalizeStringList(event.coveredFindingIds, 'Corrective decision coveredFindingIds');
|
|
1415
1450
|
normalizeStringList(event.coveredEventIds, 'Corrective decision coveredEventIds');
|
|
1416
1451
|
assertHash64(event.planHash, `Corrective decision planHash at ${file}:${line}`);
|
|
1417
|
-
if (event.recoveryBasis === '
|
|
1452
|
+
if (event.recoveryBasis === 'planning-obstruction' || event.planningObstructionEvidence !== undefined) {
|
|
1453
|
+
const evidence = event.planningObstructionEvidence;
|
|
1454
|
+
if (!evidence || event.recoveryBasis !== 'planning-obstruction' || event.planIntegrityEvidence !== undefined
|
|
1455
|
+
|| evidence.kind !== 'first-failure-rebound-planning-obstruction' || event.decision !== 'replan-required'
|
|
1456
|
+
|| event.triggeringAttemptCount !== 2 || evidence.sourcePlanHash !== event.planHash
|
|
1457
|
+
|| !Number.isSafeInteger(evidence.taskRevision) || evidence.taskRevision < 1
|
|
1458
|
+
|| !Number.isSafeInteger(evidence.knowledgeRebindCount) || evidence.knowledgeRebindCount < 1
|
|
1459
|
+
|| event.coveredEventIds.length !== 1 || event.coveredEventIds[0] !== evidence.remediationEventId) {
|
|
1460
|
+
throw new WorkflowError('STATE_CORRUPT', 'First-failure planning obstruction requires exact bounded proof.', { file, line, taskId: task.id });
|
|
1461
|
+
}
|
|
1462
|
+
for (const key of ['sourcePlanHash', 'baselinePlanHash', 'baselineAuditEventHash', 'obstructionAuditEventHash',
|
|
1463
|
+
'priorAuthorizationHash', 'remediationEventHash', 'knowledgeRebindHash'] as const) assertHash64(evidence[key], `Planning obstruction ${key}`);
|
|
1464
|
+
} else if (event.recoveryBasis === 'plan-integrity' || event.planIntegrityEvidence !== undefined) {
|
|
1418
1465
|
validatePlanIntegrityDecision(event, task, file, line);
|
|
1419
1466
|
} else if (event.triggeringAttemptCount === 2) {
|
|
1420
1467
|
throw new WorkflowError('STATE_CORRUPT', 'A second-attempt corrective decision requires bounded Plan-integrity evidence.', {
|
|
@@ -2200,6 +2247,26 @@ function validateCorrectiveDecisionCoverage(
|
|
|
2200
2247
|
});
|
|
2201
2248
|
}
|
|
2202
2249
|
}
|
|
2250
|
+
if (decision.recoveryBasis === 'planning-obstruction') {
|
|
2251
|
+
const proof = decision.planningObstructionEvidence!;
|
|
2252
|
+
const audits = readPlanRiskAuditEvents(store, task);
|
|
2253
|
+
const baseline = audits.find(event => event.eventId === proof.baselineAuditEventId && event.eventHash === proof.baselineAuditEventHash);
|
|
2254
|
+
const obstruction = audits.find(event => event.eventId === proof.obstructionAuditEventId && event.eventHash === proof.obstructionAuditEventHash);
|
|
2255
|
+
const failed = priorEvents[0];
|
|
2256
|
+
if (!baseline || !obstruction || !['approved', 'approved-with-rationale'].includes(baseline.decision)
|
|
2257
|
+
|| obstruction.decision !== 'stop-escalate' || audits.indexOf(baseline) >= audits.indexOf(obstruction)
|
|
2258
|
+
|| baseline.planHash !== proof.baselinePlanHash || obstruction.planHash !== proof.sourcePlanHash
|
|
2259
|
+
|| obstruction.taskRevision !== proof.taskRevision
|
|
2260
|
+
|| baseline.briefHash !== obstruction.briefHash || baseline.briefHash !== task.briefHash
|
|
2261
|
+
|| !failed || priorEvents.length !== 1 || failed.eventId !== proof.remediationEventId || failed.eventHash !== proof.remediationEventHash
|
|
2262
|
+
|| !task.authorizations.some(authorization => authorization.kind === 'execution' && authorization.decision === 'superseded'
|
|
2263
|
+
&& authorization.planHash === baseline.planHash && authorization.briefHash === baseline.briefHash
|
|
2264
|
+
&& hashCanonical(authorization) === proof.priorAuthorizationHash)
|
|
2265
|
+
|| task.knowledgeRebinds.length < proof.knowledgeRebindCount
|
|
2266
|
+
|| hashCanonical(task.knowledgeRebinds.slice(0, proof.knowledgeRebindCount)) !== proof.knowledgeRebindHash) {
|
|
2267
|
+
throw new WorkflowError('STATE_CORRUPT', 'Planning-obstruction decision does not match immutable audit, authorization, rebind and failure history.', { taskId: task.id, decisionEventId: decision.eventId });
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
2203
2270
|
if (decision.recoveryBasis === 'plan-integrity') {
|
|
2204
2271
|
const evidence = decision.planIntegrityEvidence;
|
|
2205
2272
|
const bindings = evidence?.kind === 'missing-npm-script-outside-allowed-writes'
|
package/src/alpha7/autonomy.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type {
|
|
|
6
6
|
SidecarEventRecord,
|
|
7
7
|
} from '../contracts.js';
|
|
8
8
|
import { STATE_SCHEMA_VERSION } from '../contracts.js';
|
|
9
|
-
import { hashDelegationPolicy } from '../delegation.js';
|
|
9
|
+
import { assertDelegationCanAuthorize, hashDelegationPolicy } from '../delegation.js';
|
|
10
10
|
import { WorkflowError } from '../errors.js';
|
|
11
11
|
import type { FileStateStore } from '../state/store.js';
|
|
12
12
|
import { createEntityId, createUlid } from '../ulid.js';
|
|
@@ -284,6 +284,44 @@ export function applyMilestoneAutonomyContract(options: {
|
|
|
284
284
|
return { contract: event, grant };
|
|
285
285
|
}
|
|
286
286
|
|
|
287
|
+
/** Derive a contract without issuing, extending or reactivating a grant. */
|
|
288
|
+
export function bindMilestoneAutonomyToGrant(options: {
|
|
289
|
+
store: FileStateStore; projectId: string; milestone: MilestoneState;
|
|
290
|
+
actor: string; grantId: string; now: Date;
|
|
291
|
+
}): ActiveMilestoneAutonomy {
|
|
292
|
+
const { store, projectId, milestone, actor, now } = options;
|
|
293
|
+
if (!['awaiting_execution_authorization', 'active'].includes(milestone.status) || !milestone.planHash)
|
|
294
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Autonomy binding requires a complete active Milestone Plan.');
|
|
295
|
+
const grant = store.readDelegation(projectId, options.grantId);
|
|
296
|
+
if (grant.scope.kind !== 'milestone' || grant.scope.id !== milestone.id)
|
|
297
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Autonomy binding requires an exact Milestone grant.');
|
|
298
|
+
for (const transition of AUTONOMY_TRANSITIONS) {
|
|
299
|
+
if (transition !== 'project_memory.approve' && !grant.transitions.includes(transition))
|
|
300
|
+
throw new WorkflowError('TRANSITION_BLOCKED', `Autonomy binding requires ${transition}; request grant expansion.`);
|
|
301
|
+
}
|
|
302
|
+
assertDelegationCanAuthorize(grant, 'milestone.execution_authorize', actor, milestone, now);
|
|
303
|
+
const previous = readMilestoneAutonomyEvents(store, projectId, milestone.id).at(-1);
|
|
304
|
+
if (previous) {
|
|
305
|
+
if (previous.delegationGrantId === grant.id) return requireActiveMilestoneAutonomy(options);
|
|
306
|
+
if (previous.semanticScopeHash !== milestoneSemanticScopeHash(milestone))
|
|
307
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Autonomy rebinding cannot change the approved semantic scope.');
|
|
308
|
+
let previousGrant: DelegationGrant | null = null;
|
|
309
|
+
try { previousGrant = store.readDelegation(projectId, previous.delegationGrantId); }
|
|
310
|
+
catch (error) { if (!(error instanceof WorkflowError) || error.code !== 'NOT_FOUND') throw error; }
|
|
311
|
+
if (previousGrant?.status === 'active' && Date.parse(previousGrant.expiresAt) > now.getTime())
|
|
312
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Revoke the existing active grant before replacing its autonomy binding.');
|
|
313
|
+
}
|
|
314
|
+
const contract = store.appendSidecarEvent<MilestoneAutonomyEvent>(
|
|
315
|
+
store.milestoneRoot(projectId, milestone.id), AUTONOMY_SIDECAR, {
|
|
316
|
+
eventId: `MAC-${createUlid(now.getTime())}`, recordedAt: now.toISOString(), eventType: 'issued',
|
|
317
|
+
projectId, milestoneId: milestone.id, milestoneRevision: milestone.revision,
|
|
318
|
+
principal: grant.principal, delegate: grant.delegate, issuedAt: now.toISOString(), expiresAt: grant.expiresAt,
|
|
319
|
+
semanticScopeHash: milestoneSemanticScopeHash(milestone), delegationGrantId: grant.id,
|
|
320
|
+
delegationPolicyHash: grant.policyHash,
|
|
321
|
+
}, { validateEvent: candidate => validateMilestoneAutonomyEvent(candidate, projectId, milestone.id) });
|
|
322
|
+
return { contract, grant };
|
|
323
|
+
}
|
|
324
|
+
|
|
287
325
|
export function requireActiveMilestoneAutonomy(options: {
|
|
288
326
|
store: FileStateStore;
|
|
289
327
|
projectId: string;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
2
|
-
import { existsSync, lstatSync, readFileSync, readdirSync, rmSync } from 'node:fs';
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { existsSync, lstatSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import os from 'node:os';
|
|
5
5
|
import { WorkflowError } from '../errors.js';
|
|
6
|
-
import { ensureDirectory, writeJsonAtomic
|
|
6
|
+
import { ensureDirectory, writeJsonAtomic } from '../fs-utils.js';
|
|
7
7
|
|
|
8
8
|
type TransactionPhase = 'prepared' | 'applying' | 'applied' | 'rolled_back';
|
|
9
9
|
|
|
@@ -311,7 +311,16 @@ function restoreSnapshot(file: string, snapshot: Snapshot): void {
|
|
|
311
311
|
rmSync(file, { force: true });
|
|
312
312
|
return;
|
|
313
313
|
}
|
|
314
|
-
|
|
314
|
+
// Snapshots preserve bytes; text normalization would corrupt newline-less or binary preimages.
|
|
315
|
+
const directory = path.dirname(file);
|
|
316
|
+
ensureDirectory(directory);
|
|
317
|
+
const temporary = path.join(directory, `.${path.basename(file)}.${randomUUID()}.tmp`);
|
|
318
|
+
try {
|
|
319
|
+
writeFileSync(temporary, Buffer.from(snapshot.contentBase64!, 'base64'), { mode: 0o600 });
|
|
320
|
+
renameSync(temporary, file);
|
|
321
|
+
} finally {
|
|
322
|
+
rmSync(temporary, { force: true });
|
|
323
|
+
}
|
|
315
324
|
if (!snapshotsEqual(captureSnapshot(file), snapshot)) {
|
|
316
325
|
throw new WorkflowError('STATE_CORRUPT', `Project transaction snapshot restore failed: ${file}`);
|
|
317
326
|
}
|
package/src/cli-actions.ts
CHANGED
|
@@ -12,6 +12,9 @@ const CLI_ACTIONS_LITERAL = {
|
|
|
12
12
|
'navigation-source-preflight',
|
|
13
13
|
'navigation-transport-preflight',
|
|
14
14
|
'navigation-recover',
|
|
15
|
+
'failed-checkpoint-source-preflight',
|
|
16
|
+
'failed-checkpoint-transport-preflight',
|
|
17
|
+
'failed-checkpoint-recover',
|
|
15
18
|
'dependency-provenance-preflight',
|
|
16
19
|
'dependency-provenance-recover',
|
|
17
20
|
'historical-step-dependency-preflight',
|
|
@@ -51,6 +54,7 @@ const CLI_ACTIONS_LITERAL = {
|
|
|
51
54
|
'corrective-yield',
|
|
52
55
|
'corrective-yield-recover',
|
|
53
56
|
'corrective-replan-prepare',
|
|
57
|
+
'corrective-replan-authorize',
|
|
54
58
|
'corrective-replan-confirm',
|
|
55
59
|
'corrective-replan-execute',
|
|
56
60
|
'corrective-replan-recover',
|
|
@@ -80,6 +84,7 @@ const CLI_ACTIONS_LITERAL = {
|
|
|
80
84
|
'scope-change-apply',
|
|
81
85
|
'autonomy-prepare',
|
|
82
86
|
'autonomy-grant',
|
|
87
|
+
'autonomy-bind',
|
|
83
88
|
'autonomy-evolve',
|
|
84
89
|
'authorize',
|
|
85
90
|
'validate',
|