codex-orchestrator 2.0.10 → 2.0.12
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 +52 -0
- package/README.md +29 -30
- package/dist/src/index.d.ts +4 -9
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -5
- package/dist/src/index.js.map +1 -1
- package/dist/src/v2/acceptance-proof.d.ts +53 -25
- package/dist/src/v2/acceptance-proof.d.ts.map +1 -1
- package/dist/src/v2/acceptance-proof.js +189 -197
- 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 +7 -0
- package/dist/src/v2/adapters/command.d.ts.map +1 -1
- package/dist/src/v2/adapters/command.js +44 -3
- package/dist/src/v2/adapters/command.js.map +1 -1
- package/dist/src/v2/adapters/gh-pull-request-adapter.js +1 -0
- package/dist/src/v2/adapters/gh-pull-request-adapter.js.map +1 -1
- package/dist/src/v2/adapters/pull-requests.d.ts +1 -0
- package/dist/src/v2/adapters/pull-requests.d.ts.map +1 -1
- package/dist/src/v2/adapters/pull-requests.js +1 -0
- package/dist/src/v2/adapters/pull-requests.js.map +1 -1
- package/dist/src/v2/adapters/worktree.js +3 -3
- package/dist/src/v2/adapters/worktree.js.map +1 -1
- package/dist/src/v2/atomic-store.d.ts +15 -0
- package/dist/src/v2/atomic-store.d.ts.map +1 -1
- package/dist/src/v2/atomic-store.js +55 -8
- package/dist/src/v2/atomic-store.js.map +1 -1
- package/dist/src/v2/candidate.d.ts +136 -0
- package/dist/src/v2/candidate.d.ts.map +1 -0
- package/dist/src/v2/candidate.js +107 -0
- package/dist/src/v2/candidate.js.map +1 -0
- package/dist/src/v2/checked-change.d.ts +39 -7
- package/dist/src/v2/checked-change.d.ts.map +1 -1
- package/dist/src/v2/checked-change.js +73 -1
- 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 +14 -0
- package/dist/src/v2/cli.d.ts.map +1 -1
- package/dist/src/v2/cli.js +70 -21
- 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 -1
- package/dist/src/v2/codex-process.d.ts.map +1 -1
- package/dist/src/v2/codex-process.js +36 -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 +17 -6
- package/dist/src/v2/contained-report-operation.d.ts.map +1 -1
- package/dist/src/v2/contained-report-operation.js +11 -24
- 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 +19 -57
- package/dist/src/v2/direct-delivery.d.ts.map +1 -1
- package/dist/src/v2/direct-delivery.js +154 -211
- 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 +17 -4
- package/dist/src/v2/implementation-report.js.map +1 -1
- package/dist/src/v2/implementation-reviewer.d.ts +22 -13
- package/dist/src/v2/implementation-reviewer.d.ts.map +1 -1
- package/dist/src/v2/implementation-reviewer.js +96 -30
- package/dist/src/v2/implementation-reviewer.js.map +1 -1
- package/dist/src/v2/owner-control-lock.d.ts +13 -1
- package/dist/src/v2/owner-control-lock.d.ts.map +1 -1
- package/dist/src/v2/owner-control-lock.js +66 -7
- package/dist/src/v2/owner-control-lock.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 +4 -2
- package/dist/src/v2/proof-report.d.ts.map +1 -1
- package/dist/src/v2/proof-report.js +16 -44
- package/dist/src/v2/proof-report.js.map +1 -1
- package/dist/src/v2/review-feedback-coordinator.d.ts +8 -2
- package/dist/src/v2/review-feedback-coordinator.d.ts.map +1 -1
- package/dist/src/v2/review-feedback-coordinator.js +98 -71
- package/dist/src/v2/review-feedback-coordinator.js.map +1 -1
- package/dist/src/v2/review-feedback.d.ts +14 -38
- package/dist/src/v2/review-feedback.d.ts.map +1 -1
- package/dist/src/v2/review-feedback.js +48 -115
- package/dist/src/v2/review-feedback.js.map +1 -1
- package/dist/src/v2/run-issue.d.ts +139 -60
- package/dist/src/v2/run-issue.d.ts.map +1 -1
- package/dist/src/v2/run-issue.js +2236 -1974
- 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 +114 -82
- package/dist/src/v2/run-store.d.ts.map +1 -1
- package/dist/src/v2/run-store.js +308 -224
- 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 +52 -15
- package/dist/src/v2/runtime.d.ts.map +1 -1
- package/dist/src/v2/runtime.js +818 -348
- 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/docs/deep-dive.md +52 -14
- 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 -288
- 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 -42
- package/dist/src/v2/proof-store.d.ts.map +0 -1
- package/dist/src/v2/proof-store.js +0 -180
- 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 -77
- package/dist/src/v2/route-coordinator.d.ts.map +0 -1
- package/dist/src/v2/route-coordinator.js +0 -370
- package/dist/src/v2/route-coordinator.js.map +0 -1
- package/dist/src/v2/route-decision.d.ts +0 -129
- package/dist/src/v2/route-decision.d.ts.map +0 -1
- package/dist/src/v2/route-decision.js +0 -400
- package/dist/src/v2/route-decision.js.map +0 -1
- package/dist/src/v2/spec-coordinator.d.ts +0 -85
- package/dist/src/v2/spec-coordinator.d.ts.map +0 -1
- package/dist/src/v2/spec-coordinator.js +0 -88
- package/dist/src/v2/spec-coordinator.js.map +0 -1
- package/dist/src/v2/spec-delivery.d.ts +0 -143
- package/dist/src/v2/spec-delivery.d.ts.map +0 -1
- package/dist/src/v2/spec-delivery.js +0 -401
- 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 -332
- 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 -126
- 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,78 +1,103 @@
|
|
|
1
|
+
import { validateActiveAttempt } from './active-attempt.js';
|
|
1
2
|
import { validateDirectReview } from './direct-delivery.js';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { validateWaitingHumanExecution } from './waiting-human.js';
|
|
3
|
+
import { validateDeliveryAuthority } from './delivery-authority.js';
|
|
4
|
+
import { constants } from 'node:fs';
|
|
5
5
|
import { posix } from 'node:path';
|
|
6
|
+
import { open } from 'node:fs/promises';
|
|
6
7
|
import { AtomicStateFile } from './atomic-store.js';
|
|
7
|
-
import {
|
|
8
|
+
import { canonicalJson, sha256 } from './containment.js';
|
|
9
|
+
import { validateCandidateBinding, validateCandidateMaterialization } from './candidate.js';
|
|
10
|
+
import { validateReviewFeedbackRunData, } from './review-feedback.js';
|
|
8
11
|
const SHA256_PATTERN = /^[0-9a-f]{64}$/;
|
|
9
12
|
const GIT_SHA_PATTERN = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
|
|
10
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
|
+
}
|
|
11
17
|
export class WorkflowGenerationUnrecoverableError extends Error {
|
|
12
18
|
constructor() {
|
|
13
19
|
super('workflow-generation-unrecoverable');
|
|
14
20
|
this.name = 'WorkflowGenerationUnrecoverableError';
|
|
15
21
|
}
|
|
16
22
|
}
|
|
17
|
-
export class
|
|
23
|
+
export class IssueInitializationUnrecoverableError extends Error {
|
|
18
24
|
constructor() {
|
|
19
|
-
super('
|
|
20
|
-
this.name = '
|
|
25
|
+
super('issue-initialization-unrecoverable');
|
|
26
|
+
this.name = 'IssueInitializationUnrecoverableError';
|
|
21
27
|
}
|
|
22
28
|
}
|
|
23
29
|
export class FileRunRecordWriter {
|
|
24
30
|
file;
|
|
31
|
+
maxBytes;
|
|
25
32
|
constructor(path, options = {}) {
|
|
26
33
|
this.file = new AtomicStateFile(path, validateRunStateFile, options);
|
|
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 };
|
|
43
|
+
try {
|
|
44
|
+
return { status: 'supported', rawSha256, state: parseRawState(bytes) };
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return { status: 'unsupported', rawSha256 };
|
|
48
|
+
}
|
|
27
49
|
}
|
|
28
50
|
async read() {
|
|
29
|
-
|
|
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');
|
|
30
57
|
}
|
|
31
58
|
async compareAndSwap(expectedGeneration, next) {
|
|
32
59
|
validateRunStateBody(next);
|
|
33
|
-
|
|
60
|
+
const candidate = { ...structuredClone(next), generation: expectedGeneration + 1 };
|
|
61
|
+
return this.file.compareAndSwap(expectedGeneration, candidate);
|
|
34
62
|
}
|
|
35
63
|
}
|
|
36
64
|
export class InMemoryRunRecordWriter {
|
|
37
|
-
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
|
+
}
|
|
38
76
|
async read() {
|
|
39
|
-
return structuredClone(this.state);
|
|
77
|
+
return structuredClone(this.state ?? emptyRunState());
|
|
40
78
|
}
|
|
41
79
|
async compareAndSwap(expectedGeneration, next) {
|
|
42
|
-
if (this.state
|
|
80
|
+
if ((this.state?.generation ?? 0) !== expectedGeneration)
|
|
43
81
|
throw new Error('run state generation conflict');
|
|
82
|
+
validateRunStateBody(next);
|
|
44
83
|
const value = validateRunStateFile({ ...structuredClone(next), generation: expectedGeneration + 1 });
|
|
45
84
|
this.state = value;
|
|
46
85
|
return structuredClone(value);
|
|
47
86
|
}
|
|
48
87
|
}
|
|
49
88
|
export function validateRunStateFile(value) {
|
|
50
|
-
assertExactObject(value, ['schema', '
|
|
51
|
-
if (value.schema !== 'codex-orchestrator.
|
|
52
|
-
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');
|
|
53
92
|
if (!Number.isSafeInteger(value.generation) || value.generation <= 0)
|
|
54
93
|
throw new Error('run state generation is invalid');
|
|
55
94
|
validateRuns(value.runs);
|
|
56
|
-
if (value.version === 1) {
|
|
57
|
-
for (const run of value.runs) {
|
|
58
|
-
if (hasOwn(run, 'reviewFeedback'))
|
|
59
|
-
throw new Error('V1 run state cannot contain review feedback');
|
|
60
|
-
}
|
|
61
|
-
return {
|
|
62
|
-
schema: 'codex-orchestrator.agent-auto-state',
|
|
63
|
-
version: 2,
|
|
64
|
-
generation: value.generation,
|
|
65
|
-
runs: value.runs.map((run) => run.lifecycle === 'review-ready'
|
|
66
|
-
? { ...structuredClone(run), reviewFeedback: createReviewFeedbackBootstrap() }
|
|
67
|
-
: structuredClone(run)),
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
95
|
return value;
|
|
71
96
|
}
|
|
72
97
|
function validateRunStateBody(value) {
|
|
73
|
-
assertExactObject(value, ['schema', '
|
|
74
|
-
if (value.schema !== 'codex-orchestrator.
|
|
75
|
-
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');
|
|
76
101
|
validateRuns(value.runs);
|
|
77
102
|
}
|
|
78
103
|
function validateRuns(value) {
|
|
@@ -88,21 +113,20 @@ function validateRuns(value) {
|
|
|
88
113
|
}
|
|
89
114
|
function validateRunRecord(value, field) {
|
|
90
115
|
const optional = [
|
|
91
|
-
'
|
|
116
|
+
'activeAttempt',
|
|
92
117
|
'checkedChangeSha256',
|
|
93
118
|
'proofId',
|
|
119
|
+
'proofExecution',
|
|
94
120
|
'proofReceipt',
|
|
95
|
-
'
|
|
121
|
+
'pendingEffect',
|
|
96
122
|
'outcomeEvidenceId',
|
|
97
123
|
'terminalOutcome',
|
|
98
|
-
'
|
|
99
|
-
'routeReceipt',
|
|
100
|
-
'waitingHuman',
|
|
124
|
+
'deliveryAuthority',
|
|
101
125
|
'directReview',
|
|
102
|
-
'specDelivery',
|
|
103
126
|
'reviewFeedback',
|
|
104
|
-
'
|
|
105
|
-
'
|
|
127
|
+
'changeBindingVersion',
|
|
128
|
+
'candidateBinding',
|
|
129
|
+
'candidateMaterialization',
|
|
106
130
|
].filter((key) => hasOwn(value, key));
|
|
107
131
|
assertExactObject(value, [
|
|
108
132
|
'runId', 'issueNumber', 'canonicalRepository', 'baseSha', 'branchName', 'worktreePath', 'lifecycle', 'cycle',
|
|
@@ -122,11 +146,11 @@ function validateRunRecord(value, field) {
|
|
|
122
146
|
}
|
|
123
147
|
if (!isLifecycle(value.lifecycle))
|
|
124
148
|
throw new Error(`${field}.lifecycle is invalid`);
|
|
125
|
-
if (!Number.isSafeInteger(value.cycle) || value.cycle < 1
|
|
149
|
+
if (!Number.isSafeInteger(value.cycle) || value.cycle < 1)
|
|
126
150
|
throw new Error(`${field}.cycle is invalid`);
|
|
127
|
-
if (value.reportRepairs
|
|
151
|
+
if (!Number.isSafeInteger(value.reportRepairs) || value.reportRepairs < 0)
|
|
128
152
|
throw new Error(`${field}.reportRepairs is invalid`);
|
|
129
|
-
if (value.transportRetries
|
|
153
|
+
if (!Number.isSafeInteger(value.transportRetries) || value.transportRetries < 0)
|
|
130
154
|
throw new Error(`${field}.transportRetries is invalid`);
|
|
131
155
|
validateIssueSnapshot(value.issueSnapshot, `${field}.issueSnapshot`);
|
|
132
156
|
validateFrozenCriteria(value.frozenCriteria, `${field}.frozenCriteria`);
|
|
@@ -137,80 +161,91 @@ function validateRunRecord(value, field) {
|
|
|
137
161
|
if (workflowGeneration.packageVersion !== value.packageVersion) {
|
|
138
162
|
throw new Error(`${field}.workflowGeneration package version mismatch`);
|
|
139
163
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
+
}
|
|
145
177
|
validateStringShaRecord(value.skillHashes, `${field}.skillHashes`);
|
|
146
|
-
if (hasOwn(value, 'baselineChecks'))
|
|
147
|
-
validateChecks(value.baselineChecks, `${field}.baselineChecks`, false);
|
|
148
178
|
validateChecks(value.checks, `${field}.checks`);
|
|
149
|
-
if (hasOwn(value, '
|
|
150
|
-
|
|
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`);
|
|
183
|
+
}
|
|
151
184
|
if (hasOwn(value, 'checkedChangeSha256'))
|
|
152
185
|
assertSha256(value.checkedChangeSha256, `${field}.checkedChangeSha256`);
|
|
153
186
|
if (hasOwn(value, 'proofId'))
|
|
154
187
|
assertNonEmptyString(value.proofId, `${field}.proofId`);
|
|
188
|
+
if (hasOwn(value, 'proofExecution'))
|
|
189
|
+
validateProofExecution(value.proofExecution, `${field}.proofExecution`);
|
|
155
190
|
if (hasOwn(value, 'proofReceipt'))
|
|
156
191
|
validateReceipt(value.proofReceipt, `${field}.proofReceipt`);
|
|
157
|
-
if (hasOwn(value, '
|
|
158
|
-
|
|
192
|
+
if (hasOwn(value, 'pendingEffect'))
|
|
193
|
+
validatePendingEffect(value.pendingEffect, `${field}.pendingEffect`);
|
|
159
194
|
if (hasOwn(value, 'outcomeEvidenceId'))
|
|
160
195
|
assertNonEmptyString(value.outcomeEvidenceId, `${field}.outcomeEvidenceId`);
|
|
161
196
|
if (hasOwn(value, 'terminalOutcome'))
|
|
162
197
|
validateTerminalOutcome(value.terminalOutcome, `${field}.terminalOutcome`);
|
|
163
|
-
if (hasOwn(value, 'waitingHuman')) {
|
|
164
|
-
if (!routeGenerationHash)
|
|
165
|
-
throw new WorkflowGenerationUnrecoverableError();
|
|
166
|
-
validateWaitingHumanExecution(value.waitingHuman, {
|
|
167
|
-
runId: value.runId,
|
|
168
|
-
lifecycle: value.lifecycle,
|
|
169
|
-
workflowGenerationHash: routeGenerationHash,
|
|
170
|
-
routeReceipt: hasOwn(value, 'routeReceipt') ? value.routeReceipt : undefined,
|
|
171
|
-
terminalOutcome: hasOwn(value, 'terminalOutcome') ? value.terminalOutcome : undefined,
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
198
|
if (hasOwn(value, 'directReview')) {
|
|
175
|
-
if (!hasOwn(value, '
|
|
176
|
-
throw new Error(`${field}.directReview requires
|
|
177
|
-
}
|
|
178
|
-
const rawProcess = hasOwn(value, 'process') && hasOwn(value.process, 'purpose')
|
|
179
|
-
? value.process
|
|
180
|
-
: undefined;
|
|
181
|
-
const process = rawProcess && !['spec-author', 'spec-review'].includes(rawProcess.purpose)
|
|
182
|
-
? {
|
|
183
|
-
purpose: rawProcess.purpose,
|
|
184
|
-
resumeLifecycle: rawProcess.resumeLifecycle,
|
|
185
|
-
resumeReviewStage: rawProcess.resumeReviewStage,
|
|
186
|
-
}
|
|
187
|
-
: undefined;
|
|
199
|
+
if (!hasOwn(value, 'deliveryAuthority'))
|
|
200
|
+
throw new Error(`${field}.directReview requires delivery authority`);
|
|
188
201
|
validateDirectReview(value.directReview, {
|
|
189
202
|
lifecycle: value.lifecycle,
|
|
190
203
|
...(hasOwn(value, 'terminalOutcome') ? { terminalOutcome: directTerminalOutcome(value.terminalOutcome) } : {}),
|
|
191
|
-
...(
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
resumeReviewStage: process.resumeReviewStage,
|
|
195
|
-
} } : {}),
|
|
204
|
+
...(hasOwn(value, 'terminalOutcome') && value.terminalOutcome.status === 'internal-error'
|
|
205
|
+
? { terminalCode: value.terminalOutcome.code }
|
|
206
|
+
: {}),
|
|
196
207
|
});
|
|
197
208
|
}
|
|
198
|
-
if (hasOwn(value, '
|
|
199
|
-
|
|
200
|
-
|
|
209
|
+
if (hasOwn(value, 'reviewFeedback')) {
|
|
210
|
+
validateReviewFeedbackRunData(value.reviewFeedback);
|
|
211
|
+
validateReviewFeedbackRunInvariant(value, field);
|
|
212
|
+
}
|
|
213
|
+
if (hasOwn(value, 'changeBindingVersion') && value.changeBindingVersion !== 2)
|
|
214
|
+
throw new Error(`${field}.changeBindingVersion is invalid`);
|
|
215
|
+
if (hasOwn(value, 'candidateBinding'))
|
|
216
|
+
validateCandidateBinding(value.candidateBinding, `${field}.candidateBinding`, value.runId);
|
|
217
|
+
if (hasOwn(value, 'candidateMaterialization'))
|
|
218
|
+
validateCandidateMaterialization(value.candidateMaterialization, `${field}.candidateMaterialization`);
|
|
219
|
+
if (hasOwn(value, 'candidateBinding') !== hasOwn(value, 'changeBindingVersion'))
|
|
220
|
+
throw new Error(`${field} candidate binding version is incomplete`);
|
|
221
|
+
if (hasOwn(value, 'candidateMaterialization')) {
|
|
222
|
+
if (!hasOwn(value, 'candidateBinding'))
|
|
223
|
+
throw new Error(`${field}.candidateMaterialization requires candidate binding`);
|
|
224
|
+
const binding = value.candidateBinding;
|
|
225
|
+
const materialization = value.candidateMaterialization;
|
|
226
|
+
if (materialization.bindingId !== binding.bindingId || materialization.candidateCommitSha !== binding.candidateCommitSha) {
|
|
227
|
+
throw new Error(`${field}.candidateMaterialization binding is invalid`);
|
|
201
228
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
throw new Error(`${field}.
|
|
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`);
|
|
206
237
|
}
|
|
207
238
|
}
|
|
208
|
-
if (
|
|
209
|
-
|
|
210
|
-
|
|
239
|
+
if (value.pendingEffect?.kind === 'candidate-pin-release') {
|
|
240
|
+
if (!hasOwn(value, 'candidateBinding'))
|
|
241
|
+
throw new Error(`${field}.candidate pin cleanup requires candidate binding`);
|
|
242
|
+
const binding = value.candidateBinding;
|
|
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`);
|
|
247
|
+
}
|
|
211
248
|
}
|
|
212
|
-
if (hasOwn(value, 'checkQualification'))
|
|
213
|
-
validateCheckQualification(value.checkQualification, `${field}.checkQualification`);
|
|
214
249
|
assertTimestamp(value.createdAt, `${field}.createdAt`);
|
|
215
250
|
assertTimestamp(value.updatedAt, `${field}.updatedAt`);
|
|
216
251
|
const terminal = ['review-ready', 'blocked', 'transport-failed', 'cancelled', 'internal-error'].includes(value.lifecycle);
|
|
@@ -222,29 +257,32 @@ function validateRunRecord(value, field) {
|
|
|
222
257
|
|| value.checks.some((check) => check.status === 'failed'))) {
|
|
223
258
|
throw new Error(`${field} proving requires passed checks and checked change proof identity`);
|
|
224
259
|
}
|
|
225
|
-
if (value.lifecycle === 'publishing' && !hasOwn(value, 'proofReceipt'))
|
|
226
|
-
throw new Error(`${field}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
260
|
+
if ((value.lifecycle === 'reviewing' || value.lifecycle === 'publishing') && !hasOwn(value, 'proofReceipt')) {
|
|
261
|
+
throw new Error(`${field} review and publication require proofReceipt`);
|
|
262
|
+
}
|
|
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'
|
|
272
|
+
&& value.terminalOutcome?.status === 'blocked'
|
|
273
|
+
&& value.terminalOutcome.kind === 'safety'
|
|
274
|
+
&& !value.terminalOutcome.resumable
|
|
275
|
+
&& hasOwn(value, 'candidateBinding')
|
|
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
|
|
237
283
|
&& value.terminalOutcome.resumable) {
|
|
238
|
-
throw new Error(`${field} resumable transport failure cannot retain
|
|
284
|
+
throw new Error(`${field} resumable transport failure cannot retain pending effect`);
|
|
239
285
|
}
|
|
240
|
-
if (value.lifecycle === 'waiting-human' && !hasOwn(value, 'waitingHuman'))
|
|
241
|
-
throw new Error(`${field} waiting-human lifecycle requires waitingHuman execution`);
|
|
242
|
-
validateRouteStateInvariant({
|
|
243
|
-
lifecycle: value.lifecycle,
|
|
244
|
-
routeExecution: value.routeExecution,
|
|
245
|
-
routeReceipt: value.routeReceipt,
|
|
246
|
-
generationHash: routeGenerationHash,
|
|
247
|
-
});
|
|
248
286
|
}
|
|
249
287
|
function directTerminalOutcome(outcome) {
|
|
250
288
|
if (outcome.status === 'review-ready')
|
|
@@ -265,83 +303,31 @@ function validateWorkflowGeneration(value, field) {
|
|
|
265
303
|
}
|
|
266
304
|
assertSha256(value.contentSha256, `${field}.contentSha256`);
|
|
267
305
|
}
|
|
268
|
-
function
|
|
269
|
-
assertExactObject(value, [
|
|
270
|
-
'pid', 'processGroupId', 'startedAt', 'baseline', 'purpose', 'resumeLifecycle', 'resumeReviewStage',
|
|
271
|
-
], field);
|
|
272
|
-
assertPositiveInteger(value.pid, `${field}.pid`);
|
|
273
|
-
assertPositiveInteger(value.processGroupId, `${field}.processGroupId`);
|
|
274
|
-
assertTimestamp(value.startedAt, `${field}.startedAt`);
|
|
275
|
-
assertExactObject(value.baseline, [
|
|
276
|
-
'headSha', 'indexTreeSha', 'trackedContentSha256', 'untrackedContentSha256', 'worktreeIdentity',
|
|
277
|
-
], `${field}.baseline`);
|
|
278
|
-
assertGitSha(value.baseline.headSha, `${field}.baseline.headSha`);
|
|
279
|
-
assertGitSha(value.baseline.indexTreeSha, `${field}.baseline.indexTreeSha`);
|
|
280
|
-
assertSha256(value.baseline.trackedContentSha256, `${field}.baseline.trackedContentSha256`);
|
|
281
|
-
assertSha256(value.baseline.untrackedContentSha256, `${field}.baseline.untrackedContentSha256`);
|
|
282
|
-
assertNonEmptyString(value.baseline.worktreeIdentity, `${field}.baseline.worktreeIdentity`);
|
|
283
|
-
if (!['route', 'implementation', 'code-review', 'proof', 'spec-author', 'spec-review'].includes(value.purpose)) {
|
|
284
|
-
throw new Error(`${field}.purpose is invalid`);
|
|
285
|
-
}
|
|
286
|
-
if (!isLifecycle(value.resumeLifecycle))
|
|
287
|
-
throw new Error(`${field}.resumeLifecycle is invalid`);
|
|
288
|
-
if (value.resumeReviewStage !== null && ![
|
|
289
|
-
'review-full', 'review-repair', 'review-closure',
|
|
290
|
-
].includes(value.resumeReviewStage))
|
|
291
|
-
throw new Error(`${field}.resumeReviewStage is invalid`);
|
|
292
|
-
}
|
|
293
|
-
function validateChecks(value, field, allowUnchangedFailure = true) {
|
|
306
|
+
function validateChecks(value, field) {
|
|
294
307
|
if (!Array.isArray(value) || value.length > 256)
|
|
295
308
|
throw new Error(`${field} is invalid`);
|
|
296
309
|
const ids = new Set();
|
|
297
310
|
for (const [index, check] of value.entries()) {
|
|
298
|
-
|
|
311
|
+
const candidate = hasOwn(check, 'bindingId') || hasOwn(check, 'candidateTreeSha') || hasOwn(check, 'checkPolicySha256');
|
|
312
|
+
assertExactObject(check, [
|
|
313
|
+
'id', 'command', 'status', 'outputSha256',
|
|
314
|
+
...(candidate ? ['bindingId', 'candidateTreeSha', 'checkPolicySha256'] : []),
|
|
315
|
+
], `${field}[${index}]`);
|
|
299
316
|
assertNonEmptyString(check.id, `${field}[${index}].id`);
|
|
300
317
|
assertNonEmptyString(check.command, `${field}[${index}].command`);
|
|
301
|
-
if (check.status !== 'passed' && check.status !== 'failed'
|
|
302
|
-
&& (check.status !== 'unchanged-failure' || !allowUnchangedFailure))
|
|
318
|
+
if (check.status !== 'passed' && check.status !== 'failed')
|
|
303
319
|
throw new Error(`${field}[${index}].status is invalid`);
|
|
304
320
|
assertSha256(check.outputSha256, `${field}[${index}].outputSha256`);
|
|
321
|
+
if (candidate) {
|
|
322
|
+
assertSha256(check.bindingId, `${field}[${index}].bindingId`);
|
|
323
|
+
assertGitSha(check.candidateTreeSha, `${field}[${index}].candidateTreeSha`);
|
|
324
|
+
assertSha256(check.checkPolicySha256, `${field}[${index}].checkPolicySha256`);
|
|
325
|
+
}
|
|
305
326
|
if (ids.has(check.id))
|
|
306
327
|
throw new Error(`${field} IDs must be unique`);
|
|
307
328
|
ids.add(check.id);
|
|
308
329
|
}
|
|
309
330
|
}
|
|
310
|
-
function validateCheckQualification(value, field) {
|
|
311
|
-
const optional = [
|
|
312
|
-
...(hasOwn(value, 'implementationStarted') ? ['implementationStarted'] : []),
|
|
313
|
-
...(hasOwn(value, 'repairInvocation') ? ['repairInvocation'] : []),
|
|
314
|
-
...(hasOwn(value, 'deniedPathsBaseline') ? ['deniedPathsBaseline'] : []),
|
|
315
|
-
];
|
|
316
|
-
assertExactObject(value, ['version', 'checkPolicySha256', 'repairAttempts', 'checks', ...optional], field);
|
|
317
|
-
if (value.version !== 1)
|
|
318
|
-
throw new Error(`${field}.version is invalid`);
|
|
319
|
-
assertSha256(value.checkPolicySha256, `${field}.checkPolicySha256`);
|
|
320
|
-
if (!Number.isSafeInteger(value.repairAttempts) || value.repairAttempts < 0 || value.repairAttempts > 5) {
|
|
321
|
-
throw new Error(`${field}.repairAttempts is invalid`);
|
|
322
|
-
}
|
|
323
|
-
validateChecks(value.checks, `${field}.checks`, false);
|
|
324
|
-
if (hasOwn(value, 'implementationStarted') && typeof value.implementationStarted !== 'boolean') {
|
|
325
|
-
throw new Error(`${field}.implementationStarted is invalid`);
|
|
326
|
-
}
|
|
327
|
-
if (hasOwn(value, 'repairInvocation') && value.repairInvocation !== null) {
|
|
328
|
-
validateImplementationInvocation(value.repairInvocation);
|
|
329
|
-
}
|
|
330
|
-
if (hasOwn(value, 'deniedPathsBaseline'))
|
|
331
|
-
assertSha256(value.deniedPathsBaseline, `${field}.deniedPathsBaseline`);
|
|
332
|
-
if (hasOwn(value, 'deniedPathsBaseline') !== (hasOwn(value, 'repairInvocation') && value.repairInvocation !== null)) {
|
|
333
|
-
throw new Error(`${field}.deniedPathsBaseline requires a repair invocation`);
|
|
334
|
-
}
|
|
335
|
-
const repairInvocation = hasOwn(value, 'repairInvocation')
|
|
336
|
-
? value.repairInvocation
|
|
337
|
-
: undefined;
|
|
338
|
-
if (value.implementationStarted === true && repairInvocation !== null && repairInvocation !== undefined) {
|
|
339
|
-
throw new Error(`${field} cannot own qualification repair and implementation launches together`);
|
|
340
|
-
}
|
|
341
|
-
if (repairInvocation?.phase === 'launched' && value.repairAttempts === 0) {
|
|
342
|
-
throw new Error(`${field}.repairAttempts must reserve a launched repair`);
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
331
|
function validateIssueSnapshot(value, field) {
|
|
346
332
|
const optional = hasOwn(value, 'comments') ? ['comments'] : [];
|
|
347
333
|
assertExactObject(value, ['number', 'title', 'body', 'url', 'state', 'labels', ...optional], field);
|
|
@@ -398,53 +384,58 @@ function validateStringList(value, field) {
|
|
|
398
384
|
for (const item of value)
|
|
399
385
|
assertNonEmptyString(item, field);
|
|
400
386
|
}
|
|
401
|
-
function
|
|
387
|
+
function validatePendingEffect(value, field) {
|
|
402
388
|
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
403
389
|
throw new Error(`${field} is invalid`);
|
|
404
390
|
const kind = value.kind;
|
|
405
|
-
|
|
406
|
-
|
|
391
|
+
const identity = ['effectId', 'kind'];
|
|
392
|
+
if (kind === 'claim-labels' || kind === 'final-labels') {
|
|
393
|
+
assertExactObject(value, [...identity, 'issueNumber', 'expected'], field);
|
|
407
394
|
assertPositiveInteger(value.issueNumber, `${field}.issueNumber`);
|
|
408
395
|
validateStringArray(value.expected, `${field}.expected`);
|
|
409
396
|
}
|
|
410
|
-
else if (kind === 'commit') {
|
|
411
|
-
assertExactObject(value, [
|
|
397
|
+
else if (kind === 'initial-commit') {
|
|
398
|
+
assertExactObject(value, [...identity, 'parentSha', 'treeSha', 'message', ...(hasOwn(value, 'candidateRef') ? ['candidateRef'] : [])], field);
|
|
412
399
|
assertGitSha(value.parentSha, `${field}.parentSha`);
|
|
413
400
|
assertGitSha(value.treeSha, `${field}.treeSha`);
|
|
414
401
|
assertNonEmptyString(value.message, `${field}.message`);
|
|
402
|
+
if (hasOwn(value, 'candidateRef'))
|
|
403
|
+
assertCandidateRef(value.candidateRef, `${field}.candidateRef`);
|
|
415
404
|
}
|
|
416
|
-
else if (kind === 'push') {
|
|
417
|
-
assertExactObject(value, [
|
|
405
|
+
else if (kind === 'initial-push') {
|
|
406
|
+
assertExactObject(value, [...identity, 'branch', 'sha'], field);
|
|
418
407
|
assertNonEmptyString(value.branch, `${field}.branch`);
|
|
419
408
|
assertGitSha(value.sha, `${field}.sha`);
|
|
420
409
|
}
|
|
421
|
-
else if (kind === 'pr') {
|
|
422
|
-
assertExactObject(value, [
|
|
410
|
+
else if (kind === 'draft-pr') {
|
|
411
|
+
assertExactObject(value, [...identity, 'owner', 'repo', 'head', 'base', 'issueNumber', 'marker'], field);
|
|
423
412
|
for (const key of ['owner', 'repo', 'head', 'base', 'marker'])
|
|
424
413
|
assertNonEmptyString(value[key], `${field}.${key}`);
|
|
425
414
|
assertPositiveInteger(value.issueNumber, `${field}.issueNumber`);
|
|
426
415
|
}
|
|
427
|
-
else if (kind === 'comment') {
|
|
428
|
-
assertExactObject(value, [
|
|
416
|
+
else if (kind === 'claim-comment' || kind === 'handoff-comment') {
|
|
417
|
+
assertExactObject(value, [...identity, 'issueNumber', 'marker', 'bodySha256'], field);
|
|
429
418
|
assertPositiveInteger(value.issueNumber, `${field}.issueNumber`);
|
|
430
419
|
assertNonEmptyString(value.marker, `${field}.marker`);
|
|
431
420
|
assertSha256(value.bodySha256, `${field}.bodySha256`);
|
|
432
421
|
}
|
|
433
422
|
else if (kind === 'review-activation-labels') {
|
|
434
|
-
assertExactObject(value, [
|
|
423
|
+
assertExactObject(value, [...identity, 'issueNumber', 'batchId', 'expected'], field);
|
|
435
424
|
assertPositiveInteger(value.issueNumber, `${field}.issueNumber`);
|
|
436
425
|
assertSha256(value.batchId, `${field}.batchId`);
|
|
437
426
|
validateStringArray(value.expected, `${field}.expected`);
|
|
438
427
|
}
|
|
439
428
|
else if (kind === 'review-update-commit') {
|
|
440
|
-
assertExactObject(value, [
|
|
429
|
+
assertExactObject(value, [...identity, 'batchId', 'parentSha', 'treeSha', 'message', ...(hasOwn(value, 'candidateRef') ? ['candidateRef'] : [])], field);
|
|
441
430
|
assertSha256(value.batchId, `${field}.batchId`);
|
|
442
431
|
assertGitSha(value.parentSha, `${field}.parentSha`);
|
|
443
432
|
assertGitSha(value.treeSha, `${field}.treeSha`);
|
|
444
433
|
assertNonEmptyString(value.message, `${field}.message`);
|
|
434
|
+
if (hasOwn(value, 'candidateRef'))
|
|
435
|
+
assertCandidateRef(value.candidateRef, `${field}.candidateRef`);
|
|
445
436
|
}
|
|
446
437
|
else if (kind === 'review-update-push') {
|
|
447
|
-
assertExactObject(value, [
|
|
438
|
+
assertExactObject(value, [...identity, 'batchId', 'branch', 'priorRemoteSha', 'sha', 'treeSha'], field);
|
|
448
439
|
assertSha256(value.batchId, `${field}.batchId`);
|
|
449
440
|
assertNonEmptyString(value.branch, `${field}.branch`);
|
|
450
441
|
assertGitSha(value.priorRemoteSha, `${field}.priorRemoteSha`);
|
|
@@ -452,7 +443,7 @@ function validateIntent(value, field) {
|
|
|
452
443
|
assertGitSha(value.treeSha, `${field}.treeSha`);
|
|
453
444
|
}
|
|
454
445
|
else if (kind === 'review-summary') {
|
|
455
|
-
assertExactObject(value, [
|
|
446
|
+
assertExactObject(value, [...identity, 'batchId', 'pullRequestNumber', 'pullRequestNodeId', 'marker', 'bodySha256', 'epochHeadSha'], field);
|
|
456
447
|
assertSha256(value.batchId, `${field}.batchId`);
|
|
457
448
|
assertPositiveInteger(value.pullRequestNumber, `${field}.pullRequestNumber`);
|
|
458
449
|
assertNonEmptyString(value.pullRequestNodeId, `${field}.pullRequestNodeId`);
|
|
@@ -461,7 +452,7 @@ function validateIntent(value, field) {
|
|
|
461
452
|
assertGitSha(value.epochHeadSha, `${field}.epochHeadSha`);
|
|
462
453
|
}
|
|
463
454
|
else if (kind === 'review-final-labels') {
|
|
464
|
-
assertExactObject(value, [
|
|
455
|
+
assertExactObject(value, [...identity, 'issueNumber', 'batchId', 'pullRequestNumber', 'pullRequestNodeId', 'epochHeadSha', 'expected'], field);
|
|
465
456
|
assertPositiveInteger(value.issueNumber, `${field}.issueNumber`);
|
|
466
457
|
assertSha256(value.batchId, `${field}.batchId`);
|
|
467
458
|
assertPositiveInteger(value.pullRequestNumber, `${field}.pullRequestNumber`);
|
|
@@ -470,27 +461,61 @@ function validateIntent(value, field) {
|
|
|
470
461
|
validateStringArray(value.expected, `${field}.expected`);
|
|
471
462
|
}
|
|
472
463
|
else if (kind === 'review-blocked-labels') {
|
|
473
|
-
assertExactObject(value, [
|
|
464
|
+
assertExactObject(value, [...identity, 'issueNumber', 'batchId', 'expected', 'blockKind', 'evidenceCode'], field);
|
|
474
465
|
assertPositiveInteger(value.issueNumber, `${field}.issueNumber`);
|
|
475
466
|
assertSha256(value.batchId, `${field}.batchId`);
|
|
476
467
|
validateStringArray(value.expected, `${field}.expected`);
|
|
477
|
-
if (
|
|
468
|
+
if (!['external', 'safety', 'decision-delta', 'out-of-scope', 'authority-boundary'].includes(value.blockKind)) {
|
|
478
469
|
throw new Error(`${field}.blockKind is invalid`);
|
|
470
|
+
}
|
|
479
471
|
assertNonEmptyString(value.evidenceCode, `${field}.evidenceCode`);
|
|
480
472
|
}
|
|
481
473
|
else if (kind === 'blocked-labels') {
|
|
482
|
-
assertExactObject(value, [
|
|
474
|
+
assertExactObject(value, [...identity, 'issueNumber', 'expected', 'blockKind', 'resumable', 'evidenceCode'], field);
|
|
483
475
|
assertPositiveInteger(value.issueNumber, `${field}.issueNumber`);
|
|
484
476
|
validateStringArray(value.expected, `${field}.expected`);
|
|
485
|
-
if (!['external', 'safety', '
|
|
477
|
+
if (!['external', 'safety', 'decision-delta', 'out-of-scope', 'authority-boundary'].includes(value.blockKind))
|
|
486
478
|
throw new Error(`${field}.blockKind is invalid`);
|
|
487
479
|
if (typeof value.resumable !== 'boolean')
|
|
488
480
|
throw new Error(`${field}.resumable is invalid`);
|
|
489
481
|
assertNonEmptyString(value.evidenceCode, `${field}.evidenceCode`);
|
|
490
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
|
+
}
|
|
491
512
|
else {
|
|
492
513
|
throw new Error(`${field}.kind is invalid`);
|
|
493
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`);
|
|
494
519
|
}
|
|
495
520
|
function validateTerminalOutcome(value, field) {
|
|
496
521
|
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
@@ -503,11 +528,13 @@ function validateTerminalOutcome(value, field) {
|
|
|
503
528
|
assertGitSha(value.continuationEpoch, `${field}.continuationEpoch`);
|
|
504
529
|
}
|
|
505
530
|
else if (status === 'blocked') {
|
|
506
|
-
assertExactObject(value, ['status', 'kind', 'resumable', 'evidencePath'], field);
|
|
507
|
-
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))
|
|
508
533
|
throw new Error(`${field}.kind is invalid`);
|
|
509
534
|
if (typeof value.resumable !== 'boolean')
|
|
510
535
|
throw new Error(`${field}.resumable is invalid`);
|
|
536
|
+
if (hasOwn(value, 'blocker'))
|
|
537
|
+
validateBlockerDetail(value.blocker, `${field}.blocker`);
|
|
511
538
|
}
|
|
512
539
|
else if (status === 'transport-failed') {
|
|
513
540
|
assertExactObject(value, ['status', 'resumable', 'evidencePath'], field);
|
|
@@ -526,6 +553,19 @@ function validateTerminalOutcome(value, field) {
|
|
|
526
553
|
}
|
|
527
554
|
assertNonEmptyString(value.evidencePath, `${field}.evidencePath`);
|
|
528
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
|
+
}
|
|
529
569
|
function validateReceipt(value, field) {
|
|
530
570
|
assertExactObject(value, ['proofId', 'bindingSha256', 'summary', 'publishableEvidence', 'localEvidenceId'], field);
|
|
531
571
|
assertNonEmptyString(value.proofId, `${field}.proofId`);
|
|
@@ -543,6 +583,18 @@ function validateReceipt(value, field) {
|
|
|
543
583
|
assertNonEmptyString(evidence.description, `${field}.publishableEvidence.description`);
|
|
544
584
|
}
|
|
545
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
|
+
}
|
|
546
598
|
function validateStringShaRecord(value, field) {
|
|
547
599
|
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
548
600
|
throw new Error(`${field} is invalid`);
|
|
@@ -563,7 +615,40 @@ function validateStringArray(value, field) {
|
|
|
563
615
|
throw new Error(`${field} must be sorted and unique`);
|
|
564
616
|
}
|
|
565
617
|
function emptyRunState() {
|
|
566
|
-
return { schema: 'codex-orchestrator.
|
|
618
|
+
return { schema: 'codex-orchestrator.run-state', generation: 0, runs: [] };
|
|
619
|
+
}
|
|
620
|
+
function parseRawState(bytes) {
|
|
621
|
+
let value;
|
|
622
|
+
try {
|
|
623
|
+
value = JSON.parse(bytes.toString('utf8'));
|
|
624
|
+
}
|
|
625
|
+
catch {
|
|
626
|
+
throw new Error('run state JSON is malformed');
|
|
627
|
+
}
|
|
628
|
+
return validateRunStateFile(value);
|
|
629
|
+
}
|
|
630
|
+
async function readOptionalStateFile(path) {
|
|
631
|
+
let handle;
|
|
632
|
+
try {
|
|
633
|
+
handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
634
|
+
}
|
|
635
|
+
catch (error) {
|
|
636
|
+
if (isErrorCode(error, 'ENOENT'))
|
|
637
|
+
return undefined;
|
|
638
|
+
throw error;
|
|
639
|
+
}
|
|
640
|
+
try {
|
|
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();
|
|
645
|
+
}
|
|
646
|
+
finally {
|
|
647
|
+
await handle.close();
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
function isErrorCode(error, code) {
|
|
651
|
+
return typeof error === 'object' && error !== null && 'code' in error && error.code === code;
|
|
567
652
|
}
|
|
568
653
|
function validateReviewFeedbackRunInvariant(run, field) {
|
|
569
654
|
const feedback = run.reviewFeedback;
|
|
@@ -573,22 +658,18 @@ function validateReviewFeedbackRunInvariant(run, field) {
|
|
|
573
658
|
|| batch.priorPublishedHeadSha !== feedback.previousPublishedHeadSha)) {
|
|
574
659
|
throw new Error(`${field}.reviewFeedback active batch identity binding is invalid`);
|
|
575
660
|
}
|
|
576
|
-
const retainedQuiescentHistory =
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
&& run.lifecycle !== 'review-ready' && !retainedQuiescentHistory) {
|
|
580
|
-
throw new Error(`${field}.reviewFeedback quiescent phase requires review-ready lifecycle`);
|
|
581
|
-
}
|
|
582
|
-
if (['frozen', 'repairing'].includes(feedback.phase) && !['implementing', 'reworking', 'checking', 'proving', 'safe-halt'].includes(run.lifecycle)) {
|
|
583
|
-
throw new Error(`${field}.reviewFeedback active repair phase has invalid 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`);
|
|
584
664
|
}
|
|
585
|
-
if (
|
|
586
|
-
|
|
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`);
|
|
587
668
|
}
|
|
588
|
-
if (feedback.
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
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
|
+
}
|
|
592
673
|
const verified = feedback.verifiedReceipt;
|
|
593
674
|
if (!verified || verified.batchId !== batch?.batchId
|
|
594
675
|
|| verified.checkedChangeSha256 !== run.checkedChangeSha256
|
|
@@ -597,16 +678,10 @@ function validateReviewFeedbackRunInvariant(run, field) {
|
|
|
597
678
|
throw new Error(`${field}.reviewFeedback verification receipt binding is invalid`);
|
|
598
679
|
}
|
|
599
680
|
}
|
|
600
|
-
if (feedback.phase === 'blocked-safety' || feedback.phase === 'blocked-exhausted') {
|
|
601
|
-
if (run.lifecycle !== 'blocked' || run.terminalOutcome?.status !== 'blocked' || run.terminalOutcome.resumable
|
|
602
|
-
|| run.terminalOutcome.kind !== (feedback.phase === 'blocked-safety' ? 'safety' : 'exhausted')) {
|
|
603
|
-
throw new Error(`${field}.reviewFeedback terminal projection mismatch`);
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
681
|
}
|
|
607
682
|
function isLifecycle(value) {
|
|
608
683
|
return typeof value === 'string' && [
|
|
609
|
-
'claimed', '
|
|
684
|
+
'claimed', 'implementing', 'checking', 'proving', 'reviewing', 'publishing', 'safe-halt',
|
|
610
685
|
'review-ready', 'blocked', 'transport-failed', 'cancelled', 'internal-error',
|
|
611
686
|
].includes(value);
|
|
612
687
|
}
|
|
@@ -625,6 +700,10 @@ function assertNonEmptyString(value, field) {
|
|
|
625
700
|
if (typeof value !== 'string' || value.length === 0 || value.length > 16 * 1024)
|
|
626
701
|
throw new Error(`${field} is invalid`);
|
|
627
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
|
+
}
|
|
628
707
|
function assertPositiveInteger(value, field) {
|
|
629
708
|
if (!Number.isSafeInteger(value) || value <= 0)
|
|
630
709
|
throw new Error(`${field} is invalid`);
|
|
@@ -637,6 +716,11 @@ function assertGitSha(value, field) {
|
|
|
637
716
|
if (typeof value !== 'string' || !GIT_SHA_PATTERN.test(value))
|
|
638
717
|
throw new Error(`${field} must be a Git object ID`);
|
|
639
718
|
}
|
|
719
|
+
function assertCandidateRef(value, field) {
|
|
720
|
+
if (typeof value !== 'string' || !/^refs\/codex-orchestrator\/candidates\/[0-9a-f-]{36}\/[0-9a-f]{64}$/u.test(value)) {
|
|
721
|
+
throw new Error(`${field} is invalid`);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
640
724
|
function assertTimestamp(value, field) {
|
|
641
725
|
if (typeof value !== 'string' || Number.isNaN(Date.parse(value)) || new Date(value).toISOString() !== value)
|
|
642
726
|
throw new Error(`${field} is invalid`);
|