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
package/src/cli.ts
CHANGED
|
@@ -70,6 +70,9 @@ interface CliHelpSpec {
|
|
|
70
70
|
|
|
71
71
|
const CLI_HELP_SPECS: Record<string, CliHelpSpec> = {
|
|
72
72
|
'update navigation-source-preflight': { requiredOptions: ['--id', '--tarball'], optionalOptions: ['--repo', '--json'] },
|
|
73
|
+
'update failed-checkpoint-source-preflight': { requiredOptions: ['--id', '--tarball'], optionalOptions: ['--repo', '--json'] },
|
|
74
|
+
'update failed-checkpoint-transport-preflight': { requiredOptions: ['--id', '--tarball', '--file'], optionalOptions: ['--repo', '--json'] },
|
|
75
|
+
'update failed-checkpoint-recover': { requiredOptions: ['--id', '--tarball', '--file', '--expected-revision', '--actor', '--reason'], optionalOptions: ['--repo', '--json'] },
|
|
73
76
|
'update navigation-transport-preflight': { requiredOptions: ['--id', '--tarball', '--file'], optionalOptions: ['--repo', '--json'] },
|
|
74
77
|
'update navigation-recover': { requiredOptions: ['--id', '--tarball', '--file', '--expected-revision', '--actor', '--reason'], optionalOptions: ['--repo', '--json'] },
|
|
75
78
|
'change explain': { requiredOptions: ['--task', '--file'], optionalOptions: ['--repo', '--json'] },
|
|
@@ -113,6 +116,14 @@ const CLI_HELP_SPECS: Record<string, CliHelpSpec> = {
|
|
|
113
116
|
requiredOptions: ['--id'],
|
|
114
117
|
optionalOptions: ['--repo', '--json'],
|
|
115
118
|
},
|
|
119
|
+
'state adoption-apply': {
|
|
120
|
+
requiredOptions: ['--actor', '--confirmation-code'],
|
|
121
|
+
optionalOptions: ['--delegation-grant', '--repo', '--json'],
|
|
122
|
+
},
|
|
123
|
+
'milestone autonomy-bind': {
|
|
124
|
+
requiredOptions: ['--id', '--expected-revision', '--actor', '--delegation-grant'],
|
|
125
|
+
optionalOptions: ['--repo', '--json'],
|
|
126
|
+
},
|
|
116
127
|
'milestone authorize': {
|
|
117
128
|
requiredOptions: ['--id', '--expected-revision'],
|
|
118
129
|
optionalOptions: ['--actor', '--reason', '--delegation-grant', '--repo', '--json'],
|
|
@@ -172,6 +183,10 @@ const CLI_HELP_SPECS: Record<string, CliHelpSpec> = {
|
|
|
172
183
|
requiredOptions: ['--id', '--transaction-id'],
|
|
173
184
|
optionalOptions: ['--repo', '--json'],
|
|
174
185
|
},
|
|
186
|
+
'task corrective-replan-authorize': {
|
|
187
|
+
requiredOptions: ['--id', '--file'],
|
|
188
|
+
optionalOptions: ['--repo', '--json'],
|
|
189
|
+
},
|
|
175
190
|
'task corrective-replan-prepare': {
|
|
176
191
|
requiredOptions: ['--id', '--file'],
|
|
177
192
|
optionalOptions: ['--repo', '--json'],
|
|
@@ -567,6 +582,16 @@ async function dispatch(args: ParsedArguments): Promise<unknown> {
|
|
|
567
582
|
if (noun === 'update' && action === 'navigation-source-preflight') {
|
|
568
583
|
return workflow.navigationUpdateSourcePreflight(repo, required(args, 'id'), required(args, 'tarball'));
|
|
569
584
|
}
|
|
585
|
+
if (noun === 'update' && action === 'failed-checkpoint-source-preflight') {
|
|
586
|
+
return workflow.failedCheckpointUpdateSourcePreflight(repo, required(args, 'id'), required(args, 'tarball'));
|
|
587
|
+
}
|
|
588
|
+
if (noun === 'update' && action === 'failed-checkpoint-transport-preflight') {
|
|
589
|
+
return workflow.failedCheckpointUpdateTransportPreflight(repo, required(args, 'id'), required(args, 'tarball'), jsonFile(args));
|
|
590
|
+
}
|
|
591
|
+
if (noun === 'update' && action === 'failed-checkpoint-recover') {
|
|
592
|
+
return workflow.recoverFailedCheckpointUpdate(repo, required(args, 'id'), revision(args), required(args, 'tarball'),
|
|
593
|
+
jsonFile(args), required(args, 'actor'), required(args, 'reason'));
|
|
594
|
+
}
|
|
570
595
|
if (noun === 'update' && action === 'navigation-transport-preflight') {
|
|
571
596
|
return workflow.navigationUpdateTransportPreflight(repo, required(args, 'id'), required(args, 'tarball'), jsonFile(args));
|
|
572
597
|
}
|
|
@@ -685,6 +710,7 @@ async function dispatch(args: ParsedArguments): Promise<unknown> {
|
|
|
685
710
|
repo,
|
|
686
711
|
required(args, 'actor'),
|
|
687
712
|
required(args, 'confirmation-code'),
|
|
713
|
+
option(args, 'delegation-grant') ?? null,
|
|
688
714
|
);
|
|
689
715
|
}
|
|
690
716
|
if (noun === 'milestone' && action === 'recover') {
|
|
@@ -1087,6 +1113,9 @@ async function dispatch(args: ParsedArguments): Promise<unknown> {
|
|
|
1087
1113
|
if (noun === 'task' && action === 'corrective-yield-recover') {
|
|
1088
1114
|
return workflow.recoverCorrectiveYield(repo, required(args, 'id'), required(args, 'transaction-id'));
|
|
1089
1115
|
}
|
|
1116
|
+
if (noun === 'task' && action === 'corrective-replan-authorize') {
|
|
1117
|
+
return workflow.authorizeDelegatedCorrectiveReplan(repo, required(args, 'id'), jsonFile<CorrectiveReplanPublicCandidate>(args));
|
|
1118
|
+
}
|
|
1090
1119
|
if (noun === 'task' && action === 'corrective-replan-prepare') {
|
|
1091
1120
|
return workflow.prepareCorrectiveReplan(
|
|
1092
1121
|
repo,
|
|
@@ -1339,6 +1368,10 @@ async function dispatch(args: ParsedArguments): Promise<unknown> {
|
|
|
1339
1368
|
required(args, 'actor'),
|
|
1340
1369
|
);
|
|
1341
1370
|
}
|
|
1371
|
+
if (noun === 'milestone' && action === 'autonomy-bind') {
|
|
1372
|
+
return workflow.bindMilestoneAutonomy(repo, required(args, 'id'), revision(args),
|
|
1373
|
+
required(args, 'actor'), required(args, 'delegation-grant'));
|
|
1374
|
+
}
|
|
1342
1375
|
if (noun === 'milestone' && action === 'authorize') {
|
|
1343
1376
|
return workflow.authorizeMilestone(
|
|
1344
1377
|
repo,
|
package/src/contracts.ts
CHANGED
|
@@ -39,6 +39,7 @@ export type AuthorizationKind = 'execution' | 'final_acceptance' | 'exception';
|
|
|
39
39
|
export type AuthorizationMode = 'human' | 'delegated' | 'corrective-derived';
|
|
40
40
|
export type DelegationStatus = 'active' | 'revoked';
|
|
41
41
|
export type DelegationTransition =
|
|
42
|
+
| 'project.adopt'
|
|
42
43
|
| 'project_memory.approve'
|
|
43
44
|
| 'task.execution_authorize'
|
|
44
45
|
| 'task.final_accept'
|
|
@@ -734,8 +735,23 @@ export interface CorrectiveDecisionEvent extends SidecarEventRecord {
|
|
|
734
735
|
coveredFindingIds: string[];
|
|
735
736
|
coveredEventIds: string[];
|
|
736
737
|
planHash: string;
|
|
737
|
-
recoveryBasis?: 'plan-integrity';
|
|
738
|
+
recoveryBasis?: 'plan-integrity' | 'planning-obstruction';
|
|
738
739
|
planIntegrityEvidence?: PlanIntegrityConflictEvidence;
|
|
740
|
+
planningObstructionEvidence?: {
|
|
741
|
+
kind: 'first-failure-rebound-planning-obstruction';
|
|
742
|
+
taskRevision: number;
|
|
743
|
+
sourcePlanHash: string;
|
|
744
|
+
baselinePlanHash: string;
|
|
745
|
+
baselineAuditEventId: string;
|
|
746
|
+
baselineAuditEventHash: string;
|
|
747
|
+
obstructionAuditEventId: string;
|
|
748
|
+
obstructionAuditEventHash: string;
|
|
749
|
+
priorAuthorizationHash: string;
|
|
750
|
+
remediationEventId: string;
|
|
751
|
+
remediationEventHash: string;
|
|
752
|
+
knowledgeRebindCount: number;
|
|
753
|
+
knowledgeRebindHash: string;
|
|
754
|
+
};
|
|
739
755
|
}
|
|
740
756
|
|
|
741
757
|
export interface CorrectiveDecisionRecoveryEvent extends SidecarEventRecord {
|
|
@@ -935,6 +951,7 @@ export interface AdoptionRepositoryIdentity {
|
|
|
935
951
|
}
|
|
936
952
|
|
|
937
953
|
export interface AdoptionPostureEvent extends SidecarEventRecord {
|
|
954
|
+
delegatedAuthorization?: DelegatedAuthorizationEvidence;
|
|
938
955
|
projectId: string;
|
|
939
956
|
eventType: AdoptionPostureEventType;
|
|
940
957
|
actor: string | null;
|
|
@@ -1175,6 +1192,7 @@ export interface DependencyProvenanceRecoveryRecord {
|
|
|
1175
1192
|
historicalStepProvenance?: HistoricalStepDependencyCompatibility;
|
|
1176
1193
|
planIntegrity?: PlanIntegrityUpdateCompatibility;
|
|
1177
1194
|
rootCauseReplan?: RootCauseReplanDirtyCarryoverCompatibility;
|
|
1195
|
+
failedCheckpoint?: import('./failed-checkpoint-update.js').FailedCheckpointUpdateBinding;
|
|
1178
1196
|
}
|
|
1179
1197
|
|
|
1180
1198
|
export interface RootCauseReplanDirtyCarryoverCompatibility {
|
package/src/delegation.ts
CHANGED
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
import { WorkflowError } from './errors.js';
|
|
13
13
|
|
|
14
14
|
const TRANSITIONS: readonly DelegationTransition[] = [
|
|
15
|
+
'project.adopt',
|
|
15
16
|
'project_memory.approve',
|
|
16
17
|
'task.execution_authorize',
|
|
17
18
|
'task.final_accept',
|
|
@@ -19,7 +20,7 @@ const TRANSITIONS: readonly DelegationTransition[] = [
|
|
|
19
20
|
'milestone.final_accept',
|
|
20
21
|
];
|
|
21
22
|
|
|
22
|
-
type DelegationTarget = TaskState | MilestoneState | ProjectKnowledgeMap;
|
|
23
|
+
type DelegationTarget = TaskState | MilestoneState | ProjectKnowledgeMap | { kind: 'project'; id: string; milestoneId?: null };
|
|
23
24
|
|
|
24
25
|
export function prepareDelegationGrantGate(
|
|
25
26
|
projectId: string,
|
|
@@ -64,6 +65,9 @@ export function normalizeDelegationPolicy(
|
|
|
64
65
|
});
|
|
65
66
|
}
|
|
66
67
|
transitions.sort();
|
|
68
|
+
if (transitions.includes('project.adopt') && scope.kind !== 'project') {
|
|
69
|
+
throw new WorkflowError('INVALID_ARGUMENT', 'project.adopt requires project scope.');
|
|
70
|
+
}
|
|
67
71
|
if (transitions.includes('project_memory.approve') && scope.kind !== 'project') {
|
|
68
72
|
throw new WorkflowError(
|
|
69
73
|
'INVALID_ARGUMENT',
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { existsSync, lstatSync, readFileSync, readdirSync } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import type { TaskState } from './contracts.js';
|
|
5
|
+
import type { FileStateStore } from './state/store.js';
|
|
6
|
+
import { canonicalJsonStringify, sha256Hex } from './alpha6/store-sidecars.js';
|
|
7
|
+
import { readCorrectiveDecisionEvents, readRemediationEvents } from './alpha6/remediation.js';
|
|
8
|
+
import { readTaskC1Posture, readTaskHandoffEvents } from './alpha6/handoff.js';
|
|
9
|
+
import { readPlanRiskAuditEvents } from './alpha6/plan-risk.js';
|
|
10
|
+
import { verifiedHistoricalStepReviewBindings } from './alpha6/review.js';
|
|
11
|
+
import { readVerifiedPlanArtifact } from './domain/plan-semantics.js';
|
|
12
|
+
import { pathAllowed } from './domain/validation.js';
|
|
13
|
+
import { validateTaskHistory } from './git.js';
|
|
14
|
+
import { inspectBoundedWorkflowDependencyCommit, workflowDependencyVersionAt } from './dependency-provenance.js';
|
|
15
|
+
import { inspectWorkflowVersions } from './diagnostics.js';
|
|
16
|
+
import { changedFiles, currentBranch, headCommit, runGit } from './repository.js';
|
|
17
|
+
import { PACKAGE_VERSION } from './version.js';
|
|
18
|
+
|
|
19
|
+
export const FAILED_CHECKPOINT_SOURCE = '2.0.0-beta.14';
|
|
20
|
+
export const FAILED_CHECKPOINT_TARGET = '2.0.0-beta.14.1';
|
|
21
|
+
const hash = (value: unknown) => sha256Hex(canonicalJsonStringify(value));
|
|
22
|
+
function require(condition: unknown, message: string): asserts condition {
|
|
23
|
+
if (!condition) throw new Error(`FAILED_CHECKPOINT_UPDATE_BLOCKED: ${message}`);
|
|
24
|
+
}
|
|
25
|
+
export interface FailedCheckpointUpdateReceipt {
|
|
26
|
+
protocol: 'failed-checkpoint-update-source-v1';
|
|
27
|
+
projectId: string; taskId: string; revision: number; issuedAt: string;
|
|
28
|
+
taskHash: string; artifactsHash: string; knowledgeHash: string; milestoneHash: string | null;
|
|
29
|
+
sourceHead: string; sourceBaseHead: string; taskBranch: string; baseBranch: string;
|
|
30
|
+
sourceVersion: string; targetVersion: string; actor: string; stepId: string;
|
|
31
|
+
dirtyFiles: string[]; sourceDirtyHash: string; remediationEventId: string; remediationEventHash: string;
|
|
32
|
+
artifact: { sha256: string; runtimeHash: string };
|
|
33
|
+
}
|
|
34
|
+
export interface FailedCheckpointUpdateBinding {
|
|
35
|
+
protocol: 'failed-checkpoint-update-v1';
|
|
36
|
+
sourceHead: string; sourcePlanHash: string; stepId: string;
|
|
37
|
+
sourceDirtyHash: string; targetDirtyHash: string; dirtyFiles: string[];
|
|
38
|
+
remediationEventId: string; remediationEventHash: string;
|
|
39
|
+
sourceReceiptHash: string; artifactHash: string;
|
|
40
|
+
}
|
|
41
|
+
function filesHash(root: string): string {
|
|
42
|
+
if (!existsSync(root)) return hash([]);
|
|
43
|
+
const visit = (directory: string): Array<[string, string]> => readdirSync(directory).sort().flatMap(name => {
|
|
44
|
+
const file = path.join(directory, name); const info = lstatSync(file);
|
|
45
|
+
require(!info.isSymbolicLink() && (info.isFile() || info.isDirectory()), 'State artifacts must be regular files.');
|
|
46
|
+
return info.isDirectory() ? visit(file) : [[path.relative(root, file), sha256Hex(readFileSync(file))]];
|
|
47
|
+
});
|
|
48
|
+
return hash(visit(root));
|
|
49
|
+
}
|
|
50
|
+
/** Reverse only the dependency string, retaining every other byte (including whitespace and product scripts). */
|
|
51
|
+
function sourceManifestBytes(content: Buffer): Buffer {
|
|
52
|
+
const text = content.toString('utf8'); const manifest = JSON.parse(text);
|
|
53
|
+
require(manifest.devDependencies?.['codex-workflow-v2'] === FAILED_CHECKPOINT_TARGET
|
|
54
|
+
&& !manifest.dependencies?.['codex-workflow-v2'], 'Target working manifest must have one exact dev dependency.');
|
|
55
|
+
const key = /("codex-workflow-v2"\s*:\s*)"2\.0\.0-beta\.14\.1"/g;
|
|
56
|
+
require([...text.matchAll(key)].length === 1, 'Working manifest must have one unambiguous literal target pin.');
|
|
57
|
+
return Buffer.from(text.replace(key, `$1"${FAILED_CHECKPOINT_SOURCE}"`));
|
|
58
|
+
}
|
|
59
|
+
/** Same byte framing as native failure evidence; no normalization of product content. */
|
|
60
|
+
export function failedCheckpointDirtyHash(repository: string, files: readonly string[], transported = false): string {
|
|
61
|
+
const digest = createHash('sha256');
|
|
62
|
+
for (const file of [...files].sort()) {
|
|
63
|
+
const absolute = path.resolve(repository, file); const relative = path.relative(repository, absolute);
|
|
64
|
+
require(relative && !relative.startsWith('..') && !path.isAbsolute(relative), 'Dirty path escapes checkout.');
|
|
65
|
+
require(existsSync(absolute) && lstatSync(absolute).isFile() && !lstatSync(absolute).isSymbolicLink(),
|
|
66
|
+
'This checkpoint supports retained regular files only.');
|
|
67
|
+
let bytes: Buffer = readFileSync(absolute);
|
|
68
|
+
if (transported && file === 'package.json') bytes = sourceManifestBytes(bytes);
|
|
69
|
+
digest.update(`path\0${file}\0file\0`); digest.update(bytes); digest.update('\0');
|
|
70
|
+
}
|
|
71
|
+
return digest.digest('hex');
|
|
72
|
+
}
|
|
73
|
+
function assertPins(repository: string, commit: string, version: string): void {
|
|
74
|
+
const manifest = JSON.parse(runGit(repository, ['show', `${commit}:package.json`]));
|
|
75
|
+
const lock = JSON.parse(runGit(repository, ['show', `${commit}:package-lock.json`]));
|
|
76
|
+
require(manifest.devDependencies?.['codex-workflow-v2'] === version && !manifest.dependencies?.['codex-workflow-v2']
|
|
77
|
+
&& workflowDependencyVersionAt(repository, commit) === version
|
|
78
|
+
&& lock.packages?.['']?.devDependencies?.['codex-workflow-v2'] === version
|
|
79
|
+
&& !lock.packages?.['']?.dependencies?.['codex-workflow-v2']
|
|
80
|
+
&& lock.packages?.['node_modules/codex-workflow-v2']?.version === version,
|
|
81
|
+
'Exact source manifest and lock bindings are required.');
|
|
82
|
+
}
|
|
83
|
+
export function assertFailedCheckpointInstalled(repository: string, task: TaskState, version: string): void {
|
|
84
|
+
const versions = inspectWorkflowVersions(repository, [task.baseBranch]);
|
|
85
|
+
require([versions.declared, versions.locked, versions.installed, versions.currentBranch,
|
|
86
|
+
...versions.milestoneBases.map(item => item.version)].every(value => value === version),
|
|
87
|
+
`Every installed and Git version surface must equal ${version}.`);
|
|
88
|
+
}
|
|
89
|
+
export function failedCheckpointUpdateSource(repository: string, store: FileStateStore, task: TaskState,
|
|
90
|
+
artifact: FailedCheckpointUpdateReceipt['artifact'],
|
|
91
|
+
sourceHead = headCommit(repository), sourceBaseHead = runGit(repository, ['rev-parse', task.baseBranch]),
|
|
92
|
+
transported = false): FailedCheckpointUpdateReceipt {
|
|
93
|
+
require(PACKAGE_VERSION === FAILED_CHECKPOINT_TARGET, 'Only the beta.14 to beta.14.1 repair is supported.');
|
|
94
|
+
require(task.status === 'awaiting_execution_authorization' && task.workspaceOwner === 'external',
|
|
95
|
+
'Expected an external failed Task awaiting its new Plan audit.');
|
|
96
|
+
require(readCorrectiveDecisionEvents(store, task).length === 0,
|
|
97
|
+
'A checkpoint with corrective decision history requires its own supported boundary.');
|
|
98
|
+
require(task.taskBranch && currentBranch(repository) === task.taskBranch, 'Expected the recorded Task branch.');
|
|
99
|
+
const failed = task.steps.filter(step => step.status === 'failed');
|
|
100
|
+
require(failed.length === 1 && !task.steps.some(step => step.status === 'in_progress'), 'Exactly one paused failed Step is required.');
|
|
101
|
+
const step = failed[0]!;
|
|
102
|
+
require(step.failurePause && step.evidence === null, 'Native failed-check evidence is required.');
|
|
103
|
+
require(task.planHash && task.briefHash, 'Current Brief and Plan are required.');
|
|
104
|
+
const taskRoot = store.taskRoot(task.projectId, task.id);
|
|
105
|
+
require(store.hashArtifact(taskRoot, 'brief.md') === task.briefHash && store.hashArtifact(taskRoot, 'plan.md') === task.planHash,
|
|
106
|
+
'Brief and Plan artifacts must match state.');
|
|
107
|
+
readVerifiedPlanArtifact(taskRoot, task.planHash, task.planHash);
|
|
108
|
+
const currentAudit = readPlanRiskAuditEvents(store, task).at(-1);
|
|
109
|
+
require(!currentAudit || currentAudit.planHash !== task.planHash
|
|
110
|
+
|| currentAudit.decision === 'stop-escalate', 'The checkpoint must await its audit or retain a planning obstruction.');
|
|
111
|
+
assertPins(repository, sourceHead, FAILED_CHECKPOINT_SOURCE); assertPins(repository, sourceBaseHead, FAILED_CHECKPOINT_SOURCE);
|
|
112
|
+
validateTaskHistory(repository, task, sourceHead);
|
|
113
|
+
const completed = task.steps.filter(item => item.status === 'completed');
|
|
114
|
+
const reviews = verifiedHistoricalStepReviewBindings(store, task);
|
|
115
|
+
require(completed.length > 0 && completed.every(item => item.evidence
|
|
116
|
+
&& reviews.some(review => review.stepId === item.id && review.commitSha === item.evidence!.commitSha)),
|
|
117
|
+
'Every retained completed Step must have immutable execution and independent review evidence.');
|
|
118
|
+
require(step.dependencies.every(id => completed.some(item => item.id === id)), 'Failed Step dependencies must be complete.');
|
|
119
|
+
const events = readRemediationEvents(store, task).filter(event => event.stepId === step.id && event.failureKind === 'checks-failed');
|
|
120
|
+
require(events.length === 1, 'This checkpoint supports exactly the first native check failure.');
|
|
121
|
+
const terminal = events.at(-1);
|
|
122
|
+
require(terminal && terminal.failureEvidence.completionCommit === sourceHead
|
|
123
|
+
&& terminal.failureEvidence.dirtyWorktreeHash === step.failurePause.dirtyWorktreeHash,
|
|
124
|
+
'Native terminal failure must bind source HEAD and retained dirty bytes.');
|
|
125
|
+
const dirtyFiles = changedFiles(repository).sort();
|
|
126
|
+
require(dirtyFiles.length > 0 && !dirtyFiles.includes('package-lock.json')
|
|
127
|
+
&& JSON.stringify(dirtyFiles) === JSON.stringify([...step.failurePause.dirtyFiles].sort()),
|
|
128
|
+
'Dirty paths must equal the native failure; dirty lockfile transport is unsupported.');
|
|
129
|
+
require(dirtyFiles.every(file => pathAllowed(file, step.allowedWrites) && !pathAllowed(file, step.forbiddenScope)),
|
|
130
|
+
'Retained dirty files must be inside the failed Step scope.');
|
|
131
|
+
const sourceDirtyHash = failedCheckpointDirtyHash(repository, dirtyFiles, transported);
|
|
132
|
+
require(sourceDirtyHash === terminal.failureEvidence.dirtyWorktreeHash, 'Product bytes changed since native failure.');
|
|
133
|
+
const posture = readTaskC1Posture(store, task);
|
|
134
|
+
const claim = readTaskHandoffEvents(store, task).filter(event => event.eventType === 'claim').at(-1);
|
|
135
|
+
require(posture.state === 'claimed' && claim && posture.claimant === claim.actor && claim.briefHash === task.briefHash,
|
|
136
|
+
'Original immutable Worker claim is required.');
|
|
137
|
+
return { protocol: 'failed-checkpoint-update-source-v1', projectId: task.projectId, taskId: task.id,
|
|
138
|
+
revision: task.revision, issuedAt: task.updatedAt, taskHash: hash(task), artifactsHash: filesHash(taskRoot),
|
|
139
|
+
knowledgeHash: hash(store.readKnowledgeMap(task.projectId)),
|
|
140
|
+
milestoneHash: task.milestoneId ? hash(store.readMilestone(task.projectId, task.milestoneId)) : null,
|
|
141
|
+
sourceHead, sourceBaseHead, taskBranch: task.taskBranch, baseBranch: task.baseBranch,
|
|
142
|
+
sourceVersion: FAILED_CHECKPOINT_SOURCE, targetVersion: FAILED_CHECKPOINT_TARGET, actor: claim.actor,
|
|
143
|
+
stepId: step.id, dirtyFiles, sourceDirtyHash, remediationEventId: terminal.eventId,
|
|
144
|
+
remediationEventHash: terminal.eventHash, artifact };
|
|
145
|
+
}
|
|
146
|
+
export function inspectFailedCheckpointTransport(repository: string, store: FileStateStore, task: TaskState,
|
|
147
|
+
receipt: FailedCheckpointUpdateReceipt, artifact: FailedCheckpointUpdateReceipt['artifact']) {
|
|
148
|
+
require(receipt?.protocol === 'failed-checkpoint-update-source-v1', 'Missing or foreign source receipt.');
|
|
149
|
+
const targetHead = headCommit(repository); const base = runGit(repository, ['rev-parse', task.baseBranch]);
|
|
150
|
+
const target = inspectBoundedWorkflowDependencyCommit(repository, targetHead);
|
|
151
|
+
const baseTransport = inspectBoundedWorkflowDependencyCommit(repository, base);
|
|
152
|
+
require([targetHead, base].every(commit => runGit(repository, ['rev-list', '--parents', '-n', '1', commit]).split(' ').length === 2),
|
|
153
|
+
'Transport commits must have exactly one parent.');
|
|
154
|
+
require(target?.parentCommitSha === receipt.sourceHead && target.packageVersion === FAILED_CHECKPOINT_TARGET
|
|
155
|
+
&& baseTransport?.parentCommitSha === receipt.sourceBaseHead && baseTransport.packageVersion === FAILED_CHECKPOINT_TARGET,
|
|
156
|
+
'Exactly one dependency-only transport is required on each bound branch.');
|
|
157
|
+
const reconstructed = failedCheckpointUpdateSource(repository, store, task, artifact,
|
|
158
|
+
target.parentCommitSha, baseTransport.parentCommitSha, true);
|
|
159
|
+
require(canonicalJsonStringify(receipt) === canonicalJsonStringify(reconstructed),
|
|
160
|
+
'Source receipt no longer matches state, artifacts, product bytes, history or tarball.');
|
|
161
|
+
assertFailedCheckpointInstalled(repository, task, FAILED_CHECKPOINT_TARGET);
|
|
162
|
+
const binding: FailedCheckpointUpdateBinding = { protocol: 'failed-checkpoint-update-v1',
|
|
163
|
+
sourceHead: receipt.sourceHead, sourcePlanHash: task.planHash!, stepId: receipt.stepId,
|
|
164
|
+
sourceDirtyHash: receipt.sourceDirtyHash, targetDirtyHash: failedCheckpointDirtyHash(repository, receipt.dirtyFiles),
|
|
165
|
+
dirtyFiles: receipt.dirtyFiles, remediationEventId: receipt.remediationEventId,
|
|
166
|
+
remediationEventHash: receipt.remediationEventHash, sourceReceiptHash: hash(receipt), artifactHash: artifact.sha256 };
|
|
167
|
+
return { receipt: reconstructed, target, baseTransport: base, targetHead, binding };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Revalidate the registered byte-preserving transport when a later corrective gate reads native failure evidence.
|
|
171
|
+
* Returns no authority and never changes the original failure record or its hash. */
|
|
172
|
+
export function readFailedCheckpointDirtyTransport(store: FileStateStore, repository: string, task: TaskState,
|
|
173
|
+
stepId: string, sourceHead: string, sourceDirtyHash: string) {
|
|
174
|
+
const records = (task.dependencyProvenanceRecoveries ?? []).filter(record => {
|
|
175
|
+
const binding = (record as typeof record & { failedCheckpoint?: FailedCheckpointUpdateBinding }).failedCheckpoint;
|
|
176
|
+
return binding?.stepId === stepId && binding.sourceHead === sourceHead && binding.sourceDirtyHash === sourceDirtyHash;
|
|
177
|
+
});
|
|
178
|
+
if (records.length === 0) return null;
|
|
179
|
+
require(records.length === 1, 'Registered failed checkpoint is ambiguous.');
|
|
180
|
+
const record = records[0]!;
|
|
181
|
+
const binding = (record as typeof record & { failedCheckpoint: FailedCheckpointUpdateBinding }).failedCheckpoint;
|
|
182
|
+
require(task.planHash, 'Current Plan is required to validate the historical checkpoint.');
|
|
183
|
+
readVerifiedPlanArtifact(store.taskRoot(task.projectId, task.id), binding.sourcePlanHash, task.planHash);
|
|
184
|
+
require(binding.protocol === 'failed-checkpoint-update-v1' && task.systemCommits.includes(record.commitSha)
|
|
185
|
+
&& record.packageVersion === FAILED_CHECKPOINT_TARGET && record.packageName === 'codex-workflow-v2'
|
|
186
|
+
&& record.parentCommitSha === sourceHead && JSON.stringify(record.files) === JSON.stringify(['package-lock.json','package.json']),
|
|
187
|
+
'Registered failed checkpoint dependency record is invalid.');
|
|
188
|
+
const inspected = inspectBoundedWorkflowDependencyCommit(repository, record.commitSha);
|
|
189
|
+
require(inspected?.parentCommitSha === sourceHead && inspected.packageVersion === FAILED_CHECKPOINT_TARGET,
|
|
190
|
+
'Registered failed checkpoint no longer identifies a dependency-only commit.');
|
|
191
|
+
require(headCommit(repository) === record.commitSha, 'Dirty checkpoint requires its exact transported HEAD.');
|
|
192
|
+
const terminal = readRemediationEvents(store, task).filter(event => event.stepId === stepId && event.failureKind === 'checks-failed').at(-1);
|
|
193
|
+
require(terminal?.eventId === binding.remediationEventId && terminal.eventHash === binding.remediationEventHash
|
|
194
|
+
&& terminal.failureEvidence.completionCommit === sourceHead && terminal.failureEvidence.dirtyWorktreeHash === sourceDirtyHash,
|
|
195
|
+
'Registered failed checkpoint does not bind the terminal native failure.');
|
|
196
|
+
const dirty = changedFiles(repository).sort();
|
|
197
|
+
require(JSON.stringify(dirty) === JSON.stringify(binding.dirtyFiles)
|
|
198
|
+
&& failedCheckpointDirtyHash(repository, dirty, true) === sourceDirtyHash
|
|
199
|
+
&& failedCheckpointDirtyHash(repository, dirty) === binding.targetDirtyHash,
|
|
200
|
+
'Retained product bytes changed after the registered dependency transport.');
|
|
201
|
+
return { sourceHead, sourceDirtyHash, targetHead: record.commitSha, targetDirtyHash: binding.targetDirtyHash,
|
|
202
|
+
dirtyFiles: [...dirty], sourceReceiptHash: binding.sourceReceiptHash };
|
|
203
|
+
}
|
|
@@ -28,7 +28,7 @@ export interface CorrectiveReplanCredentialCorePorts {
|
|
|
28
28
|
readonly validationEventTailSource: { readPlanValidationTail(taskId: string): unknown };
|
|
29
29
|
}
|
|
30
30
|
export interface CorrectiveReplanCredentialKernel {
|
|
31
|
-
issuePlanValidation(input: unknown): Result<JsonRecord>; issueHumanChallenge(input: unknown): Result<JsonRecord>; confirmHuman(input: unknown): Result<JsonRecord>; issueReceipt(input: unknown): Result<JsonRecord>;
|
|
31
|
+
issuePlanValidation(input: unknown): Result<JsonRecord>; issueHumanChallenge(input: unknown): Result<JsonRecord>; issueDelegatedChallenge(input: unknown): Result<JsonRecord>; confirmHuman(input: unknown): Result<JsonRecord>; confirmDelegated(input: unknown): Result<JsonRecord>; issueReceipt(input: unknown): Result<JsonRecord>;
|
|
32
32
|
verifyReceipt(input: unknown): Verification<JsonRecord>; materializeStoredEvent(input: unknown): Result<JsonRecord>; verifyStoredEvent(input: unknown): Verification<JsonRecord>;
|
|
33
33
|
}
|
|
34
34
|
export interface CredentialFunctionBinding { readonly id: string; readonly version: 1; readonly kind: 'materializer' | 'verifier';
|
|
@@ -66,6 +66,8 @@ export function createCorrectiveReplanCredentialKernel(ports: CorrectiveReplanCr
|
|
|
66
66
|
return deepFreeze({
|
|
67
67
|
issuePlanValidation: (input: unknown) => totalResult(() => issuePlanValidation(issuer, input)),
|
|
68
68
|
issueHumanChallenge: (input: unknown) => totalResult(() => issueHumanChallenge(issuer, input)),
|
|
69
|
+
issueDelegatedChallenge: (input: unknown) => totalResult(() => issueDelegatedChallenge(issuer, input)),
|
|
70
|
+
confirmDelegated: (input: unknown) => totalResult(() => confirmDelegated(issuer, input)),
|
|
69
71
|
confirmHuman: (input: unknown) => totalResult(() => confirmHuman(issuer, input)),
|
|
70
72
|
issueReceipt: (input: unknown) => totalResult(() => issueReceipt(issuer, input)),
|
|
71
73
|
verifyReceipt: (input: unknown) => totalVerification(() => verifyReceipt(issuer, input)),
|
|
@@ -110,10 +112,11 @@ const issuePlanValidation = (issuer: unknown, input: unknown): Result<JsonRecord
|
|
|
110
112
|
passed: true, findings: sortedFindings, dispositions: sortedDispositions, validatedAtMs: now, expiresAtMs: now + TTL };
|
|
111
113
|
const planValidationHash = hashCanonical(validation); const values: JsonRecord = { ...clone(base), planValidationHash };
|
|
112
114
|
if (!canonicalManifestValues(values, true)) return rejected('credential.binding.values-invalid', '/binding/values', 'binding');
|
|
115
|
+
const delegated = snapshot.delegatedAuthority;
|
|
113
116
|
const capability = { id: 'capability.task.corrective-replan', version: 1,
|
|
114
|
-
issuer: { kind: 'human', identityHash: values.humanPrincipalIdentityHash }, source: 'human-confirmation',
|
|
117
|
+
issuer: { kind: delegated ? 'delegate' : 'human', identityHash: delegated ? values.actorIdentityHash : values.humanPrincipalIdentityHash }, source: delegated ? 'delegation-grant' : 'human-confirmation',
|
|
115
118
|
scope: { transitionId: values.transitionId, taskId: values.taskId, candidatePlanHash: values.candidatePlanHash,
|
|
116
|
-
manifestSha256: correctiveReplanBindingManifest.manifestSha256 }, delegation: 'forbidden' };
|
|
119
|
+
manifestSha256: correctiveReplanBindingManifest.manifestSha256 }, delegation: delegated ?? 'forbidden' };
|
|
117
120
|
const bindingBase = { schemaVersion: 1, manifestRef: { id: correctiveReplanBindingManifest.id, version: 1 },
|
|
118
121
|
manifestSha256: correctiveReplanBindingManifest.manifestSha256, values, capability };
|
|
119
122
|
const binding = { ...bindingBase, bindingSha256: hashCanonical(bindingBase) };
|
|
@@ -132,11 +135,12 @@ const issuePlanValidation = (issuer: unknown, input: unknown): Result<JsonRecord
|
|
|
132
135
|
return accepted({ handle: handle as PlanValidationHandle, binding, attestation });
|
|
133
136
|
} finally { if (!committed && kernel.preparations.get(preparationKey)?.generation === generation) kernel.preparations.delete(preparationKey); }
|
|
134
137
|
};
|
|
135
|
-
const issueHumanChallenge = (issuer: unknown, input: unknown): Result<JsonRecord> => {
|
|
138
|
+
const issueHumanChallenge = (issuer: unknown, input: unknown, delegated = false): Result<JsonRecord> => {
|
|
136
139
|
const kernel = kernels.get(object(issuer)); if (!kernel) return rejected('credential.provenance.invalid', '/', 'provenance');
|
|
137
140
|
if (!exactKeys(input, ['planValidation'])) return rejected('credential.envelope.invalid', '/', 'schema');
|
|
138
141
|
const handle = object((input as JsonRecord).planValidation); const plan = kernel.plans.get(handle);
|
|
139
142
|
if (!plan) return rejected('credential.provenance.invalid', '/planValidation', 'provenance');
|
|
143
|
+
if (Boolean(plan.snapshot.delegatedAuthority) !== delegated) return rejected('credential.authority.mode-mismatch', '/planValidation', 'authority');
|
|
140
144
|
if (!activePreparation(kernel, plan)) return rejected('credential.preparation.superseded', '/planValidation', 'provenance');
|
|
141
145
|
if (kernel.challenged.has(handle)) return rejected('credential.provenance.invalid', '/planValidation', 'provenance');
|
|
142
146
|
const now = safeTime(kernel.ports.now()); if (now === null) return rejected('credential.time.invalid', '/coreClock', 'time');
|
|
@@ -151,8 +155,11 @@ const issueHumanChallenge = (issuer: unknown, input: unknown): Result<JsonRecord
|
|
|
151
155
|
const challenge = deepFreeze({ ...core, challengeHash: hashCanonical(core) }); const challengeHandle = opaque();
|
|
152
156
|
kernel.challenges.set(challengeHandle, deepFreeze({ plan, challenge })); kernel.challenged.add(handle);
|
|
153
157
|
return accepted({ handle: challengeHandle as HumanChallengeHandle,
|
|
154
|
-
delivery:
|
|
158
|
+
delivery: delegated
|
|
159
|
+
? { kind: 'corrective-replan-delegation-binding', version: 1, challengeId, expiresAtMs }
|
|
160
|
+
: { kind: 'corrective-replan-human-challenge-delivery', version: 1, challengeId, confirmationCode: code, expiresAtMs } });
|
|
155
161
|
};
|
|
162
|
+
const issueDelegatedChallenge = (issuer: unknown, input: unknown): Result<JsonRecord> => issueHumanChallenge(issuer, input, true);
|
|
156
163
|
const confirmHuman = (issuer: unknown, input: unknown): Result<JsonRecord> => {
|
|
157
164
|
const kernel = kernels.get(object(issuer)); if (!kernel) return rejected('credential.provenance.invalid', '/', 'provenance');
|
|
158
165
|
if (!exactKeys(input, ['challenge', 'confirmation'])) return rejected('credential.envelope.invalid', '/', 'schema');
|
|
@@ -162,6 +169,7 @@ const confirmHuman = (issuer: unknown, input: unknown): Result<JsonRecord> => {
|
|
|
162
169
|
if (!challenge) return rejected('credential.provenance.invalid', '/challenge', 'provenance');
|
|
163
170
|
if (!activePreparation(kernel, challenge.plan)) return rejected('credential.preparation.superseded', '/challenge', 'provenance');
|
|
164
171
|
if (kernel.confirmed.has(challengeHandle)) return rejected('credential.provenance.invalid', '/challenge', 'provenance');
|
|
172
|
+
if (challenge.plan.snapshot.delegatedAuthority) return rejected('credential.authority.mode-mismatch', '/challenge', 'authority');
|
|
165
173
|
const candidate = confirmation as JsonRecord; const now = safeTime(kernel.ports.now());
|
|
166
174
|
if (now === null) return rejected('credential.time.invalid', '/coreClock', 'time');
|
|
167
175
|
const validatedAt = number(challenge.plan.attestation.validatedAtMs); const issuedAt = number(challenge.challenge.issuedAtMs);
|
|
@@ -176,6 +184,24 @@ const confirmHuman = (issuer: unknown, input: unknown): Result<JsonRecord> => {
|
|
|
176
184
|
const handle = opaque(); kernel.confirmations.set(handle, deepFreeze({ challenge, projection })); kernel.confirmed.add(challengeHandle);
|
|
177
185
|
return accepted({ handle: handle as VerifiedConfirmationHandle, projection });
|
|
178
186
|
};
|
|
187
|
+
// The authority comes exclusively from the Core snapshot, never from caller assertions.
|
|
188
|
+
const confirmDelegated = (issuer: unknown, input: unknown): Result<JsonRecord> => {
|
|
189
|
+
const kernel = kernels.get(object(issuer));
|
|
190
|
+
if (!kernel || !exactKeys(input, ['challenge'])) return rejected('credential.provenance.invalid', '/', 'provenance');
|
|
191
|
+
const handle = object((input as JsonRecord).challenge); const challenge = kernel.challenges.get(handle);
|
|
192
|
+
if (!challenge || !challenge.plan.snapshot.delegatedAuthority || kernel.confirmed.has(handle))
|
|
193
|
+
return rejected('credential.authority.mode-mismatch', '/challenge', 'authority');
|
|
194
|
+
const freshness = receiptFreshnessIssue(kernel, challenge.plan);
|
|
195
|
+
if (freshness) return rejected(freshness.issueId, freshness.path, freshness.reasonClass);
|
|
196
|
+
const now = safeTime(kernel.ports.now());
|
|
197
|
+
if (now === null || now < number(challenge.challenge.issuedAtMs) || now >= number(challenge.challenge.expiresAtMs))
|
|
198
|
+
return rejected('credential.delegation.expired', '/challenge', 'time');
|
|
199
|
+
const projection = { challengeId: challenge.challenge.challengeId, challengeHash: challenge.challenge.challengeHash,
|
|
200
|
+
bindingSha256: challenge.plan.binding.bindingSha256, delegatedAuthority: challenge.plan.snapshot.delegatedAuthority,
|
|
201
|
+
confirmedAtMs: now };
|
|
202
|
+
const confirmation = opaque(); kernel.confirmations.set(confirmation, deepFreeze({ challenge, projection })); kernel.confirmed.add(handle);
|
|
203
|
+
return accepted({ handle: confirmation as VerifiedConfirmationHandle, projection });
|
|
204
|
+
};
|
|
179
205
|
const issueReceipt = (issuer: unknown, input: unknown): Result<JsonRecord> => {
|
|
180
206
|
const kernel = kernels.get(object(issuer)); if (!kernel) return rejected('credential.provenance.invalid', '/', 'provenance');
|
|
181
207
|
if (!exactKeys(input, ['planValidation', 'confirmation'])) return rejected('credential.envelope.invalid', '/', 'schema');
|
|
@@ -211,8 +237,10 @@ const issueReceipt = (issuer: unknown, input: unknown): Result<JsonRecord> => {
|
|
|
211
237
|
expiresAtMs: plan.attestation.expiresAtMs };
|
|
212
238
|
const base = { schemaVersion: 1, receiptId, domain: 'codex-workflow/corrective-replan-receipt/v1',
|
|
213
239
|
binding: plan.binding, validation, audit: { riskAuditHash: values.riskAuditHash, riskAuditorIdentityHash: values.riskAuditorIdentityHash },
|
|
214
|
-
|
|
215
|
-
|
|
240
|
+
...(plan.snapshot.delegatedAuthority
|
|
241
|
+
? { delegated: { authority: plan.snapshot.delegatedAuthority, authorizedAtMs: confirmation.projection.confirmedAtMs } }
|
|
242
|
+
: { human: { challengeId: confirmation.projection.challengeId, challengeHash: confirmation.projection.challengeHash,
|
|
243
|
+
humanPrincipalIdentityHash: confirmation.projection.humanPrincipalIdentityHash, confirmedAtMs: confirmation.projection.confirmedAtMs } }),
|
|
216
244
|
supersession: { priorGrantHash: snapshotGrant.grantHash, disposition: 'superseded' },
|
|
217
245
|
issuedAtMs: now, expiresAtMs };
|
|
218
246
|
const withDomain = { ...base, domainHash: hashCanonical(base) }; const receipt = deepFreeze({ ...withDomain, receiptHash: hashCanonical(withDomain) });
|
|
@@ -266,11 +294,12 @@ const verifyStoredEvent = (issuer: unknown, input: unknown): Verification<JsonRe
|
|
|
266
294
|
if (!same(candidate, recordValue.event)) return verificationRejected('credential.event.mismatch', '/candidate', 'integrity');
|
|
267
295
|
return verified(recordValue.event);
|
|
268
296
|
};
|
|
269
|
-
const functions = [issuePlanValidation, issueHumanChallenge, confirmHuman, issueReceipt, verifyReceipt, materializeStoredEvent, verifyStoredEvent];
|
|
297
|
+
const functions = [issuePlanValidation, issueHumanChallenge, confirmHuman, issueReceipt, verifyReceipt, materializeStoredEvent, verifyStoredEvent, issueDelegatedChallenge, confirmDelegated];
|
|
270
298
|
const ids = ['credential-materializer.corrective-replan.plan-validation-attestation', 'credential-materializer.corrective-replan.expected-human-challenge',
|
|
271
299
|
'credential-verifier.corrective-replan.human-confirmation', 'credential-materializer.corrective-replan.receipt',
|
|
272
300
|
'credential-verifier.corrective-replan.receipt', 'credential-materializer.corrective-replan.stored-plan-validation-event',
|
|
273
|
-
'credential-verifier.corrective-replan.stored-plan-validation-event'
|
|
301
|
+
'credential-verifier.corrective-replan.stored-plan-validation-event',
|
|
302
|
+
'credential-materializer.corrective-replan.delegated-binding', 'credential-verifier.corrective-replan.delegated-authorization'] as const;
|
|
274
303
|
export const correctiveReplanCredentialFunctionBindings: readonly CredentialFunctionBinding[] = deepFreeze(ids.map((id, index) => ({ id, version: 1,
|
|
275
304
|
kind: id.includes('materializer') ? 'materializer' as const : 'verifier' as const, implementationId: `implementation.${id}` as const,
|
|
276
305
|
implementationVersion: 1, wiring: 'invoked', callable: functions[index]! })));
|
|
@@ -289,7 +318,7 @@ function receiptFreshnessIssue(kernel: KernelRecord, plan: PlanRecord): Issue |
|
|
|
289
318
|
return loaded.snapshotBindingHash === plan.snapshotBindingHash ? null : issue('credential.receipt.snapshot-stale', '/snapshotBindingHash', 'binding');
|
|
290
319
|
}
|
|
291
320
|
function snapshotIssue(snapshot: JsonRecord, taskId: unknown): Issue | null {
|
|
292
|
-
if (!exactKeys(snapshot, ['snapshotVersion', 'bindingManifest', 'worktree', 'baseValues', 'validationExpectation', 'priorPlanAuthorization']) || snapshot.snapshotVersion !== 2)
|
|
321
|
+
if (!exactKeys(snapshot, ['snapshotVersion', 'bindingManifest', 'worktree', 'baseValues', 'validationExpectation', 'priorPlanAuthorization', ...(snapshot.delegatedAuthority ? ['delegatedAuthority'] : [])]) || snapshot.snapshotVersion !== 2)
|
|
293
322
|
return issue('credential.snapshot.invalid', '/snapshot', 'schema');
|
|
294
323
|
const base = record(snapshot.baseValues); const expected = record(snapshot.validationExpectation);
|
|
295
324
|
if (base.taskId !== taskId || base.transitionId !== 'task.plan.corrective-replan' || base.capabilityId !== 'capability.task.corrective-replan'
|
|
@@ -151,7 +151,7 @@ const decisionGuardMetadata = guardMetadata(
|
|
|
151
151
|
);
|
|
152
152
|
const attemptGuardMetadata = guardMetadata(
|
|
153
153
|
'task.corrective-replan.attempt-threshold',
|
|
154
|
-
'The decision
|
|
154
|
+
'The decision binds a proven first-failure Plan-integrity or rebound planning-obstruction route, or the ordinary two-failure threshold.',
|
|
155
155
|
'availability',
|
|
156
156
|
'observation',
|
|
157
157
|
['field.corrective-decision-triggering-attempt-count', 'field.remediation-attempt-count'],
|
|
@@ -193,7 +193,7 @@ const planValidationPassedGuardMetadata = guardMetadata(
|
|
|
193
193
|
'execution', 'candidate', ['field.plan-validation-passed'], ['input.corrective-plan-validation'], 'candidate-blocker',
|
|
194
194
|
);
|
|
195
195
|
const receiptValidGuardMetadata = guardMetadata(
|
|
196
|
-
'task.corrective-replan.receipt-valid', 'The Human-confirmed receipt matches every current execution binding.',
|
|
196
|
+
'task.corrective-replan.receipt-valid', 'The Human-confirmed or explicitly delegated receipt matches every current execution binding.',
|
|
197
197
|
'execution', 'authority', ['field.receipt-valid'], ['input.corrective-execution-receipt'], 'authority-blocker',
|
|
198
198
|
);
|
|
199
199
|
const riskAuditPassedGuardMetadata = guardMetadata(
|
|
@@ -264,12 +264,16 @@ const guards: readonly TransitionGuard<CorrectiveReplanSnapshot, CorrectiveRepla
|
|
|
264
264
|
&& decision?.triggeringAttemptCount === 2
|
|
265
265
|
&& decision.recoveryBasis === 'plan-integrity'
|
|
266
266
|
&& decision.planIntegrityEvidence !== undefined;
|
|
267
|
-
const
|
|
267
|
+
const boundedPlanningObstruction = snapshot.remediationAttemptCount === 1
|
|
268
|
+
&& decision?.triggeringAttemptCount === 2
|
|
269
|
+
&& decision.recoveryBasis === 'planning-obstruction'
|
|
270
|
+
&& decision.planningObstructionEvidence !== undefined;
|
|
271
|
+
const passed = ordinaryThreshold || boundedPlanIntegrity || boundedPlanningObstruction;
|
|
268
272
|
return verdict(
|
|
269
273
|
attemptGuardMetadata,
|
|
270
274
|
passed,
|
|
271
275
|
'ATTEMPT_BINDING_INVALID',
|
|
272
|
-
'Corrective replan requires
|
|
276
|
+
'Corrective replan requires one bounded Plan-integrity/planning-obstruction failure or two ordinary failed attempts, with an exact next-ordinal decision.',
|
|
273
277
|
);
|
|
274
278
|
},
|
|
275
279
|
},
|
|
@@ -347,18 +351,18 @@ const metadata: TransitionDefinitionMetadata = {
|
|
|
347
351
|
eligibleRoles: ['delivery-coordinator', 'technical-planner'],
|
|
348
352
|
actor: {
|
|
349
353
|
identity: v1('identity.actor'),
|
|
350
|
-
principalBasis: v1('principal-basis.
|
|
354
|
+
principalBasis: v1('principal-basis.bound-corrective-authority'),
|
|
351
355
|
required: true,
|
|
352
356
|
},
|
|
353
357
|
principal: {
|
|
354
358
|
identity: v1('identity.human-principal'),
|
|
355
|
-
principalBasis: v1('principal-basis.
|
|
359
|
+
principalBasis: v1('principal-basis.bound-corrective-authority'),
|
|
356
360
|
issuer: v1('identity.human-principal'),
|
|
357
|
-
allowedKinds: ['human'],
|
|
361
|
+
allowedKinds: ['human', 'delegation-grant'],
|
|
358
362
|
},
|
|
359
363
|
capabilities: [{ capability: v1('capability.task.corrective-replan'), source: 'credential', required: true }],
|
|
360
364
|
credentials: [v1('credential-policy.corrective-execution-receipt')],
|
|
361
|
-
delegation: v1('delegation.
|
|
365
|
+
delegation: v1('delegation.corrective-execution-exact'),
|
|
362
366
|
ownership: v1('ownership.corrective-replan-clear'),
|
|
363
367
|
executionBoundaries: ['human', 'coordinator', 'workflow-core'],
|
|
364
368
|
separationOfDuty: [
|
|
@@ -232,15 +232,16 @@ export const transitionSemanticRegistry: SemanticRegistry = deepFreeze(validateS
|
|
|
232
232
|
identities,
|
|
233
233
|
identityComparators,
|
|
234
234
|
principalBases: [
|
|
235
|
+
{ id: 'principal-basis.bound-corrective-authority', version: 1, description: 'Core verifies an exact Human confirmation or a current task.execution_authorize grant for unchanged corrective scope.', principalKind: 'workflow-core', authoritySource: 'workflow-core-derived' },
|
|
235
236
|
{ id: 'principal-basis.human-confirmation', version: 1, description: 'Exact Human confirmation authority basis.', principalKind: 'human', authoritySource: 'human-confirmation' },
|
|
236
237
|
],
|
|
237
238
|
capabilities: [{
|
|
238
|
-
id: 'capability.task.corrective-replan', version: 1, description: 'Human-
|
|
239
|
+
id: 'capability.task.corrective-replan', version: 1, description: 'Human-confirmed or exact execution-grant capability for one replacement candidate.',
|
|
239
240
|
transitionId: 'task.plan.corrective-replan', issuer: v1('identity.human-principal'),
|
|
240
241
|
scope: v1('projection.corrective-replan.execution-receipt'), exactCandidateBound: true,
|
|
241
242
|
}],
|
|
242
243
|
credentialPolicies: [{
|
|
243
|
-
id: 'credential-policy.corrective-execution-receipt', version: 1, description: 'Exact state-bound Human
|
|
244
|
+
id: 'credential-policy.corrective-execution-receipt', version: 1, description: 'Exact state-bound receipt with distinct Human or delegated authority evidence.',
|
|
244
245
|
credentialKind: 'execution-receipt', schema: v1('schema.corrective-replan-receipt'),
|
|
245
246
|
verifier: v1('credential-verifier.corrective-replan.receipt'), issuer: v1('identity.human-principal'),
|
|
246
247
|
presentation: { mode: 'explicit-input', input: v1('input.corrective-execution-receipt') },
|
|
@@ -251,6 +252,7 @@ export const transitionSemanticRegistry: SemanticRegistry = deepFreeze(validateS
|
|
|
251
252
|
credentialVerifiers: [{ id: 'credential-verifier.corrective-replan.receipt', version: 1, description: 'The invoked Core credential kernel recomputes receipt and all current bindings.', verifierKind: 'state-bound-sha256' }],
|
|
252
253
|
clocks: [{ id: 'clock.workflow-core-utc', version: 1, description: 'Workflow Core injected UTC clock.', clockKind: 'workflow-core-utc' }],
|
|
253
254
|
delegationPolicies: [
|
|
255
|
+
{ id: 'delegation.corrective-execution-exact', version: 1, description: 'An explicit live task.execution_authorize grant may authorize a same-scope corrective candidate; independent validation and unchanged acceptance remain mandatory.', mode: 'allowed-exact', scopeBound: true, expiryRequired: true, redelegationAllowed: false },
|
|
254
256
|
{ id: 'delegation.forbidden', version: 1, description: 'Delegation is forbidden.', mode: 'forbidden', scopeBound: false, expiryRequired: false, redelegationAllowed: false },
|
|
255
257
|
],
|
|
256
258
|
ownershipPolicies: [
|
package/src/observed-routes.ts
CHANGED
|
@@ -114,6 +114,9 @@ const OBSERVED_ROUTE_INVENTORY: readonly ObservedRouteDefinition[] = [
|
|
|
114
114
|
diagnostic('update navigation-source-preflight'),
|
|
115
115
|
diagnostic('update navigation-transport-preflight'),
|
|
116
116
|
executable('update navigation-recover', 'recoverNavigationUpdate'),
|
|
117
|
+
diagnostic('update failed-checkpoint-source-preflight'),
|
|
118
|
+
diagnostic('update failed-checkpoint-transport-preflight'),
|
|
119
|
+
executable('update failed-checkpoint-recover', 'recoverFailedCheckpointUpdate'),
|
|
117
120
|
executable('task historical-step-provenance-recover', 'recoverHistoricalStepProvenance'),
|
|
118
121
|
executable('update historical-step-dependency-recover', 'recoverHistoricalStepDependencyProvenance'),
|
|
119
122
|
executable('update dependency-provenance-recover', 'recoverDependencyProvenance'),
|
|
@@ -134,6 +137,7 @@ const OBSERVED_ROUTE_INVENTORY: readonly ObservedRouteDefinition[] = [
|
|
|
134
137
|
executable('task corrective-decision', 'recordStepCorrectiveDecision'),
|
|
135
138
|
executable('task corrective-yield', 'executeCorrectiveYield'),
|
|
136
139
|
executable('task corrective-replan-prepare', 'prepareCorrectiveReplan'),
|
|
140
|
+
executable('task corrective-replan-authorize', 'authorizeDelegatedCorrectiveReplan'),
|
|
137
141
|
],
|
|
138
142
|
successorWitness: {
|
|
139
143
|
kind: 'reachable', routeIds: ['observed.task-lifecycle'],
|
|
@@ -231,6 +235,7 @@ const OBSERVED_ROUTE_INVENTORY: readonly ObservedRouteDefinition[] = [
|
|
|
231
235
|
executable('milestone plan-set', 'setMilestonePlan'),
|
|
232
236
|
executable('milestone scope-change-prepare', 'prepareMilestoneScopeChange'),
|
|
233
237
|
executable('milestone autonomy-evolve', 'evolveMilestonePlanAutonomously'),
|
|
238
|
+
executable('milestone autonomy-bind', 'bindMilestoneAutonomy'),
|
|
234
239
|
executable('milestone authorize', 'authorizeMilestone'),
|
|
235
240
|
executable('milestone validate', 'validateMilestone'),
|
|
236
241
|
],
|
|
@@ -638,6 +638,7 @@ function materializeExecutionEvent(
|
|
|
638
638
|
planRiskAuditEventHash: riskEvent.eventHash,
|
|
639
639
|
actorIdentityHash: values.actorIdentityHash,
|
|
640
640
|
humanPrincipalIdentityHash: values.humanPrincipalIdentityHash,
|
|
641
|
+
...(receipt.delegated ? { delegatedAuthority: get(receipt, '/delegated/authority') } : {}),
|
|
641
642
|
priorGrantHash: get(receipt, '/supersession/priorGrantHash'),
|
|
642
643
|
taskStateSha256,
|
|
643
644
|
planBytesSha256,
|