codex-orchestrator 2.0.11 → 2.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/README.md +25 -51
- package/dist/src/index.d.ts +2 -8
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -4
- package/dist/src/index.js.map +1 -1
- package/dist/src/v2/acceptance-proof.d.ts +46 -31
- package/dist/src/v2/acceptance-proof.d.ts.map +1 -1
- package/dist/src/v2/acceptance-proof.js +157 -195
- package/dist/src/v2/acceptance-proof.js.map +1 -1
- package/dist/src/v2/active-attempt.d.ts +94 -0
- package/dist/src/v2/active-attempt.d.ts.map +1 -0
- package/dist/src/v2/active-attempt.js +200 -0
- package/dist/src/v2/active-attempt.js.map +1 -0
- package/dist/src/v2/adapters/command.d.ts +6 -0
- package/dist/src/v2/adapters/command.d.ts.map +1 -1
- package/dist/src/v2/adapters/command.js +43 -2
- package/dist/src/v2/adapters/command.js.map +1 -1
- package/dist/src/v2/candidate.d.ts +15 -31
- package/dist/src/v2/candidate.d.ts.map +1 -1
- package/dist/src/v2/candidate.js +7 -29
- package/dist/src/v2/candidate.js.map +1 -1
- package/dist/src/v2/checked-change.d.ts +3 -2
- package/dist/src/v2/checked-change.d.ts.map +1 -1
- package/dist/src/v2/checked-change.js +4 -3
- package/dist/src/v2/checked-change.js.map +1 -1
- package/dist/src/v2/cli-contract.d.ts +1 -1
- package/dist/src/v2/cli-contract.d.ts.map +1 -1
- package/dist/src/v2/cli-contract.js +4 -6
- package/dist/src/v2/cli-contract.js.map +1 -1
- package/dist/src/v2/cli.d.ts +8 -0
- package/dist/src/v2/cli.d.ts.map +1 -1
- package/dist/src/v2/cli.js +13 -0
- package/dist/src/v2/cli.js.map +1 -1
- package/dist/src/v2/code-review-report.d.ts +10 -18
- package/dist/src/v2/code-review-report.d.ts.map +1 -1
- package/dist/src/v2/code-review-report.js +63 -60
- package/dist/src/v2/code-review-report.js.map +1 -1
- package/dist/src/v2/codex-process.d.ts +6 -2
- package/dist/src/v2/codex-process.d.ts.map +1 -1
- package/dist/src/v2/codex-process.js +25 -9
- package/dist/src/v2/codex-process.js.map +1 -1
- package/dist/src/v2/config.d.ts +0 -2
- package/dist/src/v2/config.d.ts.map +1 -1
- package/dist/src/v2/config.js +3 -6
- package/dist/src/v2/config.js.map +1 -1
- package/dist/src/v2/contained-report-operation.d.ts +41 -196
- package/dist/src/v2/contained-report-operation.d.ts.map +1 -1
- package/dist/src/v2/contained-report-operation.js +139 -466
- package/dist/src/v2/contained-report-operation.js.map +1 -1
- package/dist/src/v2/containment.d.ts +1 -0
- package/dist/src/v2/containment.d.ts.map +1 -1
- package/dist/src/v2/containment.js +12 -2
- package/dist/src/v2/containment.js.map +1 -1
- package/dist/src/v2/delivery-authority.d.ts +26 -0
- package/dist/src/v2/delivery-authority.d.ts.map +1 -0
- package/dist/src/v2/delivery-authority.js +44 -0
- package/dist/src/v2/delivery-authority.js.map +1 -0
- package/dist/src/v2/direct-delivery.d.ts +16 -36
- package/dist/src/v2/direct-delivery.d.ts.map +1 -1
- package/dist/src/v2/direct-delivery.js +135 -122
- package/dist/src/v2/direct-delivery.js.map +1 -1
- package/dist/src/v2/immutable-workflow-publisher.d.ts.map +1 -1
- package/dist/src/v2/immutable-workflow-publisher.js +3 -1
- package/dist/src/v2/immutable-workflow-publisher.js.map +1 -1
- package/dist/src/v2/implementation-report.d.ts +3 -1
- package/dist/src/v2/implementation-report.d.ts.map +1 -1
- package/dist/src/v2/implementation-report.js +28 -10
- package/dist/src/v2/implementation-report.js.map +1 -1
- package/dist/src/v2/implementation-reviewer.d.ts +41 -12
- package/dist/src/v2/implementation-reviewer.d.ts.map +1 -1
- package/dist/src/v2/implementation-reviewer.js +114 -42
- package/dist/src/v2/implementation-reviewer.js.map +1 -1
- package/dist/src/v2/pending-effect-settlement.d.ts +44 -0
- package/dist/src/v2/pending-effect-settlement.d.ts.map +1 -0
- package/dist/src/v2/pending-effect-settlement.js +69 -0
- package/dist/src/v2/pending-effect-settlement.js.map +1 -0
- package/dist/src/v2/process-identity.d.ts +45 -0
- package/dist/src/v2/process-identity.d.ts.map +1 -0
- package/dist/src/v2/process-identity.js +118 -0
- package/dist/src/v2/process-identity.js.map +1 -0
- package/dist/src/v2/proof-report.d.ts +2 -1
- package/dist/src/v2/proof-report.d.ts.map +1 -1
- package/dist/src/v2/proof-report.js +10 -4
- package/dist/src/v2/proof-report.js.map +1 -1
- package/dist/src/v2/review-feedback-coordinator.d.ts +1 -1
- package/dist/src/v2/review-feedback-coordinator.d.ts.map +1 -1
- package/dist/src/v2/review-feedback-coordinator.js +1 -1
- package/dist/src/v2/review-feedback-coordinator.js.map +1 -1
- package/dist/src/v2/review-feedback.d.ts +14 -20
- package/dist/src/v2/review-feedback.d.ts.map +1 -1
- package/dist/src/v2/review-feedback.js +45 -87
- package/dist/src/v2/review-feedback.js.map +1 -1
- package/dist/src/v2/run-issue.d.ts +129 -88
- package/dist/src/v2/run-issue.d.ts.map +1 -1
- package/dist/src/v2/run-issue.js +1965 -2381
- package/dist/src/v2/run-issue.js.map +1 -1
- package/dist/src/v2/run-state-projections.d.ts +84 -0
- package/dist/src/v2/run-state-projections.d.ts.map +1 -0
- package/dist/src/v2/run-state-projections.js +142 -0
- package/dist/src/v2/run-state-projections.js.map +1 -0
- package/dist/src/v2/run-store.d.ts +99 -81
- package/dist/src/v2/run-store.d.ts.map +1 -1
- package/dist/src/v2/run-store.js +245 -542
- package/dist/src/v2/run-store.js.map +1 -1
- package/dist/src/v2/runtime-assets.d.ts +3 -0
- package/dist/src/v2/runtime-assets.d.ts.map +1 -1
- package/dist/src/v2/runtime-assets.js +104 -0
- package/dist/src/v2/runtime-assets.js.map +1 -1
- package/dist/src/v2/runtime.d.ts +56 -44
- package/dist/src/v2/runtime.d.ts.map +1 -1
- package/dist/src/v2/runtime.js +383 -503
- package/dist/src/v2/runtime.js.map +1 -1
- package/dist/src/v2/setup.js +0 -2
- package/dist/src/v2/setup.js.map +1 -1
- package/dist/src/v2/validation-progression.d.ts +70 -0
- package/dist/src/v2/validation-progression.d.ts.map +1 -0
- package/dist/src/v2/validation-progression.js +247 -0
- package/dist/src/v2/validation-progression.js.map +1 -0
- package/dist/src/v2/workflow-assets.d.ts +9 -3
- package/dist/src/v2/workflow-assets.d.ts.map +1 -1
- package/dist/src/v2/workflow-assets.js +256 -43
- package/dist/src/v2/workflow-assets.js.map +1 -1
- package/internal-workflow/docs/agents/bug-workflow-routing.md +9 -7
- package/internal-workflow/docs/agents/coding-skill-routing.md +170 -120
- package/internal-workflow/docs/agents/tool-usage.md +23 -12
- package/internal-workflow/manifest.json +1 -1
- package/internal-workflow/operations/code-review/SKILL.md +34 -15
- package/internal-workflow/operations/implementation/SKILL.md +21 -16
- package/internal-workflow/profiles/implementer.toml +9 -0
- package/internal-workflow/profiles/review_coordinator.toml +9 -0
- package/internal-workflow/profiles/spec_reviewer.toml +9 -0
- package/internal-workflow/profiles/standards_reviewer.toml +9 -0
- package/internal-workflow/schemas/code-review-v1.json +1 -1
- package/internal-workflow/schemas/implementation-report-v1.json +1 -1
- package/internal-workflow/schemas/proof-report-v1.json +1 -1
- package/internal-workflow/skills/bug-root-cause-explainer/SKILL.md +114 -0
- package/internal-workflow/skills/bug-root-cause-explainer/agents/openai.yaml +7 -0
- package/internal-workflow/skills/bug-root-cause-explainer/evals/evals.json +18 -0
- package/internal-workflow/skills/code-review/SKILL.md +84 -306
- package/internal-workflow/skills/code-review/agents/openai.yaml +5 -3
- package/internal-workflow/skills/code-review/evals/evals.json +83 -0
- package/internal-workflow/skills/code-review/references/standards-smells.md +41 -0
- package/internal-workflow/skills/diagnosing-bugs/SKILL.md +69 -32
- package/internal-workflow/skills/diagnosing-bugs/agents/openai.yaml +2 -2
- package/internal-workflow/skills/diagnosing-bugs/evals/evals.json +63 -0
- package/internal-workflow/skills/grilling/SKILL.md +51 -0
- package/internal-workflow/skills/grilling/agents/openai.yaml +6 -0
- package/internal-workflow/skills/grilling/evals/evals.json +47 -0
- package/internal-workflow/skills/implement/SKILL.md +135 -0
- package/internal-workflow/skills/implement/agents/openai.yaml +6 -0
- package/internal-workflow/skills/implement/evals/evals.json +150 -0
- package/internal-workflow/skills/plan/SKILL.md +59 -0
- package/internal-workflow/skills/plan/agents/openai.yaml +6 -0
- package/internal-workflow/skills/plan/evals/evals.json +36 -0
- package/internal-workflow/skills/prototype/LOGIC.md +130 -0
- package/internal-workflow/skills/prototype/SKILL.md +69 -0
- package/internal-workflow/skills/prototype/UI.md +157 -0
- package/internal-workflow/skills/prototype/agents/openai.yaml +6 -0
- package/internal-workflow/skills/prototype/evals/evals.json +67 -0
- package/internal-workflow/skills/research/SKILL.md +110 -0
- package/internal-workflow/skills/research/agents/openai.yaml +6 -0
- package/internal-workflow/skills/research/evals/evals.json +49 -0
- package/internal-workflow/skills/tdd/SKILL.md +72 -67
- package/internal-workflow/skills/tdd/agents/openai.yaml +2 -2
- package/internal-workflow/skills/tdd/evals/evals.json +12 -0
- package/internal-workflow/skills/tdd/mocking.md +48 -1
- package/internal-workflow/skills/tdd/refactoring.md +3 -3
- package/internal-workflow/skills/tickets-orchestrator/SKILL.md +199 -0
- package/internal-workflow/skills/tickets-orchestrator/agents/openai.yaml +6 -0
- package/internal-workflow/skills/tickets-orchestrator/evals/evals.json +126 -0
- package/internal-workflow/skills/tickets-orchestrator/references/delegate-integrate.md +83 -0
- package/internal-workflow/skills/tickets-orchestrator/references/finish-delivery.md +69 -0
- package/internal-workflow/skills/tickets-orchestrator/references/stop-completion.md +63 -0
- package/internal-workflow/skills/to-spec/SKILL.md +133 -0
- package/internal-workflow/skills/to-spec/agents/openai.yaml +6 -0
- package/internal-workflow/skills/to-spec/evals/evals.json +24 -0
- package/internal-workflow/skills/to-tickets/SKILL.md +189 -0
- package/internal-workflow/skills/to-tickets/agents/openai.yaml +6 -0
- package/internal-workflow/skills/to-tickets/evals/evals.json +79 -0
- package/internal-workflow/skills/to-tickets/references/publishing-details.md +117 -0
- package/package.json +1 -1
- package/dist/src/v2/proof-store.d.ts +0 -54
- package/dist/src/v2/proof-store.d.ts.map +0 -1
- package/dist/src/v2/proof-store.js +0 -301
- package/dist/src/v2/proof-store.js.map +0 -1
- package/dist/src/v2/route-continuations.d.ts +0 -32
- package/dist/src/v2/route-continuations.d.ts.map +0 -1
- package/dist/src/v2/route-continuations.js +0 -2
- package/dist/src/v2/route-continuations.js.map +0 -1
- package/dist/src/v2/route-coordinator.d.ts +0 -72
- package/dist/src/v2/route-coordinator.d.ts.map +0 -1
- package/dist/src/v2/route-coordinator.js +0 -275
- package/dist/src/v2/route-coordinator.js.map +0 -1
- package/dist/src/v2/route-decision.d.ts +0 -120
- package/dist/src/v2/route-decision.d.ts.map +0 -1
- package/dist/src/v2/route-decision.js +0 -380
- package/dist/src/v2/route-decision.js.map +0 -1
- package/dist/src/v2/spec-coordinator.d.ts +0 -73
- package/dist/src/v2/spec-coordinator.d.ts.map +0 -1
- package/dist/src/v2/spec-coordinator.js +0 -126
- package/dist/src/v2/spec-coordinator.js.map +0 -1
- package/dist/src/v2/spec-delivery.d.ts +0 -112
- package/dist/src/v2/spec-delivery.d.ts.map +0 -1
- package/dist/src/v2/spec-delivery.js +0 -336
- package/dist/src/v2/spec-delivery.js.map +0 -1
- package/dist/src/v2/triage-route.d.ts +0 -68
- package/dist/src/v2/triage-route.d.ts.map +0 -1
- package/dist/src/v2/triage-route.js +0 -223
- package/dist/src/v2/triage-route.js.map +0 -1
- package/dist/src/v2/waiting-human-coordinator.d.ts +0 -49
- package/dist/src/v2/waiting-human-coordinator.d.ts.map +0 -1
- package/dist/src/v2/waiting-human-coordinator.js +0 -509
- package/dist/src/v2/waiting-human-coordinator.js.map +0 -1
- package/dist/src/v2/waiting-human.d.ts +0 -143
- package/dist/src/v2/waiting-human.d.ts.map +0 -1
- package/dist/src/v2/waiting-human.js +0 -408
- package/dist/src/v2/waiting-human.js.map +0 -1
- package/internal-workflow/docs/agents/contract-test-ledger.md +0 -71
- package/internal-workflow/docs/agents/review-gates.md +0 -42
- package/internal-workflow/docs/agents/review-protocol.md +0 -98
- package/internal-workflow/evals/coding-skill-evals.json +0 -373
- package/internal-workflow/operations/ambiguity-review/SKILL.md +0 -5
- package/internal-workflow/operations/qualification-repair/SKILL.md +0 -17
- package/internal-workflow/operations/spec-author/SKILL.md +0 -12
- package/internal-workflow/operations/spec-review/SKILL.md +0 -12
- package/internal-workflow/operations/triage/SKILL.md +0 -12
- package/internal-workflow/profiles/analyst_deep.toml +0 -9
- package/internal-workflow/profiles/implementer_standard.toml +0 -9
- package/internal-workflow/profiles/proof_agent.toml +0 -8
- package/internal-workflow/profiles/reviewer_deep.toml +0 -9
- package/internal-workflow/profiles/reviewer_standard.toml +0 -9
- package/internal-workflow/schemas/ambiguity-review-v1.json +0 -1
- package/internal-workflow/schemas/spec-author-v1.json +0 -1
- package/internal-workflow/schemas/spec-review-v1.json +0 -30
- package/internal-workflow/schemas/triage-route-v1.json +0 -1
- package/internal-workflow/skills/agent-auto/SKILL.md +0 -19
- package/internal-workflow/skills/agent-auto/agents/openai.yaml +0 -6
- package/internal-workflow/skills/code-debugger/SKILL.md +0 -122
- package/internal-workflow/skills/code-debugger/agents/openai.yaml +0 -7
- package/internal-workflow/skills/code-review/references/bug-classes.md +0 -56
- package/internal-workflow/skills/code-review/references/cleanup-lens.md +0 -52
- package/internal-workflow/skills/code-review/references/framework-lenses.md +0 -34
- package/internal-workflow/skills/code-review/references/targeted-recipes.md +0 -49
- package/internal-workflow/skills/implementation-spec-maker/SKILL.md +0 -107
- package/internal-workflow/skills/implementation-spec-maker/agents/openai.yaml +0 -6
- package/internal-workflow/skills/implementation-spec-maker/references/source-modes.md +0 -32
- package/internal-workflow/skills/implementation-spec-maker/references/spec-template.md +0 -146
- package/internal-workflow/skills/implementation-spec-review/SKILL.md +0 -131
- package/internal-workflow/skills/implementation-spec-review/agents/openai.yaml +0 -6
- package/internal-workflow/skills/implementation-spec-review/evals/evals.json +0 -78
- package/internal-workflow/skills/implementation-spec-review/references/review-loop.md +0 -121
- package/internal-workflow/skills/small-task-implementer/SKILL.md +0 -112
- package/internal-workflow/skills/small-task-implementer/agents/openai.yaml +0 -6
- package/internal-workflow/skills/spec-implementer/SKILL.md +0 -133
- package/internal-workflow/skills/spec-implementer/agents/openai.yaml +0 -6
- package/internal-workflow/skills/spec-implementer/evals/evals.json +0 -30
- package/internal-workflow/skills/spec-implementer/references/review-loop.md +0 -100
- package/internal-workflow/skills/triage/AGENT-BRIEF.md +0 -192
- package/internal-workflow/skills/triage/OUT-OF-SCOPE.md +0 -101
- package/internal-workflow/skills/triage/SKILL.md +0 -134
- package/internal-workflow/skills/triage/agents/openai.yaml +0 -6
package/dist/src/v2/run-store.js
CHANGED
|
@@ -1,208 +1,104 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { validateWaitingHumanExecution } from './waiting-human.js';
|
|
1
|
+
import { validateActiveAttempt } from './active-attempt.js';
|
|
2
|
+
import { validateDirectReview } from './direct-delivery.js';
|
|
3
|
+
import { validateDeliveryAuthority } from './delivery-authority.js';
|
|
4
|
+
import { constants } from 'node:fs';
|
|
6
5
|
import { posix } from 'node:path';
|
|
7
|
-
import {
|
|
6
|
+
import { open } from 'node:fs/promises';
|
|
8
7
|
import { AtomicStateFile } from './atomic-store.js';
|
|
9
|
-
import { writeDurableAtomicFile } from './adapters/durable-atomic-file.js';
|
|
10
8
|
import { canonicalJson, sha256 } from './containment.js';
|
|
11
|
-
import { validateCandidateBinding,
|
|
12
|
-
import {
|
|
9
|
+
import { validateCandidateBinding, validateCandidateMaterialization } from './candidate.js';
|
|
10
|
+
import { validateReviewFeedbackRunData, } from './review-feedback.js';
|
|
13
11
|
const SHA256_PATTERN = /^[0-9a-f]{64}$/;
|
|
14
12
|
const GIT_SHA_PATTERN = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
|
|
15
13
|
const UUID_V4_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
14
|
+
export function createPendingEffect(effect) {
|
|
15
|
+
return { ...structuredClone(effect), effectId: sha256(canonicalJson(effect)) };
|
|
16
|
+
}
|
|
16
17
|
export class WorkflowGenerationUnrecoverableError extends Error {
|
|
17
18
|
constructor() {
|
|
18
19
|
super('workflow-generation-unrecoverable');
|
|
19
20
|
this.name = 'WorkflowGenerationUnrecoverableError';
|
|
20
21
|
}
|
|
21
22
|
}
|
|
22
|
-
export class
|
|
23
|
+
export class IssueInitializationUnrecoverableError extends Error {
|
|
23
24
|
constructor() {
|
|
24
|
-
super('
|
|
25
|
-
this.name = '
|
|
25
|
+
super('issue-initialization-unrecoverable');
|
|
26
|
+
this.name = 'IssueInitializationUnrecoverableError';
|
|
26
27
|
}
|
|
27
28
|
}
|
|
28
29
|
export class FileRunRecordWriter {
|
|
29
30
|
file;
|
|
30
|
-
|
|
31
|
-
migrationMetadataPath;
|
|
32
|
-
reportLifecycleBackupPath;
|
|
33
|
-
mutableLifecycleBackupPath;
|
|
34
|
-
now;
|
|
31
|
+
maxBytes;
|
|
35
32
|
constructor(path, options = {}) {
|
|
36
33
|
this.file = new AtomicStateFile(path, validateRunStateFile, options);
|
|
37
|
-
this.
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
34
|
+
this.maxBytes = options.maxBytes ?? 1024 * 1024;
|
|
35
|
+
}
|
|
36
|
+
async inspect() {
|
|
37
|
+
const bytes = await readOptionalStateFile(this.file.path);
|
|
38
|
+
if (!bytes)
|
|
39
|
+
return { status: 'absent', rawSha256: null };
|
|
40
|
+
const rawSha256 = sha256(bytes);
|
|
41
|
+
if (bytes.length > this.maxBytes)
|
|
42
|
+
return { status: 'unsupported', rawSha256 };
|
|
44
43
|
try {
|
|
45
|
-
return
|
|
44
|
+
return { status: 'supported', rawSha256, state: parseRawState(bytes) };
|
|
46
45
|
}
|
|
47
|
-
catch
|
|
48
|
-
return
|
|
46
|
+
catch {
|
|
47
|
+
return { status: 'unsupported', rawSha256 };
|
|
49
48
|
}
|
|
50
49
|
}
|
|
50
|
+
async read() {
|
|
51
|
+
const inspection = await this.inspect();
|
|
52
|
+
if (inspection.status === 'absent')
|
|
53
|
+
return emptyRunState();
|
|
54
|
+
if (inspection.status === 'supported')
|
|
55
|
+
return inspection.state;
|
|
56
|
+
throw new Error('run state schema is unsupported');
|
|
57
|
+
}
|
|
51
58
|
async compareAndSwap(expectedGeneration, next) {
|
|
52
59
|
validateRunStateBody(next);
|
|
53
60
|
const candidate = { ...structuredClone(next), generation: expectedGeneration + 1 };
|
|
54
|
-
return this.file.
|
|
55
|
-
if (!priorBytes)
|
|
56
|
-
return;
|
|
57
|
-
const raw = parseRawState(priorBytes);
|
|
58
|
-
if (raw.version === 3 && next.version !== 3)
|
|
59
|
-
throw new Error('run state V3 downgrade requires explicit rollback');
|
|
60
|
-
if (raw.version === 2 && next.version === 3)
|
|
61
|
-
await this.ensureCandidateMigrationBackup(priorBytes, raw.generation);
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
async markPublicationEffectPossible() {
|
|
65
|
-
await this.file.withExclusiveRaw(async () => {
|
|
66
|
-
const existing = await readMigrationMetadata(this.migrationMetadataPath);
|
|
67
|
-
if (!existing?.publicationEffectPossible) {
|
|
68
|
-
const now = this.now();
|
|
69
|
-
await writeDurableAtomicFile(this.migrationMetadataPath, `${canonicalJson({
|
|
70
|
-
version: 1,
|
|
71
|
-
sourceGeneration: existing?.sourceGeneration ?? null,
|
|
72
|
-
sourceBytesSha256: existing?.sourceBytesSha256 ?? null,
|
|
73
|
-
publicationEffectPossible: true,
|
|
74
|
-
createdAt: existing?.createdAt ?? now,
|
|
75
|
-
updatedAt: now,
|
|
76
|
-
})}\n`);
|
|
77
|
-
}
|
|
78
|
-
return { result: undefined };
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
async rollbackCandidateMigration(input) {
|
|
82
|
-
return this.file.withExclusiveRaw(async (_currentBytes, current) => {
|
|
83
|
-
const metadata = await readMigrationMetadata(this.migrationMetadataPath);
|
|
84
|
-
if (!metadata || metadata.sourceGeneration === null || metadata.sourceBytesSha256 === null)
|
|
85
|
-
throw new Error('candidate V3 rollback backup is unavailable');
|
|
86
|
-
if (metadata.publicationEffectPossible)
|
|
87
|
-
throw new Error('candidate V3 rollback is forbidden after publication boundary');
|
|
88
|
-
if (!current || current.version !== 3)
|
|
89
|
-
throw new Error('candidate V3 rollback requires active V3 state');
|
|
90
|
-
await input.assertNoActiveProcesses();
|
|
91
|
-
await input.cleanupCandidateState(structuredClone(current));
|
|
92
|
-
const backup = await readOptionalFile(this.backupPath);
|
|
93
|
-
if (!backup || sha256(backup) !== metadata.sourceBytesSha256)
|
|
94
|
-
throw new Error('candidate V3 rollback backup identity is invalid');
|
|
95
|
-
const restored = parseRawState(backup);
|
|
96
|
-
if (restored.version !== 2 || restored.generation !== metadata.sourceGeneration)
|
|
97
|
-
throw new Error('candidate V3 rollback backup generation is invalid');
|
|
98
|
-
return { result: restored, replacementBytes: backup };
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
async ensureCandidateMigrationBackup(rawV2Bytes, generation) {
|
|
102
|
-
const sourceBytesSha256 = sha256(rawV2Bytes);
|
|
103
|
-
const existing = await readMigrationMetadata(this.migrationMetadataPath);
|
|
104
|
-
const backup = await readOptionalFile(this.backupPath);
|
|
105
|
-
if (existing?.publicationEffectPossible)
|
|
106
|
-
throw new Error('candidate V3 migration backup cannot change after publication boundary');
|
|
107
|
-
if (existing?.sourceGeneration === generation && existing.sourceBytesSha256 === sourceBytesSha256
|
|
108
|
-
&& backup?.equals(rawV2Bytes))
|
|
109
|
-
return;
|
|
110
|
-
await writeDurableAtomicFile(this.backupPath, rawV2Bytes);
|
|
111
|
-
const now = this.now();
|
|
112
|
-
await writeDurableAtomicFile(this.migrationMetadataPath, `${canonicalJson({
|
|
113
|
-
version: 1,
|
|
114
|
-
sourceGeneration: generation,
|
|
115
|
-
sourceBytesSha256,
|
|
116
|
-
publicationEffectPossible: false,
|
|
117
|
-
createdAt: now,
|
|
118
|
-
updatedAt: now,
|
|
119
|
-
})}\n`);
|
|
120
|
-
}
|
|
121
|
-
async migrateLegacyReportLifecycle(originalError) {
|
|
122
|
-
return this.file.withExclusiveUnparsedRaw(async (priorBytes) => {
|
|
123
|
-
if (!priorBytes)
|
|
124
|
-
return { result: emptyRunState() };
|
|
125
|
-
let raw;
|
|
126
|
-
try {
|
|
127
|
-
raw = JSON.parse(priorBytes.toString('utf8'));
|
|
128
|
-
}
|
|
129
|
-
catch {
|
|
130
|
-
throw originalError;
|
|
131
|
-
}
|
|
132
|
-
try {
|
|
133
|
-
return { result: validateRunStateFile(raw) };
|
|
134
|
-
}
|
|
135
|
-
catch {
|
|
136
|
-
const mutable = hasLegacyMutableLifecycle(raw);
|
|
137
|
-
let backupPath = mutable ? this.mutableLifecycleBackupPath : this.reportLifecycleBackupPath;
|
|
138
|
-
if (!mutable) {
|
|
139
|
-
const priorReportBackup = await readOptionalFile(backupPath);
|
|
140
|
-
if (priorReportBackup && !priorReportBackup.equals(priorBytes)) {
|
|
141
|
-
const generation = raw.generation;
|
|
142
|
-
backupPath = `${backupPath}.g${String(generation)}-${sha256(priorBytes).slice(0, 16)}`;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
const migrated = canonicalizeLegacyReportLifecycle(raw, backupPath);
|
|
146
|
-
if (!migrated)
|
|
147
|
-
throw originalError;
|
|
148
|
-
const existingBackup = await readOptionalFile(backupPath);
|
|
149
|
-
if (existingBackup && !existingBackup.equals(priorBytes))
|
|
150
|
-
throw new Error('report lifecycle migration backup conflicts with current source bytes');
|
|
151
|
-
if (!existingBackup)
|
|
152
|
-
await writeDurableAtomicFile(backupPath, priorBytes);
|
|
153
|
-
return { result: migrated, replacementBytes: Buffer.from(`${canonicalJson(migrated)}\n`) };
|
|
154
|
-
}
|
|
155
|
-
});
|
|
61
|
+
return this.file.compareAndSwap(expectedGeneration, candidate);
|
|
156
62
|
}
|
|
157
63
|
}
|
|
158
64
|
export class InMemoryRunRecordWriter {
|
|
159
|
-
state
|
|
65
|
+
state;
|
|
66
|
+
async inspect() {
|
|
67
|
+
if (!this.state)
|
|
68
|
+
return { status: 'absent', rawSha256: null };
|
|
69
|
+
const state = structuredClone(this.state);
|
|
70
|
+
return {
|
|
71
|
+
status: 'supported',
|
|
72
|
+
rawSha256: sha256(`${canonicalJson(state)}\n`),
|
|
73
|
+
state,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
160
76
|
async read() {
|
|
161
|
-
return structuredClone(this.state);
|
|
77
|
+
return structuredClone(this.state ?? emptyRunState());
|
|
162
78
|
}
|
|
163
79
|
async compareAndSwap(expectedGeneration, next) {
|
|
164
|
-
if (this.state
|
|
80
|
+
if ((this.state?.generation ?? 0) !== expectedGeneration)
|
|
165
81
|
throw new Error('run state generation conflict');
|
|
166
|
-
|
|
167
|
-
throw new Error('run state V3 downgrade requires explicit rollback');
|
|
82
|
+
validateRunStateBody(next);
|
|
168
83
|
const value = validateRunStateFile({ ...structuredClone(next), generation: expectedGeneration + 1 });
|
|
169
84
|
this.state = value;
|
|
170
85
|
return structuredClone(value);
|
|
171
86
|
}
|
|
172
|
-
async markPublicationEffectPossible() { }
|
|
173
87
|
}
|
|
174
88
|
export function validateRunStateFile(value) {
|
|
175
|
-
assertExactObject(value, ['schema', '
|
|
176
|
-
if (value.schema !== 'codex-orchestrator.
|
|
177
|
-
throw new Error('run state schema
|
|
89
|
+
assertExactObject(value, ['schema', 'generation', 'runs'], 'run state');
|
|
90
|
+
if (value.schema !== 'codex-orchestrator.run-state')
|
|
91
|
+
throw new Error('run state schema is invalid');
|
|
178
92
|
if (!Number.isSafeInteger(value.generation) || value.generation <= 0)
|
|
179
93
|
throw new Error('run state generation is invalid');
|
|
180
94
|
validateRuns(value.runs);
|
|
181
|
-
if (value.version === 1) {
|
|
182
|
-
for (const run of value.runs) {
|
|
183
|
-
if (hasOwn(run, 'reviewFeedback'))
|
|
184
|
-
throw new Error('V1 run state cannot contain review feedback');
|
|
185
|
-
}
|
|
186
|
-
return {
|
|
187
|
-
schema: 'codex-orchestrator.agent-auto-state',
|
|
188
|
-
version: 2,
|
|
189
|
-
generation: value.generation,
|
|
190
|
-
runs: value.runs.map((run) => run.lifecycle === 'review-ready'
|
|
191
|
-
? { ...structuredClone(run), reviewFeedback: createReviewFeedbackBootstrap() }
|
|
192
|
-
: structuredClone(run)),
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
if (value.version === 2)
|
|
196
|
-
assertNoCandidateV3Fields(value.runs);
|
|
197
95
|
return value;
|
|
198
96
|
}
|
|
199
97
|
function validateRunStateBody(value) {
|
|
200
|
-
assertExactObject(value, ['schema', '
|
|
201
|
-
if (value.schema !== 'codex-orchestrator.
|
|
202
|
-
throw new Error('run state schema
|
|
98
|
+
assertExactObject(value, ['schema', 'runs'], 'run state body');
|
|
99
|
+
if (value.schema !== 'codex-orchestrator.run-state')
|
|
100
|
+
throw new Error('run state schema is invalid');
|
|
203
101
|
validateRuns(value.runs);
|
|
204
|
-
if (value.version === 2)
|
|
205
|
-
assertNoCandidateV3Fields(value.runs);
|
|
206
102
|
}
|
|
207
103
|
function validateRuns(value) {
|
|
208
104
|
if (!Array.isArray(value) || value.length > 256)
|
|
@@ -217,29 +113,24 @@ function validateRuns(value) {
|
|
|
217
113
|
}
|
|
218
114
|
function validateRunRecord(value, field) {
|
|
219
115
|
const optional = [
|
|
116
|
+
'activeAttempt',
|
|
220
117
|
'checkedChangeSha256',
|
|
221
118
|
'proofId',
|
|
119
|
+
'proofExecution',
|
|
222
120
|
'proofReceipt',
|
|
223
|
-
'
|
|
121
|
+
'pendingEffect',
|
|
224
122
|
'outcomeEvidenceId',
|
|
225
123
|
'terminalOutcome',
|
|
226
|
-
'
|
|
227
|
-
'routeReceipt',
|
|
228
|
-
'waitingHuman',
|
|
124
|
+
'deliveryAuthority',
|
|
229
125
|
'directReview',
|
|
230
|
-
'specDelivery',
|
|
231
126
|
'reviewFeedback',
|
|
232
|
-
'checkQualification',
|
|
233
|
-
'baselineChecks',
|
|
234
127
|
'changeBindingVersion',
|
|
235
128
|
'candidateBinding',
|
|
236
|
-
'
|
|
237
|
-
'reportInvocation',
|
|
238
|
-
'mutableInvocation',
|
|
129
|
+
'candidateMaterialization',
|
|
239
130
|
].filter((key) => hasOwn(value, key));
|
|
240
131
|
assertExactObject(value, [
|
|
241
132
|
'runId', 'issueNumber', 'canonicalRepository', 'baseSha', 'branchName', 'worktreePath', 'lifecycle', 'cycle',
|
|
242
|
-
'reportRepairs', 'issueSnapshot', 'frozenCriteria', 'reworkFindings',
|
|
133
|
+
'reportRepairs', 'transportRetries', 'issueSnapshot', 'frozenCriteria', 'reworkFindings',
|
|
243
134
|
'packageVersion', 'workflowGeneration', 'skillHashes', 'checks', 'createdAt', 'updatedAt', ...optional,
|
|
244
135
|
], field);
|
|
245
136
|
if (typeof value.runId !== 'string' || !UUID_V4_PATTERN.test(value.runId))
|
|
@@ -255,10 +146,12 @@ function validateRunRecord(value, field) {
|
|
|
255
146
|
}
|
|
256
147
|
if (!isLifecycle(value.lifecycle))
|
|
257
148
|
throw new Error(`${field}.lifecycle is invalid`);
|
|
258
|
-
if (!Number.isSafeInteger(value.cycle) || value.cycle < 1
|
|
149
|
+
if (!Number.isSafeInteger(value.cycle) || value.cycle < 1)
|
|
259
150
|
throw new Error(`${field}.cycle is invalid`);
|
|
260
|
-
if (value.reportRepairs
|
|
151
|
+
if (!Number.isSafeInteger(value.reportRepairs) || value.reportRepairs < 0)
|
|
261
152
|
throw new Error(`${field}.reportRepairs is invalid`);
|
|
153
|
+
if (!Number.isSafeInteger(value.transportRetries) || value.transportRetries < 0)
|
|
154
|
+
throw new Error(`${field}.transportRetries is invalid`);
|
|
262
155
|
validateIssueSnapshot(value.issueSnapshot, `${field}.issueSnapshot`);
|
|
263
156
|
validateFrozenCriteria(value.frozenCriteria, `${field}.frozenCriteria`);
|
|
264
157
|
validateStringList(value.reworkFindings, `${field}.reworkFindings`);
|
|
@@ -268,114 +161,91 @@ function validateRunRecord(value, field) {
|
|
|
268
161
|
if (workflowGeneration.packageVersion !== value.packageVersion) {
|
|
269
162
|
throw new Error(`${field}.workflowGeneration package version mismatch`);
|
|
270
163
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
164
|
+
if (hasOwn(value, 'deliveryAuthority')) {
|
|
165
|
+
validateDeliveryAuthority(value.deliveryAuthority, {
|
|
166
|
+
issueNumber: value.issueSnapshot.number,
|
|
167
|
+
issueUrl: value.issueSnapshot.url,
|
|
168
|
+
title: value.issueSnapshot.title,
|
|
169
|
+
body: value.issueSnapshot.body,
|
|
170
|
+
authorizationLabel: value.deliveryAuthority.authorizationLabel,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
if (['implementing', 'checking', 'proving', 'reviewing', 'publishing', 'review-ready'].includes(value.lifecycle)
|
|
174
|
+
&& !hasOwn(value, 'deliveryAuthority')) {
|
|
175
|
+
throw new Error(`${field}.deliveryAuthority is required for delivery progression`);
|
|
176
|
+
}
|
|
276
177
|
validateStringShaRecord(value.skillHashes, `${field}.skillHashes`);
|
|
277
|
-
if (hasOwn(value, 'baselineChecks'))
|
|
278
|
-
validateChecks(value.baselineChecks, `${field}.baselineChecks`, false);
|
|
279
178
|
validateChecks(value.checks, `${field}.checks`);
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
const expectedLifecycle = invocation.operation === 'triage' || invocation.operation === 'ambiguity-review'
|
|
285
|
-
? 'triaging'
|
|
286
|
-
: invocation.operation === 'code-review' ? 'implementing' : 'spec-authoring';
|
|
287
|
-
if (value.lifecycle !== expectedLifecycle)
|
|
288
|
-
throw new Error(`${field}.reportInvocation lifecycle is invalid`);
|
|
289
|
-
}
|
|
290
|
-
if (hasOwn(value, 'mutableInvocation')) {
|
|
291
|
-
const invocation = validateDurableMutableInvocation(value.mutableInvocation);
|
|
292
|
-
if (value.lifecycle !== 'implementing')
|
|
293
|
-
throw new Error(`${field}.mutableInvocation lifecycle is invalid`);
|
|
294
|
-
if (invocation.worktreePath !== value.worktreePath || invocation.generationHash !== routeGenerationHash) {
|
|
295
|
-
throw new Error(`${field}.mutableInvocation binding is invalid`);
|
|
296
|
-
}
|
|
297
|
-
if (invocation.operation === 'qualification-repair' && !hasOwn(value, 'checkQualification')) {
|
|
298
|
-
throw new Error(`${field}.qualification mutableInvocation requires check qualification`);
|
|
299
|
-
}
|
|
300
|
-
const feedbackActive = hasOwn(value, 'reviewFeedback')
|
|
301
|
-
&& value.reviewFeedback.activeBatch !== null;
|
|
302
|
-
if ((invocation.operation === 'review-feedback-implementation') !== feedbackActive) {
|
|
303
|
-
throw new Error(`${field}.mutableInvocation feedback binding is invalid`);
|
|
304
|
-
}
|
|
179
|
+
if (hasOwn(value, 'activeAttempt')) {
|
|
180
|
+
const attempt = validateActiveAttempt(value.activeAttempt);
|
|
181
|
+
if (attempt.runId !== value.runId)
|
|
182
|
+
throw new Error(`${field}.activeAttempt run identity is invalid`);
|
|
305
183
|
}
|
|
306
184
|
if (hasOwn(value, 'checkedChangeSha256'))
|
|
307
185
|
assertSha256(value.checkedChangeSha256, `${field}.checkedChangeSha256`);
|
|
308
186
|
if (hasOwn(value, 'proofId'))
|
|
309
187
|
assertNonEmptyString(value.proofId, `${field}.proofId`);
|
|
188
|
+
if (hasOwn(value, 'proofExecution'))
|
|
189
|
+
validateProofExecution(value.proofExecution, `${field}.proofExecution`);
|
|
310
190
|
if (hasOwn(value, 'proofReceipt'))
|
|
311
191
|
validateReceipt(value.proofReceipt, `${field}.proofReceipt`);
|
|
312
|
-
if (hasOwn(value, '
|
|
313
|
-
|
|
192
|
+
if (hasOwn(value, 'pendingEffect'))
|
|
193
|
+
validatePendingEffect(value.pendingEffect, `${field}.pendingEffect`);
|
|
314
194
|
if (hasOwn(value, 'outcomeEvidenceId'))
|
|
315
195
|
assertNonEmptyString(value.outcomeEvidenceId, `${field}.outcomeEvidenceId`);
|
|
316
196
|
if (hasOwn(value, 'terminalOutcome'))
|
|
317
197
|
validateTerminalOutcome(value.terminalOutcome, `${field}.terminalOutcome`);
|
|
318
|
-
if (hasOwn(value, 'waitingHuman')) {
|
|
319
|
-
if (!routeGenerationHash)
|
|
320
|
-
throw new WorkflowGenerationUnrecoverableError();
|
|
321
|
-
validateWaitingHumanExecution(value.waitingHuman, {
|
|
322
|
-
runId: value.runId,
|
|
323
|
-
lifecycle: value.lifecycle,
|
|
324
|
-
workflowGenerationHash: routeGenerationHash,
|
|
325
|
-
routeReceipt: hasOwn(value, 'routeReceipt') ? value.routeReceipt : undefined,
|
|
326
|
-
terminalOutcome: hasOwn(value, 'terminalOutcome') ? value.terminalOutcome : undefined,
|
|
327
|
-
});
|
|
328
|
-
}
|
|
329
198
|
if (hasOwn(value, 'directReview')) {
|
|
330
|
-
if (!hasOwn(value, '
|
|
331
|
-
throw new Error(`${field}.directReview requires
|
|
332
|
-
}
|
|
199
|
+
if (!hasOwn(value, 'deliveryAuthority'))
|
|
200
|
+
throw new Error(`${field}.directReview requires delivery authority`);
|
|
333
201
|
validateDirectReview(value.directReview, {
|
|
334
202
|
lifecycle: value.lifecycle,
|
|
335
203
|
...(hasOwn(value, 'terminalOutcome') ? { terminalOutcome: directTerminalOutcome(value.terminalOutcome) } : {}),
|
|
204
|
+
...(hasOwn(value, 'terminalOutcome') && value.terminalOutcome.status === 'internal-error'
|
|
205
|
+
? { terminalCode: value.terminalOutcome.code }
|
|
206
|
+
: {}),
|
|
336
207
|
});
|
|
337
208
|
}
|
|
338
|
-
if (hasOwn(value, 'specDelivery')) {
|
|
339
|
-
if (!hasOwn(value, 'routeReceipt') || value.routeReceipt.route !== 'spec-required') {
|
|
340
|
-
throw new Error(`${field}.specDelivery requires a spec-required route`);
|
|
341
|
-
}
|
|
342
|
-
const spec = validateSpecDelivery(value.specDelivery);
|
|
343
|
-
if (spec.issueNumber !== value.issueNumber || spec.runId !== value.runId
|
|
344
|
-
|| spec.workflowGenerationSha256 !== routeGenerationHash) {
|
|
345
|
-
throw new Error(`${field}.specDelivery identity binding is invalid`);
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
209
|
if (hasOwn(value, 'reviewFeedback')) {
|
|
349
|
-
|
|
210
|
+
validateReviewFeedbackRunData(value.reviewFeedback);
|
|
350
211
|
validateReviewFeedbackRunInvariant(value, field);
|
|
351
212
|
}
|
|
352
213
|
if (hasOwn(value, 'changeBindingVersion') && value.changeBindingVersion !== 2)
|
|
353
214
|
throw new Error(`${field}.changeBindingVersion is invalid`);
|
|
354
215
|
if (hasOwn(value, 'candidateBinding'))
|
|
355
216
|
validateCandidateBinding(value.candidateBinding, `${field}.candidateBinding`, value.runId);
|
|
356
|
-
if (hasOwn(value, '
|
|
357
|
-
|
|
217
|
+
if (hasOwn(value, 'candidateMaterialization'))
|
|
218
|
+
validateCandidateMaterialization(value.candidateMaterialization, `${field}.candidateMaterialization`);
|
|
358
219
|
if (hasOwn(value, 'candidateBinding') !== hasOwn(value, 'changeBindingVersion'))
|
|
359
220
|
throw new Error(`${field} candidate binding version is incomplete`);
|
|
360
|
-
if (hasOwn(value, '
|
|
221
|
+
if (hasOwn(value, 'candidateMaterialization')) {
|
|
361
222
|
if (!hasOwn(value, 'candidateBinding'))
|
|
362
|
-
throw new Error(`${field}.
|
|
223
|
+
throw new Error(`${field}.candidateMaterialization requires candidate binding`);
|
|
363
224
|
const binding = value.candidateBinding;
|
|
364
|
-
const
|
|
365
|
-
if (
|
|
366
|
-
throw new Error(`${field}.
|
|
225
|
+
const materialization = value.candidateMaterialization;
|
|
226
|
+
if (materialization.bindingId !== binding.bindingId || materialization.candidateCommitSha !== binding.candidateCommitSha) {
|
|
227
|
+
throw new Error(`${field}.candidateMaterialization binding is invalid`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (hasOwn(value, 'pendingEffect') && hasOwn(value.pendingEffect, 'candidateRef')) {
|
|
231
|
+
if (!hasOwn(value, 'candidateBinding'))
|
|
232
|
+
throw new Error(`${field}.pendingEffect candidate ref requires candidate binding`);
|
|
233
|
+
const binding = value.candidateBinding;
|
|
234
|
+
const pendingEffect = value.pendingEffect;
|
|
235
|
+
if (pendingEffect.candidateRef !== binding.candidateRef || pendingEffect.treeSha !== binding.candidateTreeSha) {
|
|
236
|
+
throw new Error(`${field}.pendingEffect candidate binding is invalid`);
|
|
237
|
+
}
|
|
367
238
|
}
|
|
368
|
-
if (
|
|
239
|
+
if (value.pendingEffect?.kind === 'candidate-pin-release') {
|
|
369
240
|
if (!hasOwn(value, 'candidateBinding'))
|
|
370
|
-
throw new Error(`${field}.
|
|
241
|
+
throw new Error(`${field}.candidate pin cleanup requires candidate binding`);
|
|
371
242
|
const binding = value.candidateBinding;
|
|
372
|
-
const
|
|
373
|
-
if (
|
|
374
|
-
|
|
243
|
+
const pendingEffect = value.pendingEffect;
|
|
244
|
+
if (pendingEffect.bindingId !== binding.bindingId
|
|
245
|
+
|| pendingEffect.expectedPinnedCommitSha !== binding.candidateCommitSha) {
|
|
246
|
+
throw new Error(`${field}.candidate pin cleanup binding is invalid`);
|
|
375
247
|
}
|
|
376
248
|
}
|
|
377
|
-
if (hasOwn(value, 'checkQualification'))
|
|
378
|
-
validateCheckQualification(value.checkQualification, `${field}.checkQualification`);
|
|
379
249
|
assertTimestamp(value.createdAt, `${field}.createdAt`);
|
|
380
250
|
assertTimestamp(value.updatedAt, `${field}.updatedAt`);
|
|
381
251
|
const terminal = ['review-ready', 'blocked', 'transport-failed', 'cancelled', 'internal-error'].includes(value.lifecycle);
|
|
@@ -387,65 +257,31 @@ function validateRunRecord(value, field) {
|
|
|
387
257
|
|| value.checks.some((check) => check.status === 'failed'))) {
|
|
388
258
|
throw new Error(`${field} proving requires passed checks and checked change proof identity`);
|
|
389
259
|
}
|
|
390
|
-
if (value.lifecycle === 'publishing' && !hasOwn(value, 'proofReceipt'))
|
|
391
|
-
throw new Error(`${field}
|
|
392
|
-
if (value.lifecycle === 'review-ready' && (!hasOwn(value, 'proofReceipt') || hasOwn(value, 'intent'))) {
|
|
393
|
-
throw new Error(`${field} review-ready requires proofReceipt and no intent`);
|
|
260
|
+
if ((value.lifecycle === 'reviewing' || value.lifecycle === 'publishing') && !hasOwn(value, 'proofReceipt')) {
|
|
261
|
+
throw new Error(`${field} review and publication require proofReceipt`);
|
|
394
262
|
}
|
|
395
|
-
|
|
263
|
+
if (value.lifecycle === 'safe-halt' && !hasOwn(value, 'activeAttempt'))
|
|
264
|
+
throw new Error(`${field} safe-halt requires an active attempt`);
|
|
265
|
+
const reviewReadyEffect = value.pendingEffect?.kind;
|
|
266
|
+
const reviewReadyEffectAllowed = reviewReadyEffect === undefined
|
|
267
|
+
|| ['review-activation-labels', 'blocked-labels', 'continuation-worktree-create', 'outcome-evidence'].includes(reviewReadyEffect);
|
|
268
|
+
if (value.lifecycle === 'review-ready' && (!hasOwn(value, 'proofReceipt') || !reviewReadyEffectAllowed)) {
|
|
269
|
+
throw new Error(`${field} review-ready requires proofReceipt and only a review continuation or terminal effect`);
|
|
270
|
+
}
|
|
271
|
+
const retainedCandidateEffect = value.lifecycle === 'blocked'
|
|
396
272
|
&& value.terminalOutcome?.status === 'blocked'
|
|
397
273
|
&& value.terminalOutcome.kind === 'safety'
|
|
398
274
|
&& !value.terminalOutcome.resumable
|
|
399
275
|
&& hasOwn(value, 'candidateBinding')
|
|
400
|
-
&& (value.
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
276
|
+
&& (value.pendingEffect?.kind === 'initial-commit' || value.pendingEffect?.kind === 'review-update-commit');
|
|
277
|
+
const settlingOutcomeEvidence = value.pendingEffect?.kind === 'outcome-evidence';
|
|
278
|
+
if (terminal && hasOwn(value, 'pendingEffect') && value.lifecycle !== 'transport-failed'
|
|
279
|
+
&& !retainedCandidateEffect && !settlingOutcomeEvidence)
|
|
280
|
+
throw new Error(`${field} terminal lifecycle cannot retain pending effect`);
|
|
281
|
+
if (value.lifecycle === 'transport-failed' && hasOwn(value, 'pendingEffect')
|
|
282
|
+
&& !settlingOutcomeEvidence
|
|
404
283
|
&& value.terminalOutcome.resumable) {
|
|
405
|
-
throw new Error(`${field} resumable transport failure cannot retain
|
|
406
|
-
}
|
|
407
|
-
if (value.lifecycle === 'waiting-human' && !hasOwn(value, 'waitingHuman'))
|
|
408
|
-
throw new Error(`${field} waiting-human lifecycle requires waitingHuman execution`);
|
|
409
|
-
if (reportInvocation)
|
|
410
|
-
validateReportInvocationBinding(value, reportInvocation, routeGenerationHash, field);
|
|
411
|
-
validateRouteStateInvariant({
|
|
412
|
-
lifecycle: value.lifecycle,
|
|
413
|
-
routeExecution: value.routeExecution,
|
|
414
|
-
routeReceipt: value.routeReceipt,
|
|
415
|
-
generationHash: routeGenerationHash,
|
|
416
|
-
});
|
|
417
|
-
}
|
|
418
|
-
function validateReportInvocationBinding(record, invocation, generationHash, field) {
|
|
419
|
-
if (invocation.generationHash !== generationHash)
|
|
420
|
-
throw new Error(`${field}.reportInvocation generation is invalid`);
|
|
421
|
-
if (invocation.operation === 'triage') {
|
|
422
|
-
if (!record.routeExecution || !['triage-in-flight', 'repair-in-flight'].includes(record.routeExecution.phase)) {
|
|
423
|
-
throw new Error(`${field}.triage reportInvocation phase is invalid`);
|
|
424
|
-
}
|
|
425
|
-
return;
|
|
426
|
-
}
|
|
427
|
-
if (invocation.operation === 'ambiguity-review') {
|
|
428
|
-
if (record.routeExecution?.phase !== 'review-in-flight')
|
|
429
|
-
throw new Error(`${field}.ambiguity reportInvocation phase is invalid`);
|
|
430
|
-
return;
|
|
431
|
-
}
|
|
432
|
-
if (invocation.operation === 'code-review') {
|
|
433
|
-
if (record.directReview?.status !== 'active'
|
|
434
|
-
|| !['review-full', 'review-closure'].includes(record.directReview.stage ?? '')
|
|
435
|
-
|| record.executionLease?.operation !== 'direct-review') {
|
|
436
|
-
throw new Error(`${field}.code-review reportInvocation binding is invalid`);
|
|
437
|
-
}
|
|
438
|
-
return;
|
|
439
|
-
}
|
|
440
|
-
if (invocation.operation === 'spec-author') {
|
|
441
|
-
if (!record.specDelivery || !['authoring', 'author-repair'].includes(record.specDelivery.stage)
|
|
442
|
-
|| record.specDelivery.authorSessionId === null) {
|
|
443
|
-
throw new Error(`${field}.spec-author reportInvocation binding is invalid`);
|
|
444
|
-
}
|
|
445
|
-
return;
|
|
446
|
-
}
|
|
447
|
-
if (!record.specDelivery || !['review-full', 'review-closure'].includes(record.specDelivery.stage)) {
|
|
448
|
-
throw new Error(`${field}.spec-review reportInvocation binding is invalid`);
|
|
284
|
+
throw new Error(`${field} resumable transport failure cannot retain pending effect`);
|
|
449
285
|
}
|
|
450
286
|
}
|
|
451
287
|
function directTerminalOutcome(outcome) {
|
|
@@ -467,7 +303,7 @@ function validateWorkflowGeneration(value, field) {
|
|
|
467
303
|
}
|
|
468
304
|
assertSha256(value.contentSha256, `${field}.contentSha256`);
|
|
469
305
|
}
|
|
470
|
-
function validateChecks(value, field
|
|
306
|
+
function validateChecks(value, field) {
|
|
471
307
|
if (!Array.isArray(value) || value.length > 256)
|
|
472
308
|
throw new Error(`${field} is invalid`);
|
|
473
309
|
const ids = new Set();
|
|
@@ -479,35 +315,19 @@ function validateChecks(value, field, allowUnchangedFailure = true) {
|
|
|
479
315
|
], `${field}[${index}]`);
|
|
480
316
|
assertNonEmptyString(check.id, `${field}[${index}].id`);
|
|
481
317
|
assertNonEmptyString(check.command, `${field}[${index}].command`);
|
|
482
|
-
if (check.status !== 'passed' && check.status !== 'failed'
|
|
483
|
-
&& (check.status !== 'unchanged-failure' || !allowUnchangedFailure))
|
|
318
|
+
if (check.status !== 'passed' && check.status !== 'failed')
|
|
484
319
|
throw new Error(`${field}[${index}].status is invalid`);
|
|
485
320
|
assertSha256(check.outputSha256, `${field}[${index}].outputSha256`);
|
|
486
321
|
if (candidate) {
|
|
487
322
|
assertSha256(check.bindingId, `${field}[${index}].bindingId`);
|
|
488
323
|
assertGitSha(check.candidateTreeSha, `${field}[${index}].candidateTreeSha`);
|
|
489
324
|
assertSha256(check.checkPolicySha256, `${field}[${index}].checkPolicySha256`);
|
|
490
|
-
if (check.status === 'unchanged-failure')
|
|
491
|
-
throw new Error(`${field}[${index}] candidate status is invalid`);
|
|
492
325
|
}
|
|
493
326
|
if (ids.has(check.id))
|
|
494
327
|
throw new Error(`${field} IDs must be unique`);
|
|
495
328
|
ids.add(check.id);
|
|
496
329
|
}
|
|
497
330
|
}
|
|
498
|
-
function validateCheckQualification(value, field) {
|
|
499
|
-
const optional = hasOwn(value, 'repairFindings') ? ['repairFindings'] : [];
|
|
500
|
-
assertExactObject(value, ['version', 'checkPolicySha256', 'repairAttempts', 'checks', ...optional], field);
|
|
501
|
-
if (value.version !== 1)
|
|
502
|
-
throw new Error(`${field}.version is invalid`);
|
|
503
|
-
assertSha256(value.checkPolicySha256, `${field}.checkPolicySha256`);
|
|
504
|
-
if (!Number.isSafeInteger(value.repairAttempts) || value.repairAttempts < 0 || value.repairAttempts > 5) {
|
|
505
|
-
throw new Error(`${field}.repairAttempts is invalid`);
|
|
506
|
-
}
|
|
507
|
-
validateChecks(value.checks, `${field}.checks`, false);
|
|
508
|
-
if (hasOwn(value, 'repairFindings'))
|
|
509
|
-
validateStringList(value.repairFindings, `${field}.repairFindings`);
|
|
510
|
-
}
|
|
511
331
|
function validateIssueSnapshot(value, field) {
|
|
512
332
|
const optional = hasOwn(value, 'comments') ? ['comments'] : [];
|
|
513
333
|
assertExactObject(value, ['number', 'title', 'body', 'url', 'state', 'labels', ...optional], field);
|
|
@@ -564,48 +384,49 @@ function validateStringList(value, field) {
|
|
|
564
384
|
for (const item of value)
|
|
565
385
|
assertNonEmptyString(item, field);
|
|
566
386
|
}
|
|
567
|
-
function
|
|
387
|
+
function validatePendingEffect(value, field) {
|
|
568
388
|
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
569
389
|
throw new Error(`${field} is invalid`);
|
|
570
390
|
const kind = value.kind;
|
|
571
|
-
|
|
572
|
-
|
|
391
|
+
const identity = ['effectId', 'kind'];
|
|
392
|
+
if (kind === 'claim-labels' || kind === 'final-labels') {
|
|
393
|
+
assertExactObject(value, [...identity, 'issueNumber', 'expected'], field);
|
|
573
394
|
assertPositiveInteger(value.issueNumber, `${field}.issueNumber`);
|
|
574
395
|
validateStringArray(value.expected, `${field}.expected`);
|
|
575
396
|
}
|
|
576
|
-
else if (kind === 'commit') {
|
|
577
|
-
assertExactObject(value, [
|
|
397
|
+
else if (kind === 'initial-commit') {
|
|
398
|
+
assertExactObject(value, [...identity, 'parentSha', 'treeSha', 'message', ...(hasOwn(value, 'candidateRef') ? ['candidateRef'] : [])], field);
|
|
578
399
|
assertGitSha(value.parentSha, `${field}.parentSha`);
|
|
579
400
|
assertGitSha(value.treeSha, `${field}.treeSha`);
|
|
580
401
|
assertNonEmptyString(value.message, `${field}.message`);
|
|
581
402
|
if (hasOwn(value, 'candidateRef'))
|
|
582
403
|
assertCandidateRef(value.candidateRef, `${field}.candidateRef`);
|
|
583
404
|
}
|
|
584
|
-
else if (kind === 'push') {
|
|
585
|
-
assertExactObject(value, [
|
|
405
|
+
else if (kind === 'initial-push') {
|
|
406
|
+
assertExactObject(value, [...identity, 'branch', 'sha'], field);
|
|
586
407
|
assertNonEmptyString(value.branch, `${field}.branch`);
|
|
587
408
|
assertGitSha(value.sha, `${field}.sha`);
|
|
588
409
|
}
|
|
589
|
-
else if (kind === 'pr') {
|
|
590
|
-
assertExactObject(value, [
|
|
410
|
+
else if (kind === 'draft-pr') {
|
|
411
|
+
assertExactObject(value, [...identity, 'owner', 'repo', 'head', 'base', 'issueNumber', 'marker'], field);
|
|
591
412
|
for (const key of ['owner', 'repo', 'head', 'base', 'marker'])
|
|
592
413
|
assertNonEmptyString(value[key], `${field}.${key}`);
|
|
593
414
|
assertPositiveInteger(value.issueNumber, `${field}.issueNumber`);
|
|
594
415
|
}
|
|
595
|
-
else if (kind === 'comment') {
|
|
596
|
-
assertExactObject(value, [
|
|
416
|
+
else if (kind === 'claim-comment' || kind === 'handoff-comment') {
|
|
417
|
+
assertExactObject(value, [...identity, 'issueNumber', 'marker', 'bodySha256'], field);
|
|
597
418
|
assertPositiveInteger(value.issueNumber, `${field}.issueNumber`);
|
|
598
419
|
assertNonEmptyString(value.marker, `${field}.marker`);
|
|
599
420
|
assertSha256(value.bodySha256, `${field}.bodySha256`);
|
|
600
421
|
}
|
|
601
422
|
else if (kind === 'review-activation-labels') {
|
|
602
|
-
assertExactObject(value, [
|
|
423
|
+
assertExactObject(value, [...identity, 'issueNumber', 'batchId', 'expected'], field);
|
|
603
424
|
assertPositiveInteger(value.issueNumber, `${field}.issueNumber`);
|
|
604
425
|
assertSha256(value.batchId, `${field}.batchId`);
|
|
605
426
|
validateStringArray(value.expected, `${field}.expected`);
|
|
606
427
|
}
|
|
607
428
|
else if (kind === 'review-update-commit') {
|
|
608
|
-
assertExactObject(value, [
|
|
429
|
+
assertExactObject(value, [...identity, 'batchId', 'parentSha', 'treeSha', 'message', ...(hasOwn(value, 'candidateRef') ? ['candidateRef'] : [])], field);
|
|
609
430
|
assertSha256(value.batchId, `${field}.batchId`);
|
|
610
431
|
assertGitSha(value.parentSha, `${field}.parentSha`);
|
|
611
432
|
assertGitSha(value.treeSha, `${field}.treeSha`);
|
|
@@ -614,7 +435,7 @@ function validateIntent(value, field) {
|
|
|
614
435
|
assertCandidateRef(value.candidateRef, `${field}.candidateRef`);
|
|
615
436
|
}
|
|
616
437
|
else if (kind === 'review-update-push') {
|
|
617
|
-
assertExactObject(value, [
|
|
438
|
+
assertExactObject(value, [...identity, 'batchId', 'branch', 'priorRemoteSha', 'sha', 'treeSha'], field);
|
|
618
439
|
assertSha256(value.batchId, `${field}.batchId`);
|
|
619
440
|
assertNonEmptyString(value.branch, `${field}.branch`);
|
|
620
441
|
assertGitSha(value.priorRemoteSha, `${field}.priorRemoteSha`);
|
|
@@ -622,7 +443,7 @@ function validateIntent(value, field) {
|
|
|
622
443
|
assertGitSha(value.treeSha, `${field}.treeSha`);
|
|
623
444
|
}
|
|
624
445
|
else if (kind === 'review-summary') {
|
|
625
|
-
assertExactObject(value, [
|
|
446
|
+
assertExactObject(value, [...identity, 'batchId', 'pullRequestNumber', 'pullRequestNodeId', 'marker', 'bodySha256', 'epochHeadSha'], field);
|
|
626
447
|
assertSha256(value.batchId, `${field}.batchId`);
|
|
627
448
|
assertPositiveInteger(value.pullRequestNumber, `${field}.pullRequestNumber`);
|
|
628
449
|
assertNonEmptyString(value.pullRequestNodeId, `${field}.pullRequestNodeId`);
|
|
@@ -631,7 +452,7 @@ function validateIntent(value, field) {
|
|
|
631
452
|
assertGitSha(value.epochHeadSha, `${field}.epochHeadSha`);
|
|
632
453
|
}
|
|
633
454
|
else if (kind === 'review-final-labels') {
|
|
634
|
-
assertExactObject(value, [
|
|
455
|
+
assertExactObject(value, [...identity, 'issueNumber', 'batchId', 'pullRequestNumber', 'pullRequestNodeId', 'epochHeadSha', 'expected'], field);
|
|
635
456
|
assertPositiveInteger(value.issueNumber, `${field}.issueNumber`);
|
|
636
457
|
assertSha256(value.batchId, `${field}.batchId`);
|
|
637
458
|
assertPositiveInteger(value.pullRequestNumber, `${field}.pullRequestNumber`);
|
|
@@ -640,27 +461,61 @@ function validateIntent(value, field) {
|
|
|
640
461
|
validateStringArray(value.expected, `${field}.expected`);
|
|
641
462
|
}
|
|
642
463
|
else if (kind === 'review-blocked-labels') {
|
|
643
|
-
assertExactObject(value, [
|
|
464
|
+
assertExactObject(value, [...identity, 'issueNumber', 'batchId', 'expected', 'blockKind', 'evidenceCode'], field);
|
|
644
465
|
assertPositiveInteger(value.issueNumber, `${field}.issueNumber`);
|
|
645
466
|
assertSha256(value.batchId, `${field}.batchId`);
|
|
646
467
|
validateStringArray(value.expected, `${field}.expected`);
|
|
647
|
-
if (
|
|
468
|
+
if (!['external', 'safety', 'decision-delta', 'out-of-scope', 'authority-boundary'].includes(value.blockKind)) {
|
|
648
469
|
throw new Error(`${field}.blockKind is invalid`);
|
|
470
|
+
}
|
|
649
471
|
assertNonEmptyString(value.evidenceCode, `${field}.evidenceCode`);
|
|
650
472
|
}
|
|
651
473
|
else if (kind === 'blocked-labels') {
|
|
652
|
-
assertExactObject(value, [
|
|
474
|
+
assertExactObject(value, [...identity, 'issueNumber', 'expected', 'blockKind', 'resumable', 'evidenceCode'], field);
|
|
653
475
|
assertPositiveInteger(value.issueNumber, `${field}.issueNumber`);
|
|
654
476
|
validateStringArray(value.expected, `${field}.expected`);
|
|
655
|
-
if (!['external', 'safety', '
|
|
477
|
+
if (!['external', 'safety', 'decision-delta', 'out-of-scope', 'authority-boundary'].includes(value.blockKind))
|
|
656
478
|
throw new Error(`${field}.blockKind is invalid`);
|
|
657
479
|
if (typeof value.resumable !== 'boolean')
|
|
658
480
|
throw new Error(`${field}.resumable is invalid`);
|
|
659
481
|
assertNonEmptyString(value.evidenceCode, `${field}.evidenceCode`);
|
|
660
482
|
}
|
|
483
|
+
else if (kind === 'worktree-create') {
|
|
484
|
+
assertExactObject(value, [...identity, 'worktreePath', 'branchName', 'baseBranch', 'baseSha'], field);
|
|
485
|
+
assertAbsolutePath(value.worktreePath, `${field}.worktreePath`);
|
|
486
|
+
assertNonEmptyString(value.branchName, `${field}.branchName`);
|
|
487
|
+
assertNonEmptyString(value.baseBranch, `${field}.baseBranch`);
|
|
488
|
+
assertGitSha(value.baseSha, `${field}.baseSha`);
|
|
489
|
+
}
|
|
490
|
+
else if (kind === 'continuation-worktree-create') {
|
|
491
|
+
assertExactObject(value, [...identity, 'worktreePath', 'branchName', 'baseBranch', 'publishedHeadSha'], field);
|
|
492
|
+
assertAbsolutePath(value.worktreePath, `${field}.worktreePath`);
|
|
493
|
+
assertNonEmptyString(value.branchName, `${field}.branchName`);
|
|
494
|
+
assertNonEmptyString(value.baseBranch, `${field}.baseBranch`);
|
|
495
|
+
assertGitSha(value.publishedHeadSha, `${field}.publishedHeadSha`);
|
|
496
|
+
}
|
|
497
|
+
else if (kind === 'candidate-pin-release') {
|
|
498
|
+
assertExactObject(value, [...identity, 'bindingId', 'expectedPinnedCommitSha'], field);
|
|
499
|
+
assertSha256(value.bindingId, `${field}.bindingId`);
|
|
500
|
+
assertGitSha(value.expectedPinnedCommitSha, `${field}.expectedPinnedCommitSha`);
|
|
501
|
+
}
|
|
502
|
+
else if (kind === 'outcome-evidence') {
|
|
503
|
+
assertExactObject(value, [...identity, 'path', 'runId', 'code', 'summary', 'recordedAt', 'bytesSha256'], field);
|
|
504
|
+
assertNonEmptyString(value.path, `${field}.path`);
|
|
505
|
+
if (typeof value.runId !== 'string' || !UUID_V4_PATTERN.test(value.runId))
|
|
506
|
+
throw new Error(`${field}.runId is invalid`);
|
|
507
|
+
assertNonEmptyString(value.code, `${field}.code`);
|
|
508
|
+
assertNonEmptyString(value.summary, `${field}.summary`);
|
|
509
|
+
assertTimestamp(value.recordedAt, `${field}.recordedAt`);
|
|
510
|
+
assertSha256(value.bytesSha256, `${field}.bytesSha256`);
|
|
511
|
+
}
|
|
661
512
|
else {
|
|
662
513
|
throw new Error(`${field}.kind is invalid`);
|
|
663
514
|
}
|
|
515
|
+
assertSha256(value.effectId, `${field}.effectId`);
|
|
516
|
+
const { effectId, ...payload } = value;
|
|
517
|
+
if (effectId !== sha256(canonicalJson(payload)))
|
|
518
|
+
throw new Error(`${field}.effectId does not match its payload`);
|
|
664
519
|
}
|
|
665
520
|
function validateTerminalOutcome(value, field) {
|
|
666
521
|
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
@@ -673,11 +528,13 @@ function validateTerminalOutcome(value, field) {
|
|
|
673
528
|
assertGitSha(value.continuationEpoch, `${field}.continuationEpoch`);
|
|
674
529
|
}
|
|
675
530
|
else if (status === 'blocked') {
|
|
676
|
-
assertExactObject(value, ['status', 'kind', 'resumable', 'evidencePath'], field);
|
|
677
|
-
if (!['external', 'safety', '
|
|
531
|
+
assertExactObject(value, ['status', 'kind', 'resumable', 'evidencePath', ...(hasOwn(value, 'blocker') ? ['blocker'] : [])], field);
|
|
532
|
+
if (!['external', 'safety', 'decision-delta', 'out-of-scope', 'authority-boundary'].includes(value.kind))
|
|
678
533
|
throw new Error(`${field}.kind is invalid`);
|
|
679
534
|
if (typeof value.resumable !== 'boolean')
|
|
680
535
|
throw new Error(`${field}.resumable is invalid`);
|
|
536
|
+
if (hasOwn(value, 'blocker'))
|
|
537
|
+
validateBlockerDetail(value.blocker, `${field}.blocker`);
|
|
681
538
|
}
|
|
682
539
|
else if (status === 'transport-failed') {
|
|
683
540
|
assertExactObject(value, ['status', 'resumable', 'evidencePath'], field);
|
|
@@ -696,6 +553,19 @@ function validateTerminalOutcome(value, field) {
|
|
|
696
553
|
}
|
|
697
554
|
assertNonEmptyString(value.evidencePath, `${field}.evidencePath`);
|
|
698
555
|
}
|
|
556
|
+
function validateBlockerDetail(value, field) {
|
|
557
|
+
assertExactObject(value, ['kind', 'summary', 'attempted', 'resumable', ...(hasOwn(value, 'reviewerRejectionDetail') ? ['reviewerRejectionDetail'] : [])], field);
|
|
558
|
+
assertNonEmptyString(value.kind, `${field}.kind`);
|
|
559
|
+
assertNonEmptyString(value.summary, `${field}.summary`);
|
|
560
|
+
if (!Array.isArray(value.attempted) || value.attempted.length > 256)
|
|
561
|
+
throw new Error(`${field}.attempted is invalid`);
|
|
562
|
+
for (const attempted of value.attempted)
|
|
563
|
+
assertNonEmptyString(attempted, `${field}.attempted`);
|
|
564
|
+
if (typeof value.resumable !== 'boolean')
|
|
565
|
+
throw new Error(`${field}.resumable is invalid`);
|
|
566
|
+
if (hasOwn(value, 'reviewerRejectionDetail'))
|
|
567
|
+
assertNonEmptyString(value.reviewerRejectionDetail, `${field}.reviewerRejectionDetail`);
|
|
568
|
+
}
|
|
699
569
|
function validateReceipt(value, field) {
|
|
700
570
|
assertExactObject(value, ['proofId', 'bindingSha256', 'summary', 'publishableEvidence', 'localEvidenceId'], field);
|
|
701
571
|
assertNonEmptyString(value.proofId, `${field}.proofId`);
|
|
@@ -713,6 +583,18 @@ function validateReceipt(value, field) {
|
|
|
713
583
|
assertNonEmptyString(evidence.description, `${field}.publishableEvidence.description`);
|
|
714
584
|
}
|
|
715
585
|
}
|
|
586
|
+
function validateProofExecution(value, field) {
|
|
587
|
+
assertExactObject(value, ['startedAt', 'transportRetryCount', 'reportRepairCount', 'reportRepairFindings'], field);
|
|
588
|
+
assertTimestamp(value.startedAt, `${field}.startedAt`);
|
|
589
|
+
if (!Number.isSafeInteger(value.transportRetryCount) || value.transportRetryCount < 0)
|
|
590
|
+
throw new Error(`${field}.transportRetryCount is invalid`);
|
|
591
|
+
if (!Number.isSafeInteger(value.reportRepairCount) || value.reportRepairCount < 0)
|
|
592
|
+
throw new Error(`${field}.reportRepairCount is invalid`);
|
|
593
|
+
validateStringList(value.reportRepairFindings, `${field}.reportRepairFindings`);
|
|
594
|
+
if ((value.reportRepairCount === 0) !== (value.reportRepairFindings.length === 0)) {
|
|
595
|
+
throw new Error(`${field} report repair state is invalid`);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
716
598
|
function validateStringShaRecord(value, field) {
|
|
717
599
|
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
718
600
|
throw new Error(`${field} is invalid`);
|
|
@@ -733,154 +615,7 @@ function validateStringArray(value, field) {
|
|
|
733
615
|
throw new Error(`${field} must be sorted and unique`);
|
|
734
616
|
}
|
|
735
617
|
function emptyRunState() {
|
|
736
|
-
return { schema: 'codex-orchestrator.
|
|
737
|
-
}
|
|
738
|
-
function canonicalizeLegacyReportLifecycle(value, backupPath) {
|
|
739
|
-
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
740
|
-
return undefined;
|
|
741
|
-
const raw = value;
|
|
742
|
-
if (raw.schema !== 'codex-orchestrator.agent-auto-state' || (raw.version !== 2 && raw.version !== 3)
|
|
743
|
-
|| !Number.isSafeInteger(raw.generation) || raw.generation <= 0 || !Array.isArray(raw.runs))
|
|
744
|
-
return undefined;
|
|
745
|
-
const runs = structuredClone(raw.runs);
|
|
746
|
-
const changed = runs.map((run) => canonicalizeLegacyReportRun(run, backupPath)).some(Boolean);
|
|
747
|
-
if (!changed)
|
|
748
|
-
return undefined;
|
|
749
|
-
return validateRunStateFile({ schema: raw.schema, version: raw.version, generation: raw.generation + 1, runs });
|
|
750
|
-
}
|
|
751
|
-
function canonicalizeLegacyReportRun(run, backupPath) {
|
|
752
|
-
let changed = false, unsafe = false, routeInFlight = false;
|
|
753
|
-
const mutable = canonicalizeLegacyMutableRun(run);
|
|
754
|
-
changed ||= mutable.changed;
|
|
755
|
-
unsafe ||= mutable.unsafe;
|
|
756
|
-
const route = run.routeExecution;
|
|
757
|
-
if (route) {
|
|
758
|
-
const triageTransport = hasOwn(route, 'triageTransportRetries'), ambiguityTransport = hasOwn(route, 'ambiguityTransportRetries');
|
|
759
|
-
if (triageTransport !== ambiguityTransport)
|
|
760
|
-
throw new Error('legacy route transport budgets are incomplete');
|
|
761
|
-
if (triageTransport) {
|
|
762
|
-
legacyBits([route.triageTransportRetries, route.ambiguityTransportRetries], 'route');
|
|
763
|
-
delete route.triageTransportRetries;
|
|
764
|
-
delete route.ambiguityTransportRetries;
|
|
765
|
-
changed = true;
|
|
766
|
-
}
|
|
767
|
-
if (hasOwn(route, 'previousAttemptId')) {
|
|
768
|
-
delete route.previousAttemptId;
|
|
769
|
-
changed = true;
|
|
770
|
-
}
|
|
771
|
-
routeInFlight = ['triage-in-flight', 'review-in-flight', 'repair-in-flight'].includes(route.phase);
|
|
772
|
-
if (routeInFlight) {
|
|
773
|
-
unsafe = true;
|
|
774
|
-
delete route.attemptId;
|
|
775
|
-
delete route.startedAt;
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
|
-
const direct = run.directReview;
|
|
779
|
-
if (direct?.review && hasOwn(direct.review, 'transportRetries')) {
|
|
780
|
-
legacyBits([direct.review.transportRetries], 'direct review');
|
|
781
|
-
delete direct.review.transportRetries;
|
|
782
|
-
changed = true;
|
|
783
|
-
}
|
|
784
|
-
if (direct && hasOwn(direct, 'invocation')) {
|
|
785
|
-
delete direct.invocation;
|
|
786
|
-
unsafe = changed = true;
|
|
787
|
-
}
|
|
788
|
-
const spec = run.specDelivery;
|
|
789
|
-
if (spec?.budgets?.author && hasOwn(spec.budgets.author, 'transportRetries')) {
|
|
790
|
-
legacyBits([spec.budgets.author.transportRetries], 'spec author');
|
|
791
|
-
delete spec.budgets.author.transportRetries;
|
|
792
|
-
changed = true;
|
|
793
|
-
}
|
|
794
|
-
if (spec?.budgets?.review && hasOwn(spec.budgets.review, 'transportRetries')) {
|
|
795
|
-
legacyBits([spec.budgets.review.transportRetries], 'spec review');
|
|
796
|
-
delete spec.budgets.review.transportRetries;
|
|
797
|
-
changed = true;
|
|
798
|
-
}
|
|
799
|
-
if (spec?.review && !hasOwn(spec.review, 'reviewerSessionId')) {
|
|
800
|
-
spec.review.reviewerSessionId = spec.review.reviewer?.sessionId ?? (spec.invocation?.purpose === 'review' ? spec.invocation.sessionId : null);
|
|
801
|
-
changed = true;
|
|
802
|
-
}
|
|
803
|
-
if (spec?.invocation?.purpose === 'review') {
|
|
804
|
-
delete spec.invocation;
|
|
805
|
-
unsafe = changed = true;
|
|
806
|
-
}
|
|
807
|
-
if (spec?.invocation?.purpose === 'author') {
|
|
808
|
-
if (spec.invocation.status === 'launched')
|
|
809
|
-
unsafe = true;
|
|
810
|
-
if (spec.authorSessionId === null)
|
|
811
|
-
spec.authorSessionId = spec.invocation.sessionId;
|
|
812
|
-
else if (spec.authorSessionId !== spec.invocation.sessionId)
|
|
813
|
-
unsafe = true;
|
|
814
|
-
delete spec.invocation;
|
|
815
|
-
changed = true;
|
|
816
|
-
}
|
|
817
|
-
if (run.reportInvocation && !hasOwn(run.reportInvocation, 'promptFactsSha256')) {
|
|
818
|
-
delete run.reportInvocation;
|
|
819
|
-
unsafe = changed = true;
|
|
820
|
-
}
|
|
821
|
-
if (run.process && ['route', 'code-review', 'spec-author', 'spec-review', 'proof'].includes(run.process.purpose)) {
|
|
822
|
-
delete run.process;
|
|
823
|
-
unsafe = changed = true;
|
|
824
|
-
}
|
|
825
|
-
if (!unsafe)
|
|
826
|
-
return changed;
|
|
827
|
-
if (routeInFlight)
|
|
828
|
-
delete run.routeExecution;
|
|
829
|
-
delete run.reportInvocation;
|
|
830
|
-
delete run.process;
|
|
831
|
-
delete run.intent;
|
|
832
|
-
if (direct)
|
|
833
|
-
run.directReview = projectTerminalDirectReview(direct, { status: 'blocked', kind: 'safety' }, 'report-lifecycle-migration-identity-unavailable');
|
|
834
|
-
run.lifecycle = 'blocked';
|
|
835
|
-
run.outcomeEvidenceId = `report-lifecycle-migration:${String(run.runId)}`;
|
|
836
|
-
run.terminalOutcome = { status: 'blocked', kind: 'safety', resumable: false, evidencePath: backupPath };
|
|
837
|
-
return true;
|
|
838
|
-
}
|
|
839
|
-
function hasLegacyMutableLifecycle(value) {
|
|
840
|
-
if (typeof value !== 'object' || value === null || !Array.isArray(value.runs))
|
|
841
|
-
return false;
|
|
842
|
-
return (value.runs).some((run) => hasOwn(run, 'transportRetries') || run.process?.purpose === 'implementation'
|
|
843
|
-
|| hasOwn(run.checkQualification, 'implementationStarted') || hasOwn(run.checkQualification, 'repairInvocation')
|
|
844
|
-
|| hasOwn(run.checkQualification, 'deniedPathsBaseline') || hasOwn(run.reviewFeedback, 'implementationInvocation'));
|
|
845
|
-
}
|
|
846
|
-
function canonicalizeLegacyMutableRun(run) {
|
|
847
|
-
let changed = false, unsafe = false;
|
|
848
|
-
if (hasOwn(run, 'transportRetries')) {
|
|
849
|
-
legacyBits([run.transportRetries], 'mutable invocation');
|
|
850
|
-
delete run.transportRetries;
|
|
851
|
-
changed = true;
|
|
852
|
-
}
|
|
853
|
-
const qualification = run.checkQualification;
|
|
854
|
-
if (qualification) {
|
|
855
|
-
if (qualification.repairInvocation?.phase === 'launched')
|
|
856
|
-
unsafe = true;
|
|
857
|
-
for (const field of ['implementationStarted', 'repairInvocation', 'deniedPathsBaseline'])
|
|
858
|
-
if (hasOwn(qualification, field)) {
|
|
859
|
-
delete qualification[field];
|
|
860
|
-
changed = true;
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
const feedback = run.reviewFeedback;
|
|
864
|
-
if (feedback && hasOwn(feedback, 'implementationInvocation')) {
|
|
865
|
-
if (feedback.implementationInvocation?.phase === 'launched')
|
|
866
|
-
unsafe = true;
|
|
867
|
-
delete feedback.implementationInvocation;
|
|
868
|
-
changed = true;
|
|
869
|
-
}
|
|
870
|
-
if (run.process?.purpose === 'implementation') {
|
|
871
|
-
delete run.process;
|
|
872
|
-
changed = unsafe = true;
|
|
873
|
-
}
|
|
874
|
-
if (!unsafe)
|
|
875
|
-
return { changed, unsafe };
|
|
876
|
-
delete run.mutableInvocation;
|
|
877
|
-
if (feedback?.activeBatch)
|
|
878
|
-
run.reviewFeedback = blockReviewFeedback(feedback, 'safety', String(run.updatedAt));
|
|
879
|
-
return { changed: true, unsafe: true };
|
|
880
|
-
}
|
|
881
|
-
function legacyBits(values, owner) {
|
|
882
|
-
if (values.some((value) => value !== 0 && value !== 1))
|
|
883
|
-
throw new Error(`legacy ${owner} transport budget is invalid`);
|
|
618
|
+
return { schema: 'codex-orchestrator.run-state', generation: 0, runs: [] };
|
|
884
619
|
}
|
|
885
620
|
function parseRawState(bytes) {
|
|
886
621
|
let value;
|
|
@@ -892,50 +627,24 @@ function parseRawState(bytes) {
|
|
|
892
627
|
}
|
|
893
628
|
return validateRunStateFile(value);
|
|
894
629
|
}
|
|
895
|
-
async function
|
|
630
|
+
async function readOptionalStateFile(path) {
|
|
631
|
+
let handle;
|
|
896
632
|
try {
|
|
897
|
-
|
|
633
|
+
handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
898
634
|
}
|
|
899
635
|
catch (error) {
|
|
900
636
|
if (isErrorCode(error, 'ENOENT'))
|
|
901
637
|
return undefined;
|
|
902
638
|
throw error;
|
|
903
639
|
}
|
|
904
|
-
}
|
|
905
|
-
async function readMigrationMetadata(path) {
|
|
906
|
-
const bytes = await readOptionalFile(path);
|
|
907
|
-
if (!bytes)
|
|
908
|
-
return undefined;
|
|
909
|
-
let value;
|
|
910
640
|
try {
|
|
911
|
-
|
|
641
|
+
const stat = await handle.stat();
|
|
642
|
+
if (!stat.isFile())
|
|
643
|
+
throw new Error(`${path} is not a regular file`);
|
|
644
|
+
return await handle.readFile();
|
|
912
645
|
}
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
}
|
|
916
|
-
assertExactObject(value, [
|
|
917
|
-
'version', 'sourceGeneration', 'sourceBytesSha256', 'publicationEffectPossible', 'createdAt', 'updatedAt',
|
|
918
|
-
], 'candidate migration metadata');
|
|
919
|
-
if (value.version !== 1)
|
|
920
|
-
throw new Error('candidate migration metadata version is invalid');
|
|
921
|
-
if (value.sourceGeneration !== null && (!Number.isSafeInteger(value.sourceGeneration) || value.sourceGeneration <= 0)) {
|
|
922
|
-
throw new Error('candidate migration source generation is invalid');
|
|
923
|
-
}
|
|
924
|
-
if (value.sourceBytesSha256 !== null)
|
|
925
|
-
assertSha256(value.sourceBytesSha256, 'candidate migration source hash');
|
|
926
|
-
if ((value.sourceGeneration === null) !== (value.sourceBytesSha256 === null))
|
|
927
|
-
throw new Error('candidate migration source identity is incomplete');
|
|
928
|
-
if (typeof value.publicationEffectPossible !== 'boolean')
|
|
929
|
-
throw new Error('candidate migration publication watermark is invalid');
|
|
930
|
-
assertTimestamp(value.createdAt, 'candidate migration createdAt');
|
|
931
|
-
assertTimestamp(value.updatedAt, 'candidate migration updatedAt');
|
|
932
|
-
return value;
|
|
933
|
-
}
|
|
934
|
-
function assertNoCandidateV3Fields(runs) {
|
|
935
|
-
for (const run of runs) {
|
|
936
|
-
if (run.changeBindingVersion !== undefined || run.candidateBinding !== undefined || run.executionLease !== undefined) {
|
|
937
|
-
throw new Error('V2 run state cannot contain candidate V3 fields');
|
|
938
|
-
}
|
|
646
|
+
finally {
|
|
647
|
+
await handle.close();
|
|
939
648
|
}
|
|
940
649
|
}
|
|
941
650
|
function isErrorCode(error, code) {
|
|
@@ -949,22 +658,18 @@ function validateReviewFeedbackRunInvariant(run, field) {
|
|
|
949
658
|
|| batch.priorPublishedHeadSha !== feedback.previousPublishedHeadSha)) {
|
|
950
659
|
throw new Error(`${field}.reviewFeedback active batch identity binding is invalid`);
|
|
951
660
|
}
|
|
952
|
-
const retainedQuiescentHistory =
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
&& run.lifecycle !== 'review-ready' && !retainedQuiescentHistory) {
|
|
956
|
-
throw new Error(`${field}.reviewFeedback quiescent phase requires review-ready lifecycle`);
|
|
957
|
-
}
|
|
958
|
-
if (['frozen', 'repairing'].includes(feedback.phase) && !['implementing', 'reworking', 'checking', 'proving'].includes(run.lifecycle)) {
|
|
959
|
-
throw new Error(`${field}.reviewFeedback active repair phase has invalid lifecycle`);
|
|
960
|
-
}
|
|
961
|
-
if (feedback.phase === 'verified' && run.lifecycle !== 'publishing') {
|
|
962
|
-
throw new Error(`${field}.reviewFeedback verified phase requires publishing lifecycle`);
|
|
661
|
+
const retainedQuiescentHistory = !batch && run.lifecycle === 'blocked' && run.terminalOutcome?.status === 'blocked';
|
|
662
|
+
if (!batch && run.lifecycle !== 'review-ready' && !retainedQuiescentHistory) {
|
|
663
|
+
throw new Error(`${field}.reviewFeedback quiescent data requires review-ready lifecycle`);
|
|
963
664
|
}
|
|
964
|
-
if (
|
|
965
|
-
|
|
665
|
+
if (batch && !feedback.verifiedReceipt
|
|
666
|
+
&& !['implementing', 'checking', 'proving', 'reviewing', 'safe-halt'].includes(run.lifecycle)) {
|
|
667
|
+
throw new Error(`${field}.reviewFeedback active batch has invalid lifecycle`);
|
|
966
668
|
}
|
|
967
|
-
if (feedback.
|
|
669
|
+
if (feedback.verifiedReceipt) {
|
|
670
|
+
if (run.lifecycle !== 'reviewing' && run.lifecycle !== 'publishing') {
|
|
671
|
+
throw new Error(`${field}.reviewFeedback verified batch requires review or publication lifecycle`);
|
|
672
|
+
}
|
|
968
673
|
const verified = feedback.verifiedReceipt;
|
|
969
674
|
if (!verified || verified.batchId !== batch?.batchId
|
|
970
675
|
|| verified.checkedChangeSha256 !== run.checkedChangeSha256
|
|
@@ -973,16 +678,10 @@ function validateReviewFeedbackRunInvariant(run, field) {
|
|
|
973
678
|
throw new Error(`${field}.reviewFeedback verification receipt binding is invalid`);
|
|
974
679
|
}
|
|
975
680
|
}
|
|
976
|
-
if (feedback.phase === 'blocked-safety' || feedback.phase === 'blocked-exhausted') {
|
|
977
|
-
if (run.lifecycle !== 'blocked' || run.terminalOutcome?.status !== 'blocked' || run.terminalOutcome.resumable
|
|
978
|
-
|| run.terminalOutcome.kind !== (feedback.phase === 'blocked-safety' ? 'safety' : 'exhausted')) {
|
|
979
|
-
throw new Error(`${field}.reviewFeedback terminal projection mismatch`);
|
|
980
|
-
}
|
|
981
|
-
}
|
|
982
681
|
}
|
|
983
682
|
function isLifecycle(value) {
|
|
984
683
|
return typeof value === 'string' && [
|
|
985
|
-
'claimed', '
|
|
684
|
+
'claimed', 'implementing', 'checking', 'proving', 'reviewing', 'publishing', 'safe-halt',
|
|
986
685
|
'review-ready', 'blocked', 'transport-failed', 'cancelled', 'internal-error',
|
|
987
686
|
].includes(value);
|
|
988
687
|
}
|
|
@@ -1001,6 +700,10 @@ function assertNonEmptyString(value, field) {
|
|
|
1001
700
|
if (typeof value !== 'string' || value.length === 0 || value.length > 16 * 1024)
|
|
1002
701
|
throw new Error(`${field} is invalid`);
|
|
1003
702
|
}
|
|
703
|
+
function assertAbsolutePath(value, field) {
|
|
704
|
+
if (typeof value !== 'string' || !value.startsWith('/') || posix.normalize(value) !== value)
|
|
705
|
+
throw new Error(`${field} is invalid`);
|
|
706
|
+
}
|
|
1004
707
|
function assertPositiveInteger(value, field) {
|
|
1005
708
|
if (!Number.isSafeInteger(value) || value <= 0)
|
|
1006
709
|
throw new Error(`${field} is invalid`);
|