codex-workflow-v2 2.0.0-beta.13.8 → 2.0.0-beta.14
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 +27 -388
- package/dist/reviewer-runtime-build.json +96 -32
- package/dist/src/alpha6/adoption.d.ts +2 -0
- package/dist/src/alpha6/adoption.js +22 -0
- package/dist/src/alpha6/adoption.js.map +1 -1
- package/dist/src/alpha6/captured-check-evidence.d.ts +51 -0
- package/dist/src/alpha6/captured-check-evidence.js +152 -0
- package/dist/src/alpha6/captured-check-evidence.js.map +1 -0
- package/dist/src/alpha6/component-owner.d.ts +15 -1
- package/dist/src/alpha6/component-owner.js +44 -2
- package/dist/src/alpha6/component-owner.js.map +1 -1
- package/dist/src/alpha6/corrective-decision-boundary.d.ts +4 -0
- package/dist/src/alpha6/corrective-decision-boundary.js +47 -0
- package/dist/src/alpha6/corrective-decision-boundary.js.map +1 -0
- package/dist/src/alpha6/downstream-proof.d.ts +1 -25
- package/dist/src/alpha6/downstream-proof.js +0 -171
- package/dist/src/alpha6/downstream-proof.js.map +1 -1
- package/dist/src/alpha6/failed-step-planning-recovery.d.ts +10 -0
- package/dist/src/alpha6/failed-step-planning-recovery.js +93 -0
- package/dist/src/alpha6/failed-step-planning-recovery.js.map +1 -0
- package/dist/src/alpha6/literal-test-invocation.d.ts +2 -0
- package/dist/src/alpha6/literal-test-invocation.js +106 -0
- package/dist/src/alpha6/literal-test-invocation.js.map +1 -0
- package/dist/src/alpha6/milestone.d.ts +2 -0
- package/dist/src/alpha6/milestone.js +10 -1
- package/dist/src/alpha6/milestone.js.map +1 -1
- package/dist/src/alpha6/navigation-dirty-carryover.d.ts +11 -0
- package/dist/src/alpha6/navigation-dirty-carryover.js +130 -0
- package/dist/src/alpha6/navigation-dirty-carryover.js.map +1 -0
- package/dist/src/alpha6/plan-integrity.js +7 -5
- package/dist/src/alpha6/plan-integrity.js.map +1 -1
- package/dist/src/alpha6/plan-risk.d.ts +2 -0
- package/dist/src/alpha6/plan-risk.js +10 -3
- package/dist/src/alpha6/plan-risk.js.map +1 -1
- package/dist/src/alpha6/remediation.d.ts +6 -21
- package/dist/src/alpha6/remediation.js +161 -476
- package/dist/src/alpha6/remediation.js.map +1 -1
- package/dist/src/alpha6/root-cause-replan-carryover.d.ts +4 -1
- package/dist/src/alpha6/root-cause-replan-carryover.js +29 -5
- package/dist/src/alpha6/root-cause-replan-carryover.js.map +1 -1
- package/dist/src/beta1/project-transaction.d.ts +3 -1
- package/dist/src/beta1/project-transaction.js +6 -2
- package/dist/src/beta1/project-transaction.js.map +1 -1
- package/dist/src/checks/runner.d.ts +26 -0
- package/dist/src/checks/runner.js +223 -0
- package/dist/src/checks/runner.js.map +1 -0
- package/dist/src/checks/task-sync.d.ts +10 -0
- package/dist/src/checks/task-sync.js +41 -0
- package/dist/src/checks/task-sync.js.map +1 -0
- package/dist/src/checks/worker.d.ts +1 -0
- package/dist/src/checks/worker.js +154 -0
- package/dist/src/checks/worker.js.map +1 -0
- package/dist/src/cli-actions.d.ts +2 -2
- package/dist/src/cli-actions.js +3 -15
- package/dist/src/cli-actions.js.map +1 -1
- package/dist/src/cli.js +14 -98
- package/dist/src/cli.js.map +1 -1
- package/dist/src/contracts.d.ts +35 -32
- package/dist/src/dependency-provenance.d.ts +2 -2
- package/dist/src/dependency-provenance.js +12 -69
- package/dist/src/dependency-provenance.js.map +1 -1
- package/dist/src/domain/base-sync-conflict.d.ts +13 -0
- package/dist/src/domain/base-sync-conflict.js +32 -0
- package/dist/src/domain/base-sync-conflict.js.map +1 -0
- package/dist/src/domain/step-start-admission.d.ts +4 -0
- package/dist/src/domain/step-start-admission.js +9 -0
- package/dist/src/domain/step-start-admission.js.map +1 -0
- package/dist/src/domain/virgin-registration.d.ts +3 -0
- package/dist/src/domain/virgin-registration.js +65 -0
- package/dist/src/domain/virgin-registration.js.map +1 -0
- package/dist/src/errors.d.ts +1 -1
- package/dist/src/errors.js.map +1 -1
- package/dist/src/gateway-handshake.js +0 -8
- package/dist/src/gateway-handshake.js.map +1 -1
- package/dist/src/git.d.ts +3 -1
- package/dist/src/git.js +41 -14
- package/dist/src/git.js.map +1 -1
- package/dist/src/graph.js +25 -3
- package/dist/src/graph.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/lifecycle/corrective-replan.js +3 -3
- package/dist/src/lifecycle/corrective-replan.js.map +1 -1
- package/dist/src/navigation-actions.d.ts +9 -0
- package/dist/src/navigation-actions.js +72 -0
- package/dist/src/navigation-actions.js.map +1 -0
- package/dist/src/navigation-update-artifact.d.ts +5 -0
- package/dist/src/navigation-update-artifact.js +227 -0
- package/dist/src/navigation-update-artifact.js.map +1 -0
- package/dist/src/navigation-update.d.ts +39 -0
- package/dist/src/navigation-update.js +82 -0
- package/dist/src/navigation-update.js.map +1 -0
- package/dist/src/observation.js +52 -29
- package/dist/src/observation.js.map +1 -1
- package/dist/src/observed-routes.js +5 -6
- package/dist/src/observed-routes.js.map +1 -1
- package/dist/src/pending-review-update.d.ts +0 -13
- package/dist/src/pending-review-update.js +1 -6
- package/dist/src/pending-review-update.js.map +1 -1
- package/dist/src/reviewer.d.ts +1 -1
- package/dist/src/reviewer.js +20 -17
- package/dist/src/reviewer.js.map +1 -1
- package/dist/src/runtime.d.ts +3 -0
- package/dist/src/runtime.js +6 -0
- package/dist/src/runtime.js.map +1 -0
- package/dist/src/state/corrective-replan-executor.d.ts +10 -0
- package/dist/src/state/corrective-replan-executor.js +37 -1
- package/dist/src/state/corrective-replan-executor.js.map +1 -1
- package/dist/src/state/corrective-replan-public-schema.js +15 -2
- package/dist/src/state/corrective-replan-public-schema.js.map +1 -1
- package/dist/src/state/corrective-replan-public.js +6 -5
- package/dist/src/state/corrective-replan-public.js.map +1 -1
- package/dist/src/state/corrective-replan-transaction.d.ts +1 -0
- package/dist/src/state/corrective-replan-transaction.js +12 -11
- package/dist/src/state/corrective-replan-transaction.js.map +1 -1
- package/dist/src/state/corrective-yield-executor.js +2 -2
- package/dist/src/state/corrective-yield-executor.js.map +1 -1
- package/dist/src/state/corrective-yield-transaction.d.ts +1 -0
- package/dist/src/state/corrective-yield-transaction.js +11 -10
- package/dist/src/state/corrective-yield-transaction.js.map +1 -1
- package/dist/src/state/lock.d.ts +4 -0
- package/dist/src/state/lock.js +21 -0
- package/dist/src/state/lock.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.d.ts +8 -0
- package/dist/src/workflow-blocker-route.js +106 -0
- package/dist/src/workflow-blocker-route.js.map +1 -0
- package/dist/src/workflow.d.ts +48 -63
- package/dist/src/workflow.js +698 -1470
- package/dist/src/workflow.js.map +1 -1
- package/docs/autonomy-guardrails.md +26 -304
- package/docs/decisions.md +14 -112
- package/docs/development-flow.md +35 -238
- package/docs/project-memory.md +31 -50
- package/docs/release-app-evidence.md +190 -0
- package/docs/release.md +95 -363
- package/docs/updating-existing-project.md +19 -717
- package/package.json +11 -13
- package/plugins/codex-workflow-gateway/.codex-plugin/plugin.json +2 -2
- package/plugins/codex-workflow-gateway/references/chat-dispatch.md +73 -197
- package/plugins/codex-workflow-gateway/references/codebase-memory-routing.md +57 -0
- package/plugins/codex-workflow-gateway/references/protocol.md +42 -445
- package/plugins/codex-workflow-gateway/scripts/chat-dispatch.mjs +7 -1
- package/plugins/codex-workflow-gateway/scripts/chat-model-policy.mjs +10 -8
- package/plugins/codex-workflow-gateway/scripts/chat-registry.mjs +23 -5
- package/plugins/codex-workflow-gateway/skills/codex-workflow-gateway/SKILL.md +66 -740
- package/references/state-machine.md +4 -4
- package/roles/technical-planner.md +1 -1
- package/schemas/corrective-decision-event.schema.json +3 -1
- package/schemas/project-knowledge-map.schema.json +3 -1
- package/schemas/remediation-event.schema.json +10 -1
- package/schemas/task.schema.json +46 -2
- package/schemas/transition-payloads.schema.json +16 -1
- package/src/alpha6/adoption.ts +1123 -0
- package/src/alpha6/captured-check-evidence.ts +132 -0
- package/src/alpha6/check-support-anchor.ts +128 -0
- package/src/alpha6/component-owner.ts +168 -0
- package/src/alpha6/corrective-decision-boundary.ts +39 -0
- package/src/alpha6/downstream-proof.ts +520 -0
- package/src/alpha6/failed-step-planning-recovery.ts +88 -0
- package/src/alpha6/handoff.ts +1443 -0
- package/src/alpha6/journal.ts +473 -0
- package/src/alpha6/literal-test-invocation.ts +84 -0
- package/src/alpha6/mechanical-feasibility.ts +488 -0
- package/src/alpha6/milestone.ts +2192 -0
- package/src/alpha6/navigation-dirty-carryover.ts +154 -0
- package/src/alpha6/npm-check-contract.ts +47 -0
- package/src/alpha6/plan-integrity.ts +298 -0
- package/src/alpha6/plan-risk.ts +1480 -0
- package/src/alpha6/preexecution-replan.ts +187 -0
- package/src/alpha6/remediation-cause.ts +98 -0
- package/src/alpha6/remediation.ts +2438 -0
- package/src/alpha6/review.ts +1198 -0
- package/src/alpha6/root-cause-replan-carryover.ts +491 -0
- package/src/alpha6/store-sidecars.ts +335 -0
- package/src/alpha7/autonomy.ts +411 -0
- package/src/alpha7/corrective-recovery.ts +1332 -0
- package/src/artifacts.ts +130 -0
- package/src/beta1/project-transaction.ts +355 -0
- package/src/change-explanation.ts +153 -0
- package/src/checks/runner.ts +245 -0
- package/src/checks/task-sync.ts +41 -0
- package/src/checks/worker.ts +149 -0
- package/src/cli-actions.ts +107 -0
- package/src/cli.ts +1457 -0
- package/src/contracts.ts +1494 -0
- package/src/credential-output.ts +89 -0
- package/src/credential-transport.ts +215 -0
- package/src/delegation.ts +190 -0
- package/src/dependency-provenance.ts +472 -0
- package/src/diagnostics.ts +93 -0
- package/src/domain/base-sync-conflict.ts +35 -0
- package/src/domain/completed-step-carryover.ts +76 -0
- package/src/domain/discovery.ts +27 -0
- package/src/domain/plan-semantics.ts +58 -0
- package/src/domain/step-start-admission.ts +10 -0
- package/src/domain/validation.ts +177 -0
- package/src/domain/virgin-registration.ts +46 -0
- package/src/errors.ts +24 -0
- package/src/fs-utils.ts +61 -0
- package/src/gateway-handshake.ts +95 -0
- package/src/git.ts +183 -0
- package/src/graph.ts +342 -0
- package/src/historical-step-provenance.ts +136 -0
- package/src/index.ts +23 -0
- package/src/lifecycle/canonical-hash.ts +28 -0
- package/src/lifecycle/catalog.ts +202 -0
- package/src/lifecycle/compiler-inspection.ts +29 -0
- package/src/lifecycle/core-static-readiness.ts +132 -0
- package/src/lifecycle/corrective-replan-authority.ts +136 -0
- package/src/lifecycle/corrective-replan-binding-manifest.ts +51 -0
- package/src/lifecycle/corrective-replan-credential-core.ts +408 -0
- package/src/lifecycle/corrective-replan-credential-schema.ts +54 -0
- package/src/lifecycle/corrective-replan-credentials.ts +48 -0
- package/src/lifecycle/corrective-replan.ts +843 -0
- package/src/lifecycle/evaluator.ts +48 -0
- package/src/lifecycle/fingerprint.ts +488 -0
- package/src/lifecycle/immutable.ts +8 -0
- package/src/lifecycle/implementation-table.ts +118 -0
- package/src/lifecycle/index.ts +8 -0
- package/src/lifecycle/schema-artifact.ts +263 -0
- package/src/lifecycle/semantic-registry.ts +572 -0
- package/src/lifecycle/types.ts +838 -0
- package/src/memory.ts +273 -0
- package/src/migration.ts +161 -0
- package/src/navigation-actions.ts +70 -0
- package/src/navigation-update-artifact.ts +198 -0
- package/src/navigation-update.ts +121 -0
- package/src/observation.ts +225 -0
- package/src/observed-routes.ts +660 -0
- package/src/operational-contract.ts +125 -0
- package/src/pending-review-update.ts +175 -0
- package/src/repository.ts +99 -0
- package/src/reviewer.ts +1879 -0
- package/src/runtime.ts +6 -0
- package/src/state/corrective-replan-executor.ts +818 -0
- package/src/state/corrective-replan-public-schema.ts +83 -0
- package/src/state/corrective-replan-public.ts +908 -0
- package/src/state/corrective-replan-transaction.ts +949 -0
- package/src/state/corrective-yield-executor.ts +327 -0
- package/src/state/corrective-yield-transaction.ts +730 -0
- package/src/state/lock.ts +902 -0
- package/src/state/store.ts +567 -0
- package/src/transition-core.ts +330 -0
- package/src/ulid.ts +24 -0
- package/src/version.ts +2 -0
- package/src/workflow-blocker-route.ts +109 -0
- package/src/workflow.ts +10172 -0
- package/docs/alpha7.1-implementation-brief.md +0 -268
- package/docs/alpha7.2-corrective-context-refresh-brief.md +0 -484
- package/docs/alpha7.2.1-remediation-recovery-brief.md +0 -86
- package/docs/beta1-stabilization-brief.md +0 -165
- package/docs/beta11-plan-integrity-recovery-brief.md +0 -38
- package/docs/beta13.2-signal-review-recovery.md +0 -38
- package/docs/beta2-initial-assembly-navigation-brief.md +0 -616
- package/docs/change-model.md +0 -118
- package/docs/delegated-approval.md +0 -254
- package/docs/lifecycle/state-machine-stabilization.md +0 -641
- package/docs/pdf/README.md +0 -24
- package/docs/pdf/codex-workflow-v2-architecture-ru.pdf +0 -0
- package/docs/pdf/codex-workflow-v2-chat-only-guide-ru.pdf +0 -0
- package/docs/pdf/codex-workflow-v2-technical-reference-ru.pdf +0 -0
- package/docs/pdf/requirements.txt +0 -1
- package/docs/pdf/sources/codex-workflow-v2-architecture-ru.md +0 -478
- package/docs/pdf/sources/codex-workflow-v2-chat-only-guide-ru.md +0 -508
- package/docs/pdf/sources/codex-workflow-v2-technical-reference-ru.md +0 -778
- package/docs/pending-review-update.md +0 -15
- package/docs/problem-briefs/01-pre-implementation-integrity.md +0 -482
- package/docs/problem-briefs/02-minimal-step-integrity.md +0 -411
- package/docs/problem-briefs/03-minimal-agent-context-integrity.md +0 -358
- package/docs/problem-briefs/04-task-dependency-and-structural-replacement-integrity.md +0 -573
- package/docs/problem-briefs/BRIEF-TEMPLATE.md +0 -56
- package/docs/problem-briefs/README.md +0 -120
- package/docs/problem-briefs/evidence/p01-mechanical-feasibility-corpus.md +0 -90
- package/docs/problem-briefs/evidence/signal-v4-pre-m3-replay.md +0 -246
- package/docs/split-required-recovery.md +0 -47
- package/docs/stable-release-defect-register.md +0 -730
- package/docs/validation-report.md +0 -182
- package/scripts/generate-pdf-docs.py +0 -524
- package/scripts/run-pdf-docs.mjs +0 -62
|
@@ -0,0 +1,730 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import {
|
|
3
|
+
closeSync,
|
|
4
|
+
constants,
|
|
5
|
+
existsSync,
|
|
6
|
+
fsyncSync,
|
|
7
|
+
lstatSync,
|
|
8
|
+
mkdirSync,
|
|
9
|
+
openSync,
|
|
10
|
+
readFileSync,
|
|
11
|
+
readdirSync,
|
|
12
|
+
renameSync,
|
|
13
|
+
rmSync,
|
|
14
|
+
writeSync,
|
|
15
|
+
} from 'node:fs';
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
import { Ajv2020 } from 'ajv/dist/2020.js';
|
|
18
|
+
import type { CorruptObservedTaskTransaction, ObservedTaskTransaction } from '../contracts.js';
|
|
19
|
+
import { WorkflowError } from '../errors.js';
|
|
20
|
+
import { assertNoSymlinkPathComponents, computeSidecarEventHash } from '../alpha6/store-sidecars.js';
|
|
21
|
+
import { hashCanonical } from '../lifecycle/fingerprint.js';
|
|
22
|
+
import { deepFreeze } from '../lifecycle/immutable.js';
|
|
23
|
+
import {
|
|
24
|
+
WriterLockManager,
|
|
25
|
+
assertCoreTaskOperationHandle,
|
|
26
|
+
type CoreTaskOperationHandle,
|
|
27
|
+
type WriterLeaseHandle,
|
|
28
|
+
} from './lock.js';
|
|
29
|
+
|
|
30
|
+
const HASH = /^[a-f0-9]{64}$/;
|
|
31
|
+
const TASK = /^TASK-[0-9A-HJKMNP-TV-Z]{26}$/;
|
|
32
|
+
const PROJECT = /^[a-f0-9]{24}$/;
|
|
33
|
+
const TRANSITION = 'task.coordination.corrective-yield' as const;
|
|
34
|
+
const schema = JSON.parse(readFileSync(
|
|
35
|
+
new URL('../../../schemas/corrective-yield-task-transaction.private.schema.json', import.meta.url),
|
|
36
|
+
'utf8',
|
|
37
|
+
)) as object;
|
|
38
|
+
const ajv = new Ajv2020({
|
|
39
|
+
allErrors: true,
|
|
40
|
+
strict: true,
|
|
41
|
+
formats: {
|
|
42
|
+
'date-time': {
|
|
43
|
+
type: 'string',
|
|
44
|
+
validate(value: string) {
|
|
45
|
+
const parsed = Date.parse(value);
|
|
46
|
+
return Number.isFinite(parsed) && new Date(parsed).toISOString() === value;
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
if (!ajv.validateSchema(schema)) throw new TypeError(`Invalid corrective-yield transaction schema: ${ajv.errorsText(ajv.errors)}`);
|
|
52
|
+
const validateSchema = ajv.compile(schema);
|
|
53
|
+
|
|
54
|
+
export type CorrectiveYieldTransactionPhase = 'intent' | 'applying' | 'published-pending-readback' | 'completed';
|
|
55
|
+
|
|
56
|
+
interface SnapshotRecord {
|
|
57
|
+
exists: boolean;
|
|
58
|
+
contentSha256: string | null;
|
|
59
|
+
byteLength: number;
|
|
60
|
+
contentPath: 'preimages/0.bin' | null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface CorrectiveYieldTaskTransactionJournal {
|
|
64
|
+
schemaVersion: 1;
|
|
65
|
+
kind: 'corrective-yield-task-transaction';
|
|
66
|
+
transactionId: string;
|
|
67
|
+
projectId: string;
|
|
68
|
+
taskId: string;
|
|
69
|
+
transitionId: typeof TRANSITION;
|
|
70
|
+
taskStateSha256: string;
|
|
71
|
+
actor: string;
|
|
72
|
+
writerLeaseTokenSha256: string;
|
|
73
|
+
writerLeaseContentSha256: string;
|
|
74
|
+
writerLeaseExpiresAt: string;
|
|
75
|
+
admittedAt: string;
|
|
76
|
+
intentSha256: string;
|
|
77
|
+
phase: CorrectiveYieldTransactionPhase;
|
|
78
|
+
phaseRevision: number;
|
|
79
|
+
nextTargetOrdinal: number;
|
|
80
|
+
handoffTarget: {
|
|
81
|
+
targetId: 'handoff-event';
|
|
82
|
+
ordinal: 0;
|
|
83
|
+
relativePath: 'handoffs.jsonl';
|
|
84
|
+
preimage: SnapshotRecord;
|
|
85
|
+
postimage: { contentSha256: string; byteLength: number; contentPath: 'targets/0.bin' };
|
|
86
|
+
previousEventHash: string | null;
|
|
87
|
+
resultEventHash: string;
|
|
88
|
+
};
|
|
89
|
+
phaseHistory: Array<{
|
|
90
|
+
sequence: number;
|
|
91
|
+
phase: CorrectiveYieldTransactionPhase;
|
|
92
|
+
nextTargetOrdinal: number;
|
|
93
|
+
recordedAt: string;
|
|
94
|
+
previousEntryHash: string | null;
|
|
95
|
+
entryHash: string;
|
|
96
|
+
}>;
|
|
97
|
+
completedResult: { resultSha256: string } | null;
|
|
98
|
+
updatedAt: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface CreateCorrectiveYieldIntentInput {
|
|
102
|
+
projectId: string;
|
|
103
|
+
taskId: string;
|
|
104
|
+
transactionId: string;
|
|
105
|
+
taskStateSha256: string;
|
|
106
|
+
actor: string;
|
|
107
|
+
writerLeaseTokenSha256: string;
|
|
108
|
+
writerLeaseContentSha256: string;
|
|
109
|
+
writerLeaseExpiresAt: string;
|
|
110
|
+
handoffPostimage: Uint8Array;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export class CorrectiveYieldTaskTransactionStore {
|
|
114
|
+
private readonly taskRoot: string;
|
|
115
|
+
private readonly locksRoot: string;
|
|
116
|
+
private readonly trustedRoot: string;
|
|
117
|
+
private readonly now: () => Date;
|
|
118
|
+
private readonly writerLocks: WriterLockManager;
|
|
119
|
+
|
|
120
|
+
constructor(options: { taskRoot: string; locksRoot: string; trustedRoot: string; now?: () => Date }) {
|
|
121
|
+
this.taskRoot = path.resolve(options.taskRoot);
|
|
122
|
+
this.locksRoot = path.resolve(options.locksRoot);
|
|
123
|
+
this.trustedRoot = path.resolve(options.trustedRoot);
|
|
124
|
+
this.now = options.now ?? (() => new Date());
|
|
125
|
+
this.writerLocks = new WriterLockManager(this.locksRoot, this.now, this.trustedRoot);
|
|
126
|
+
assertTaskRoot(this.taskRoot, this.trustedRoot);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
createIntent(writerHandle: WriterLeaseHandle, input: CreateCorrectiveYieldIntentInput): CorrectiveYieldTaskTransactionJournal {
|
|
130
|
+
assertExactRecord(input, [
|
|
131
|
+
'actor', 'handoffPostimage', 'projectId', 'taskId', 'taskStateSha256', 'transactionId',
|
|
132
|
+
'writerLeaseContentSha256', 'writerLeaseExpiresAt', 'writerLeaseTokenSha256',
|
|
133
|
+
], 'Corrective-yield intent');
|
|
134
|
+
assertIntent(input);
|
|
135
|
+
const proof = this.writerLocks.proofBinding(writerHandle);
|
|
136
|
+
if (proof.entityId !== input.taskId || proof.owner !== input.actor
|
|
137
|
+
|| proof.tokenSha256 !== input.writerLeaseTokenSha256
|
|
138
|
+
|| proof.contentSha256 !== input.writerLeaseContentSha256
|
|
139
|
+
|| proof.expiresAt !== input.writerLeaseExpiresAt) {
|
|
140
|
+
throw new WorkflowError('LOCK_TOKEN_INVALID', 'Writer lease proof does not match the corrective-yield intent.');
|
|
141
|
+
}
|
|
142
|
+
const stateBytes = readRequired(this.targetPath('state.json'), this.taskRoot);
|
|
143
|
+
if (sha256(stateBytes) !== input.taskStateSha256) {
|
|
144
|
+
throw new WorkflowError('STATE_CONFLICT', 'Corrective-yield Task snapshot changed before intent.');
|
|
145
|
+
}
|
|
146
|
+
const preimage = readOptional(this.targetPath('handoffs.jsonl'), this.taskRoot);
|
|
147
|
+
const postimage = Buffer.from(input.handoffPostimage);
|
|
148
|
+
const anchors = validateAppend(preimage, postimage, input.transactionId, input.actor, input.writerLeaseTokenSha256);
|
|
149
|
+
const root = this.transactionRoot(input.transactionId);
|
|
150
|
+
if (existsSync(root)) throw new WorkflowError('STATE_CONFLICT', 'Corrective-yield transaction already exists.');
|
|
151
|
+
const admittedAt = this.now().toISOString();
|
|
152
|
+
const journal: CorrectiveYieldTaskTransactionJournal = {
|
|
153
|
+
schemaVersion: 1,
|
|
154
|
+
kind: 'corrective-yield-task-transaction',
|
|
155
|
+
transactionId: input.transactionId,
|
|
156
|
+
projectId: input.projectId,
|
|
157
|
+
taskId: input.taskId,
|
|
158
|
+
transitionId: TRANSITION,
|
|
159
|
+
taskStateSha256: input.taskStateSha256,
|
|
160
|
+
actor: input.actor,
|
|
161
|
+
writerLeaseTokenSha256: input.writerLeaseTokenSha256,
|
|
162
|
+
writerLeaseContentSha256: input.writerLeaseContentSha256,
|
|
163
|
+
writerLeaseExpiresAt: input.writerLeaseExpiresAt,
|
|
164
|
+
admittedAt,
|
|
165
|
+
intentSha256: '0'.repeat(64),
|
|
166
|
+
phase: 'intent',
|
|
167
|
+
phaseRevision: 1,
|
|
168
|
+
nextTargetOrdinal: 0,
|
|
169
|
+
handoffTarget: {
|
|
170
|
+
targetId: 'handoff-event',
|
|
171
|
+
ordinal: 0,
|
|
172
|
+
relativePath: 'handoffs.jsonl',
|
|
173
|
+
preimage: snapshot(preimage),
|
|
174
|
+
postimage: { contentSha256: sha256(postimage), byteLength: postimage.byteLength, contentPath: 'targets/0.bin' },
|
|
175
|
+
...anchors,
|
|
176
|
+
},
|
|
177
|
+
phaseHistory: [],
|
|
178
|
+
completedResult: null,
|
|
179
|
+
updatedAt: admittedAt,
|
|
180
|
+
};
|
|
181
|
+
journal.intentSha256 = intentHash(journal);
|
|
182
|
+
journal.phaseHistory = [phaseEntry(0, 'intent', 0, admittedAt, null, journal.intentSha256)];
|
|
183
|
+
validateJournal(journal, this.taskRoot, false);
|
|
184
|
+
ensureDirectorySafe(path.dirname(root), this.trustedRoot);
|
|
185
|
+
let created = false;
|
|
186
|
+
try {
|
|
187
|
+
mkdirSync(root, { mode: 0o700 });
|
|
188
|
+
created = true;
|
|
189
|
+
mkdirSync(path.join(root, 'preimages'), { mode: 0o700 });
|
|
190
|
+
mkdirSync(path.join(root, 'targets'), { mode: 0o700 });
|
|
191
|
+
if (preimage) writeNewDurable(path.join(root, 'preimages', '0.bin'), preimage);
|
|
192
|
+
writeNewDurable(path.join(root, 'targets', '0.bin'), postimage);
|
|
193
|
+
this.writerLocks.proofBinding(writerHandle);
|
|
194
|
+
writeNewDurable(this.journalPath(input.transactionId), jsonBytes(journal));
|
|
195
|
+
fsyncDirectory(root);
|
|
196
|
+
return this.read(input.transactionId);
|
|
197
|
+
} catch (error) {
|
|
198
|
+
if (created && !existsSync(this.journalPath(input.transactionId))) rmSync(root, { recursive: true, force: true });
|
|
199
|
+
throw error;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
read(transactionId: string): CorrectiveYieldTaskTransactionJournal {
|
|
204
|
+
assertHash(transactionId, 'Corrective-yield transaction id');
|
|
205
|
+
const file = this.journalPath(transactionId);
|
|
206
|
+
assertNoSymlinkPathComponents(file, { label: 'Corrective-yield journal', trustedRoot: this.trustedRoot, requireLeaf: true });
|
|
207
|
+
if (!existsSync(file)) throw new WorkflowError('NOT_FOUND', `Corrective-yield journal not found: ${transactionId}`);
|
|
208
|
+
let value: unknown;
|
|
209
|
+
try { value = JSON.parse(readFileSync(file, 'utf8')); } catch { throw new WorkflowError('STATE_CORRUPT', 'Corrective-yield journal is not valid JSON.'); }
|
|
210
|
+
validateJournal(value, this.taskRoot, true);
|
|
211
|
+
const journal = value as CorrectiveYieldTaskTransactionJournal;
|
|
212
|
+
if (journal.transactionId !== transactionId || journal.taskId !== path.basename(this.taskRoot)) {
|
|
213
|
+
throw new WorkflowError('STATE_CORRUPT', 'Corrective-yield journal path identity mismatch.');
|
|
214
|
+
}
|
|
215
|
+
return deepFreeze(structuredClone(journal));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
journalPath(transactionId: string): string {
|
|
219
|
+
assertHash(transactionId, 'Corrective-yield transaction id');
|
|
220
|
+
return path.join(this.transactionRoot(transactionId), 'journal.json');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
advanceWithWriter(
|
|
224
|
+
writerHandle: WriterLeaseHandle,
|
|
225
|
+
transactionId: string,
|
|
226
|
+
expectedPhase: CorrectiveYieldTransactionPhase,
|
|
227
|
+
expectedRevision: number,
|
|
228
|
+
nextPhase: CorrectiveYieldTransactionPhase,
|
|
229
|
+
nextTargetOrdinal: number,
|
|
230
|
+
): CorrectiveYieldTaskTransactionJournal {
|
|
231
|
+
const journal = this.read(transactionId);
|
|
232
|
+
const proof = this.writerLocks.proofBinding(writerHandle);
|
|
233
|
+
if (proof.entityId !== journal.taskId || proof.tokenSha256 !== journal.writerLeaseTokenSha256) {
|
|
234
|
+
throw new WorkflowError('LOCK_TOKEN_INVALID', 'Writer proof does not own the corrective-yield transaction.');
|
|
235
|
+
}
|
|
236
|
+
return this.advance(journal, expectedPhase, expectedRevision, nextPhase, nextTargetOrdinal);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
advanceWithCore(
|
|
240
|
+
handle: CoreTaskOperationHandle,
|
|
241
|
+
transactionId: string,
|
|
242
|
+
expectedPhase: CorrectiveYieldTransactionPhase,
|
|
243
|
+
expectedRevision: number,
|
|
244
|
+
nextPhase: CorrectiveYieldTransactionPhase,
|
|
245
|
+
nextTargetOrdinal: number,
|
|
246
|
+
completedResultSha256?: string,
|
|
247
|
+
): CorrectiveYieldTaskTransactionJournal {
|
|
248
|
+
const journal = this.read(transactionId);
|
|
249
|
+
assertCoreTaskOperationHandle(this.locksRoot, this.trustedRoot, handle, this.now(), {
|
|
250
|
+
entityId: journal.taskId,
|
|
251
|
+
operationId: journal.transactionId,
|
|
252
|
+
transitionId: journal.transitionId,
|
|
253
|
+
});
|
|
254
|
+
return this.advance(journal, expectedPhase, expectedRevision, nextPhase, nextTargetOrdinal, completedResultSha256);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
applyHandoffWithWriter(writerHandle: WriterLeaseHandle, transactionId: string): 'applied' | 'confirmed' {
|
|
258
|
+
const journal = this.read(transactionId);
|
|
259
|
+
const proof = this.writerLocks.proofBinding(writerHandle);
|
|
260
|
+
if (proof.entityId !== journal.taskId || proof.tokenSha256 !== journal.writerLeaseTokenSha256) {
|
|
261
|
+
throw new WorkflowError('LOCK_TOKEN_INVALID', 'Writer proof does not own the corrective-yield transaction.');
|
|
262
|
+
}
|
|
263
|
+
return this.applyHandoff(journal);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
applyHandoffWithCore(handle: CoreTaskOperationHandle, transactionId: string): 'applied' | 'confirmed' {
|
|
267
|
+
const journal = this.read(transactionId);
|
|
268
|
+
assertCoreTaskOperationHandle(this.locksRoot, this.trustedRoot, handle, this.now(), {
|
|
269
|
+
entityId: journal.taskId,
|
|
270
|
+
operationId: journal.transactionId,
|
|
271
|
+
transitionId: journal.transitionId,
|
|
272
|
+
});
|
|
273
|
+
return this.applyHandoff(journal);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
domainReadback(handle: CoreTaskOperationHandle, transactionId: string): string {
|
|
277
|
+
const journal = this.read(transactionId);
|
|
278
|
+
assertCoreTaskOperationHandle(this.locksRoot, this.trustedRoot, handle, this.now(), {
|
|
279
|
+
entityId: journal.taskId,
|
|
280
|
+
operationId: journal.transactionId,
|
|
281
|
+
transitionId: journal.transitionId,
|
|
282
|
+
});
|
|
283
|
+
if (journal.phase !== 'published-pending-readback') {
|
|
284
|
+
throw new WorkflowError('TASK_RECOVERY_REQUIRED', 'Corrective-yield domain readback requires its published phase.');
|
|
285
|
+
}
|
|
286
|
+
assertCanonicalHandoff(this.taskRoot, journal);
|
|
287
|
+
assertTaskState(this.taskRoot, journal.taskStateSha256);
|
|
288
|
+
return resultHash(journal);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
terminalReadback(transactionId: string): string {
|
|
292
|
+
const journal = this.read(transactionId);
|
|
293
|
+
if (journal.phase !== 'completed' || !journal.completedResult) {
|
|
294
|
+
throw new WorkflowError('TASK_RECOVERY_REQUIRED', 'Corrective-yield transaction is not terminal.');
|
|
295
|
+
}
|
|
296
|
+
assertCanonicalHandoffRetained(this.taskRoot, journal);
|
|
297
|
+
if (existsSync(path.join(this.locksRoot, `${journal.taskId}.json`))) {
|
|
298
|
+
throw new WorkflowError('STATE_CONFLICT', 'Corrective-yield terminal readback requires the writer/Core lock slot to be empty.');
|
|
299
|
+
}
|
|
300
|
+
const result = resultHash(journal);
|
|
301
|
+
if (result !== journal.completedResult.resultSha256) {
|
|
302
|
+
throw new WorkflowError('STATE_CORRUPT', 'Corrective-yield completed result digest mismatch.');
|
|
303
|
+
}
|
|
304
|
+
return result;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
handoffIsPostimage(journal: CorrectiveYieldTaskTransactionJournal): boolean {
|
|
308
|
+
const current = readOptional(this.targetPath('handoffs.jsonl'), this.taskRoot);
|
|
309
|
+
return current !== null && sha256(current) === journal.handoffTarget.postimage.contentSha256;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
private applyHandoff(journal: CorrectiveYieldTaskTransactionJournal): 'applied' | 'confirmed' {
|
|
313
|
+
if (journal.phase !== 'applying' || journal.nextTargetOrdinal !== 0) {
|
|
314
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Corrective-yield handoff event is not the exact next target.');
|
|
315
|
+
}
|
|
316
|
+
assertTaskState(this.taskRoot, journal.taskStateSha256);
|
|
317
|
+
const file = this.targetPath('handoffs.jsonl');
|
|
318
|
+
const current = readOptional(file, this.taskRoot);
|
|
319
|
+
const currentHash = current ? sha256(current) : null;
|
|
320
|
+
if (currentHash === journal.handoffTarget.postimage.contentSha256) return 'confirmed';
|
|
321
|
+
if (currentHash !== journal.handoffTarget.preimage.contentSha256) {
|
|
322
|
+
throw new WorkflowError('STATE_CONFLICT', 'Task handoff target found neither exact preimage nor exact yield result.');
|
|
323
|
+
}
|
|
324
|
+
const owned = readOwned(this.taskRoot, journal);
|
|
325
|
+
writeAtomicDurable(file, owned);
|
|
326
|
+
assertCanonicalHandoff(this.taskRoot, journal);
|
|
327
|
+
return 'applied';
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
private advance(
|
|
331
|
+
current: CorrectiveYieldTaskTransactionJournal,
|
|
332
|
+
expectedPhase: CorrectiveYieldTransactionPhase,
|
|
333
|
+
expectedRevision: number,
|
|
334
|
+
nextPhase: CorrectiveYieldTransactionPhase,
|
|
335
|
+
nextTargetOrdinal: number,
|
|
336
|
+
completedResultSha256?: string,
|
|
337
|
+
): CorrectiveYieldTaskTransactionJournal {
|
|
338
|
+
if (current.phase !== expectedPhase || current.phaseRevision !== expectedRevision) {
|
|
339
|
+
throw new WorkflowError('STATE_CONFLICT', 'Corrective-yield phase compare-and-set conflict.');
|
|
340
|
+
}
|
|
341
|
+
assertPhaseTransition(current, nextPhase, nextTargetOrdinal);
|
|
342
|
+
if (nextTargetOrdinal >= 1) assertCanonicalHandoff(this.taskRoot, current);
|
|
343
|
+
if (nextPhase === 'completed') assertHash(completedResultSha256, 'Corrective-yield completed result hash');
|
|
344
|
+
else if (completedResultSha256 !== undefined) throw new WorkflowError('INVALID_ARGUMENT', 'Only completed yield may bind a result.');
|
|
345
|
+
const recordedAt = this.now().toISOString();
|
|
346
|
+
const previous = current.phaseHistory.at(-1)!;
|
|
347
|
+
const entry = phaseEntry(current.phaseRevision, nextPhase, nextTargetOrdinal, recordedAt, previous.entryHash, current.intentSha256);
|
|
348
|
+
const next: CorrectiveYieldTaskTransactionJournal = {
|
|
349
|
+
...structuredClone(current),
|
|
350
|
+
phase: nextPhase,
|
|
351
|
+
phaseRevision: current.phaseRevision + 1,
|
|
352
|
+
nextTargetOrdinal,
|
|
353
|
+
phaseHistory: [...current.phaseHistory, entry],
|
|
354
|
+
completedResult: nextPhase === 'completed' ? { resultSha256: completedResultSha256! } : current.completedResult,
|
|
355
|
+
updatedAt: recordedAt,
|
|
356
|
+
};
|
|
357
|
+
validateJournal(next, this.taskRoot, true);
|
|
358
|
+
writeAtomicDurable(this.journalPath(current.transactionId), jsonBytes(next));
|
|
359
|
+
return this.read(current.transactionId);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
private transactionRoot(transactionId: string): string {
|
|
363
|
+
assertHash(transactionId, 'Corrective-yield transaction id');
|
|
364
|
+
return path.join(this.taskRoot, '.yield-transactions', transactionId);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
private targetPath(relative: string): string {
|
|
368
|
+
return path.join(this.taskRoot, relative);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
export function inspectCorrectiveYieldTaskTransactions(
|
|
373
|
+
projectRoot: string,
|
|
374
|
+
trustedRoot: string,
|
|
375
|
+
): { pending: ObservedTaskTransaction[]; completed: ObservedTaskTransaction[]; corrupt: CorruptObservedTaskTransaction[] } {
|
|
376
|
+
const pending: ObservedTaskTransaction[] = [];
|
|
377
|
+
const completed: ObservedTaskTransaction[] = [];
|
|
378
|
+
const corrupt: CorruptObservedTaskTransaction[] = [];
|
|
379
|
+
const tasksRoot = path.join(path.resolve(projectRoot), 'tasks');
|
|
380
|
+
if (!existsSync(tasksRoot)) return deepFreeze({ pending, completed, corrupt });
|
|
381
|
+
assertNoSymlinkPathComponents(tasksRoot, { label: 'Corrective-yield observation root', trustedRoot, requireLeaf: true });
|
|
382
|
+
for (const task of readdirSync(tasksRoot, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
383
|
+
if (task.isSymbolicLink()) throw new WorkflowError('STATE_CORRUPT', 'Task observation root must not be a symlink.');
|
|
384
|
+
if (!task.isDirectory() || !TASK.test(task.name)) continue;
|
|
385
|
+
const taskRoot = path.join(tasksRoot, task.name);
|
|
386
|
+
const root = path.join(taskRoot, '.yield-transactions');
|
|
387
|
+
if (!existsSync(root)) continue;
|
|
388
|
+
assertNoSymlinkPathComponents(root, { label: 'Corrective-yield transaction root', trustedRoot, requireLeaf: true });
|
|
389
|
+
for (const entry of readdirSync(root, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
390
|
+
const transactionId = HASH.test(entry.name) ? entry.name : 'invalid-transaction-id';
|
|
391
|
+
if (entry.isSymbolicLink() || !entry.isDirectory() || !HASH.test(entry.name)) {
|
|
392
|
+
corrupt.push({ taskId: task.name, transactionId, reason: 'journal-invalid' });
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
try {
|
|
396
|
+
const journal = new CorrectiveYieldTaskTransactionStore({
|
|
397
|
+
taskRoot,
|
|
398
|
+
locksRoot: path.join(projectRoot, 'locks'),
|
|
399
|
+
trustedRoot,
|
|
400
|
+
}).read(entry.name);
|
|
401
|
+
(journal.phase === 'completed' ? completed : pending).push({
|
|
402
|
+
transitionId: journal.transitionId,
|
|
403
|
+
taskId: journal.taskId,
|
|
404
|
+
transactionId: journal.transactionId,
|
|
405
|
+
phase: journal.phase,
|
|
406
|
+
phaseRevision: journal.phaseRevision,
|
|
407
|
+
updatedAt: journal.updatedAt,
|
|
408
|
+
});
|
|
409
|
+
} catch {
|
|
410
|
+
corrupt.push({ taskId: task.name, transactionId, reason: 'journal-invalid' });
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return deepFreeze({
|
|
415
|
+
completed: completed.sort(orderObserved),
|
|
416
|
+
pending: pending.sort(orderObserved),
|
|
417
|
+
corrupt: corrupt.sort(orderObserved),
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
export function assertCorrectiveYieldTaskTransactionBoundary(
|
|
422
|
+
projectRoot: string,
|
|
423
|
+
trustedRoot: string,
|
|
424
|
+
taskId?: string,
|
|
425
|
+
): void {
|
|
426
|
+
const observed = inspectCorrectiveYieldTaskTransactions(projectRoot, trustedRoot);
|
|
427
|
+
const corrupt = taskId ? observed.corrupt.filter((row) => row.taskId === taskId) : observed.corrupt;
|
|
428
|
+
if (corrupt.length > 0) throw new WorkflowError('STATE_CORRUPT', 'A corrective-yield journal failed integrity validation.', { transactions: corrupt });
|
|
429
|
+
const pending = taskId ? observed.pending.filter((row) => row.taskId === taskId) : observed.pending;
|
|
430
|
+
if (pending.length > 0) throw new WorkflowError('TASK_RECOVERY_REQUIRED', 'A corrective-yield transaction requires explicit recovery.', {
|
|
431
|
+
taskIds: [...new Set(pending.map((row) => row.taskId))].sort(),
|
|
432
|
+
transactionIds: pending.map((row) => row.transactionId).sort(),
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function validateJournal(value: unknown, taskRoot: string, verifyOwned: boolean): asserts value is CorrectiveYieldTaskTransactionJournal {
|
|
437
|
+
if (validateSchema(value) !== true) throw new WorkflowError('STATE_CORRUPT', 'Corrective-yield transaction schema is invalid.');
|
|
438
|
+
const journal = value as CorrectiveYieldTaskTransactionJournal;
|
|
439
|
+
if (journal.intentSha256 !== intentHash(journal)) throw new WorkflowError('STATE_CORRUPT', 'Corrective-yield intent hash mismatch.');
|
|
440
|
+
if (journal.phaseHistory.length !== journal.phaseRevision) throw new WorkflowError('STATE_CORRUPT', 'Corrective-yield phase revision mismatch.');
|
|
441
|
+
journal.phaseHistory.forEach((entry, index) => {
|
|
442
|
+
const previous = index === 0 ? null : journal.phaseHistory[index - 1]!.entryHash;
|
|
443
|
+
if (entry.sequence !== index || entry.previousEntryHash !== previous
|
|
444
|
+
|| entry.entryHash !== phaseEntryHash(index, entry.phase, entry.nextTargetOrdinal, entry.recordedAt, previous, journal.intentSha256)
|
|
445
|
+
|| (index > 0 && entry.recordedAt < journal.phaseHistory[index - 1]!.recordedAt)) {
|
|
446
|
+
throw new WorkflowError('STATE_CORRUPT', 'Corrective-yield phase history is invalid.');
|
|
447
|
+
}
|
|
448
|
+
if (index === 0 && (entry.phase !== 'intent' || entry.nextTargetOrdinal !== 0)) {
|
|
449
|
+
throw new WorkflowError('STATE_CORRUPT', 'Corrective-yield phase history must start at intent.');
|
|
450
|
+
}
|
|
451
|
+
if (index > 0) {
|
|
452
|
+
const prior = journal.phaseHistory[index - 1]!;
|
|
453
|
+
const legal = (prior.phase === 'intent' && entry.phase === 'applying' && prior.nextTargetOrdinal === 0 && entry.nextTargetOrdinal === 0)
|
|
454
|
+
|| (prior.phase === 'applying' && entry.phase === 'applying' && entry.nextTargetOrdinal === prior.nextTargetOrdinal + 1 && entry.nextTargetOrdinal <= 2)
|
|
455
|
+
|| (prior.phase === 'applying' && prior.nextTargetOrdinal === 2 && entry.phase === 'published-pending-readback' && entry.nextTargetOrdinal === 2)
|
|
456
|
+
|| (prior.phase === 'published-pending-readback' && entry.phase === 'completed' && entry.nextTargetOrdinal === 2);
|
|
457
|
+
if (!legal) throw new WorkflowError('STATE_CORRUPT', 'Corrective-yield phase topology is invalid.');
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
const last = journal.phaseHistory.at(-1)!;
|
|
461
|
+
if (last.phase !== journal.phase || last.nextTargetOrdinal !== journal.nextTargetOrdinal || last.recordedAt !== journal.updatedAt
|
|
462
|
+
|| (journal.phase === 'completed') !== (journal.completedResult !== null)) {
|
|
463
|
+
throw new WorkflowError('STATE_CORRUPT', 'Corrective-yield current phase projection is invalid.');
|
|
464
|
+
}
|
|
465
|
+
if (journal.phaseRevision > 6 || Date.parse(journal.admittedAt) >= Date.parse(journal.writerLeaseExpiresAt)) {
|
|
466
|
+
throw new WorkflowError('STATE_CORRUPT', 'Corrective-yield timing or phase cardinality is invalid.');
|
|
467
|
+
}
|
|
468
|
+
const before = journal.handoffTarget.preimage;
|
|
469
|
+
if (before.exists !== (before.contentSha256 !== null) || before.exists !== (before.contentPath !== null)
|
|
470
|
+
|| (before.exists ? before.byteLength < 1 : before.byteLength !== 0)) {
|
|
471
|
+
throw new WorkflowError('STATE_CORRUPT', 'Corrective-yield preimage ownership is invalid.');
|
|
472
|
+
}
|
|
473
|
+
if (journal.completedResult && journal.completedResult.resultSha256 !== resultHash(journal)) {
|
|
474
|
+
throw new WorkflowError('STATE_CORRUPT', 'Corrective-yield completed result formula mismatch.');
|
|
475
|
+
}
|
|
476
|
+
if (verifyOwned) {
|
|
477
|
+
const owned = readOwned(taskRoot, journal);
|
|
478
|
+
const preimage = readOwnedPreimage(taskRoot, journal);
|
|
479
|
+
const anchors = validateAppend(preimage, owned, journal.transactionId, journal.actor, journal.writerLeaseTokenSha256);
|
|
480
|
+
if (anchors.previousEventHash !== journal.handoffTarget.previousEventHash
|
|
481
|
+
|| anchors.resultEventHash !== journal.handoffTarget.resultEventHash) {
|
|
482
|
+
throw new WorkflowError('STATE_CORRUPT', 'Corrective-yield handoff anchors mismatch.');
|
|
483
|
+
}
|
|
484
|
+
if (journal.phase === 'completed') {
|
|
485
|
+
assertCanonicalHandoffRetained(taskRoot, journal);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function assertPhaseTransition(current: CorrectiveYieldTaskTransactionJournal, phase: CorrectiveYieldTransactionPhase, ordinal: number): void {
|
|
491
|
+
const allowed = (current.phase === 'intent' && phase === 'applying' && ordinal === 0)
|
|
492
|
+
|| (current.phase === 'applying' && phase === 'applying' && ordinal === current.nextTargetOrdinal + 1 && ordinal <= 2)
|
|
493
|
+
|| (current.phase === 'applying' && current.nextTargetOrdinal === 2 && phase === 'published-pending-readback' && ordinal === 2)
|
|
494
|
+
|| (current.phase === 'published-pending-readback' && phase === 'completed' && ordinal === 2);
|
|
495
|
+
if (!allowed) throw new WorkflowError('INVALID_ARGUMENT', 'Invalid corrective-yield phase transition.');
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function validateAppend(
|
|
499
|
+
before: Buffer | null,
|
|
500
|
+
after: Buffer,
|
|
501
|
+
transactionId: string,
|
|
502
|
+
actor: string,
|
|
503
|
+
tokenSha256: string,
|
|
504
|
+
): { previousEventHash: string | null; resultEventHash: string } {
|
|
505
|
+
if ((before && !after.subarray(0, before.byteLength).equals(before)) || after.byteLength <= (before?.byteLength ?? 0)) {
|
|
506
|
+
throw new WorkflowError('INVALID_ARGUMENT', 'Corrective-yield handoff postimage must append exact bytes.');
|
|
507
|
+
}
|
|
508
|
+
const beforeLines = jsonLines(before);
|
|
509
|
+
const afterLines = jsonLines(after);
|
|
510
|
+
if (afterLines.length !== beforeLines.length + 1) throw new WorkflowError('INVALID_ARGUMENT', 'Corrective-yield must append exactly one handoff event.');
|
|
511
|
+
const event = afterLines.at(-1)!;
|
|
512
|
+
const previousEventHash = beforeLines.length === 0 ? null : eventHash(beforeLines.at(-1)!);
|
|
513
|
+
if (event.eventId !== `HOF-${transactionId}` || event.eventType !== 'yield' || event.actor !== actor
|
|
514
|
+
|| event.writerLeaseTokenHash !== tokenSha256 || event.targetActor !== null || event.handback !== null
|
|
515
|
+
|| event.previousEventHash !== previousEventHash) {
|
|
516
|
+
throw new WorkflowError('INVALID_ARGUMENT', 'Corrective-yield event does not bind the exact claimant transaction.');
|
|
517
|
+
}
|
|
518
|
+
return { previousEventHash, resultEventHash: eventHash(event) };
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function intentHash(journal: CorrectiveYieldTaskTransactionJournal): string {
|
|
522
|
+
const { intentSha256: _intent, phase: _phase, phaseRevision: _revision, nextTargetOrdinal: _ordinal,
|
|
523
|
+
phaseHistory: _history, completedResult: _completed, updatedAt: _updated, ...intent } = journal;
|
|
524
|
+
return hashCanonical({ domain: 'codex-workflow/corrective-yield-intent/v1', intent });
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function resultHash(journal: CorrectiveYieldTaskTransactionJournal): string {
|
|
528
|
+
return hashCanonical({
|
|
529
|
+
domain: 'codex-workflow/corrective-yield-result/v1',
|
|
530
|
+
transactionId: journal.transactionId,
|
|
531
|
+
intentSha256: journal.intentSha256,
|
|
532
|
+
taskStateSha256: journal.taskStateSha256,
|
|
533
|
+
handoffSha256: journal.handoffTarget.postimage.contentSha256,
|
|
534
|
+
yieldEventHash: journal.handoffTarget.resultEventHash,
|
|
535
|
+
writerLeaseReleased: true,
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function phaseEntry(
|
|
540
|
+
sequence: number,
|
|
541
|
+
phase: CorrectiveYieldTransactionPhase,
|
|
542
|
+
nextTargetOrdinal: number,
|
|
543
|
+
recordedAt: string,
|
|
544
|
+
previousEntryHash: string | null,
|
|
545
|
+
intentSha256: string,
|
|
546
|
+
) {
|
|
547
|
+
return { sequence, phase, nextTargetOrdinal, recordedAt, previousEntryHash,
|
|
548
|
+
entryHash: phaseEntryHash(sequence, phase, nextTargetOrdinal, recordedAt, previousEntryHash, intentSha256) };
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function phaseEntryHash(
|
|
552
|
+
sequence: number,
|
|
553
|
+
phase: CorrectiveYieldTransactionPhase,
|
|
554
|
+
nextTargetOrdinal: number,
|
|
555
|
+
recordedAt: string,
|
|
556
|
+
previousEntryHash: string | null,
|
|
557
|
+
intentSha256: string,
|
|
558
|
+
): string {
|
|
559
|
+
return hashCanonical({ domain: 'codex-workflow/corrective-yield-phase/v1', sequence, phase, nextTargetOrdinal, recordedAt, previousEntryHash, intentSha256 });
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function snapshot(content: Buffer | null): SnapshotRecord {
|
|
563
|
+
return content === null
|
|
564
|
+
? { exists: false, contentSha256: null, byteLength: 0, contentPath: null }
|
|
565
|
+
: { exists: true, contentSha256: sha256(content), byteLength: content.byteLength, contentPath: 'preimages/0.bin' };
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function assertIntent(input: CreateCorrectiveYieldIntentInput): void {
|
|
569
|
+
if (!PROJECT.test(input.projectId) || !TASK.test(input.taskId) || !HASH.test(input.transactionId)
|
|
570
|
+
|| !HASH.test(input.taskStateSha256) || !HASH.test(input.writerLeaseTokenSha256)
|
|
571
|
+
|| !HASH.test(input.writerLeaseContentSha256) || !input.actor.trim() || input.actor.length > 256
|
|
572
|
+
|| !(input.handoffPostimage instanceof Uint8Array) || input.handoffPostimage.byteLength === 0
|
|
573
|
+
|| input.handoffPostimage.byteLength > 1_048_576) {
|
|
574
|
+
throw new WorkflowError('INVALID_ARGUMENT', 'Corrective-yield intent is invalid.');
|
|
575
|
+
}
|
|
576
|
+
assertIso(input.writerLeaseExpiresAt, 'Corrective-yield writer lease expiry');
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function assertExactRecord(value: unknown, keys: string[], label: string): asserts value is Record<string, unknown> {
|
|
580
|
+
if (!value || typeof value !== 'object' || Array.isArray(value) || Object.getPrototypeOf(value) !== Object.prototype) {
|
|
581
|
+
throw new WorkflowError('INVALID_ARGUMENT', `${label} must be a plain object.`);
|
|
582
|
+
}
|
|
583
|
+
const actual = Object.keys(value).sort();
|
|
584
|
+
const expected = [...keys].sort();
|
|
585
|
+
if (actual.length !== expected.length || actual.some((key, index) => key !== expected[index])) {
|
|
586
|
+
throw new WorkflowError('INVALID_ARGUMENT', `${label} has unknown or missing fields.`);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function assertCanonicalHandoff(taskRoot: string, journal: CorrectiveYieldTaskTransactionJournal): void {
|
|
591
|
+
const current = readOptional(path.join(taskRoot, journal.handoffTarget.relativePath), taskRoot);
|
|
592
|
+
if (!current || sha256(current) !== journal.handoffTarget.postimage.contentSha256) {
|
|
593
|
+
throw new WorkflowError('STATE_CONFLICT', 'Corrective-yield handoff target does not match its exact result.');
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
function assertCanonicalHandoffRetained(taskRoot: string, journal: CorrectiveYieldTaskTransactionJournal): void {
|
|
598
|
+
const current = readOptional(path.join(taskRoot, journal.handoffTarget.relativePath), taskRoot);
|
|
599
|
+
const owned = readOwned(taskRoot, journal);
|
|
600
|
+
if (!current || current.byteLength < owned.byteLength
|
|
601
|
+
|| !current.subarray(0, owned.byteLength).equals(owned)) {
|
|
602
|
+
throw new WorkflowError('STATE_CONFLICT', 'Corrective-yield handoff result is not retained in canonical history.');
|
|
603
|
+
}
|
|
604
|
+
jsonLines(current);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
function assertTaskState(taskRoot: string, expectedHash: string): void {
|
|
608
|
+
if (sha256(readRequired(path.join(taskRoot, 'state.json'), taskRoot)) !== expectedHash) {
|
|
609
|
+
throw new WorkflowError('STATE_CONFLICT', 'Task state changed across corrective-yield.');
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function readOwned(taskRoot: string, journal: CorrectiveYieldTaskTransactionJournal): Buffer {
|
|
614
|
+
const file = path.join(taskRoot, '.yield-transactions', journal.transactionId, journal.handoffTarget.postimage.contentPath);
|
|
615
|
+
const content = readRequired(file, taskRoot);
|
|
616
|
+
if (content.byteLength !== journal.handoffTarget.postimage.byteLength || sha256(content) !== journal.handoffTarget.postimage.contentSha256) {
|
|
617
|
+
throw new WorkflowError('STATE_CORRUPT', 'Corrective-yield owned postimage is corrupt.');
|
|
618
|
+
}
|
|
619
|
+
return content;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function readOwnedPreimage(taskRoot: string, journal: CorrectiveYieldTaskTransactionJournal): Buffer | null {
|
|
623
|
+
if (!journal.handoffTarget.preimage.exists) return null;
|
|
624
|
+
const file = path.join(taskRoot, '.yield-transactions', journal.transactionId, journal.handoffTarget.preimage.contentPath!);
|
|
625
|
+
const content = readRequired(file, taskRoot);
|
|
626
|
+
if (content.byteLength !== journal.handoffTarget.preimage.byteLength || sha256(content) !== journal.handoffTarget.preimage.contentSha256) {
|
|
627
|
+
throw new WorkflowError('STATE_CORRUPT', 'Corrective-yield owned preimage is corrupt.');
|
|
628
|
+
}
|
|
629
|
+
return content;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
function jsonLines(content: Buffer | null): Array<Record<string, unknown>> {
|
|
633
|
+
if (!content) return [];
|
|
634
|
+
const text = content.toString('utf8');
|
|
635
|
+
if (!text.endsWith('\n')) throw new WorkflowError('INVALID_ARGUMENT', 'Corrective-yield handoff bytes must end with newline.');
|
|
636
|
+
const events = text.slice(0, -1).split('\n').map((line) => {
|
|
637
|
+
const value = JSON.parse(line) as unknown;
|
|
638
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new WorkflowError('INVALID_ARGUMENT', 'Corrective-yield handoff line is invalid.');
|
|
639
|
+
return value as Record<string, unknown>;
|
|
640
|
+
});
|
|
641
|
+
let previousEventHash: string | null = null;
|
|
642
|
+
for (const event of events) {
|
|
643
|
+
if (event.previousEventHash !== previousEventHash
|
|
644
|
+
|| event.eventHash !== computeSidecarEventHash(event as never)) {
|
|
645
|
+
throw new WorkflowError('INVALID_ARGUMENT', 'Corrective-yield handoff event chain is invalid.');
|
|
646
|
+
}
|
|
647
|
+
previousEventHash = eventHash(event);
|
|
648
|
+
}
|
|
649
|
+
return events;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
function eventHash(event: Record<string, unknown>): string {
|
|
653
|
+
if (typeof event.eventHash !== 'string' || !HASH.test(event.eventHash)) throw new WorkflowError('INVALID_ARGUMENT', 'Handoff event hash is invalid.');
|
|
654
|
+
return event.eventHash;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function readOptional(file: string, trustedRoot: string): Buffer | null {
|
|
658
|
+
assertNoSymlinkPathComponents(file, { label: 'Corrective-yield target', trustedRoot, requireLeaf: true });
|
|
659
|
+
return existsSync(file) ? readFileSync(file) : null;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
function readRequired(file: string, trustedRoot: string): Buffer {
|
|
663
|
+
const value = readOptional(file, trustedRoot);
|
|
664
|
+
if (!value) throw new WorkflowError('STATE_CORRUPT', `Corrective-yield required file is missing: ${file}`);
|
|
665
|
+
return value;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
function writeNewDurable(file: string, content: Buffer): void {
|
|
669
|
+
let descriptor: number | null = null;
|
|
670
|
+
try {
|
|
671
|
+
descriptor = openSync(file, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | constants.O_NOFOLLOW, 0o600);
|
|
672
|
+
writeSync(descriptor, content);
|
|
673
|
+
fsyncSync(descriptor);
|
|
674
|
+
} finally { if (descriptor !== null) closeSync(descriptor); }
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
function writeAtomicDurable(file: string, content: Buffer): void {
|
|
678
|
+
const temporary = path.join(path.dirname(file), `.${path.basename(file)}.${randomUUID()}.tmp`);
|
|
679
|
+
let descriptor: number | null = null;
|
|
680
|
+
try {
|
|
681
|
+
descriptor = openSync(temporary, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | constants.O_NOFOLLOW, 0o600);
|
|
682
|
+
writeSync(descriptor, content);
|
|
683
|
+
fsyncSync(descriptor);
|
|
684
|
+
closeSync(descriptor);
|
|
685
|
+
descriptor = null;
|
|
686
|
+
renameSync(temporary, file);
|
|
687
|
+
fsyncDirectory(path.dirname(file));
|
|
688
|
+
} finally {
|
|
689
|
+
if (descriptor !== null) closeSync(descriptor);
|
|
690
|
+
rmSync(temporary, { force: true });
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
function ensureDirectorySafe(directory: string, trustedRoot: string): void {
|
|
695
|
+
if (!existsSync(directory)) mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
696
|
+
assertNoSymlinkPathComponents(directory, { label: 'Corrective-yield transaction directory', trustedRoot, requireLeaf: true });
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
function fsyncDirectory(directory: string): void {
|
|
700
|
+
const descriptor = openSync(directory, 'r');
|
|
701
|
+
try { fsyncSync(descriptor); } finally { closeSync(descriptor); }
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
function assertTaskRoot(taskRoot: string, trustedRoot: string): void {
|
|
705
|
+
const resolvedTask = path.resolve(taskRoot);
|
|
706
|
+
const resolvedTrusted = path.resolve(trustedRoot);
|
|
707
|
+
if (!resolvedTask.startsWith(`${resolvedTrusted}${path.sep}`)) throw new WorkflowError('INVALID_ARGUMENT', 'Corrective-yield Task root is outside trusted state.');
|
|
708
|
+
assertNoSymlinkPathComponents(resolvedTask, { label: 'Corrective-yield Task root', trustedRoot: resolvedTrusted, requireLeaf: true });
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
function assertHash(value: unknown, label: string): asserts value is string {
|
|
712
|
+
if (typeof value !== 'string' || !HASH.test(value)) throw new WorkflowError('INVALID_ARGUMENT', `${label} must be lowercase SHA-256.`);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
function assertIso(value: string, label: string): void {
|
|
716
|
+
const parsed = Date.parse(value);
|
|
717
|
+
if (!Number.isFinite(parsed) || new Date(parsed).toISOString() !== value) throw new WorkflowError('INVALID_ARGUMENT', `${label} must be canonical UTC.`);
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
function jsonBytes(value: unknown): Buffer {
|
|
721
|
+
return Buffer.from(`${JSON.stringify(value, null, 2)}\n`, 'utf8');
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
function sha256(value: Buffer | string): string {
|
|
725
|
+
return createHash('sha256').update(value).digest('hex');
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
function orderObserved(left: { taskId: string; transactionId: string }, right: { taskId: string; transactionId: string }): number {
|
|
729
|
+
return left.taskId.localeCompare(right.taskId) || left.transactionId.localeCompare(right.transactionId);
|
|
730
|
+
}
|