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
|
@@ -1,53 +1,33 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
+
import { validateCandidateBinding } from './candidate.js';
|
|
2
3
|
import { canonicalJson } from './containment.js';
|
|
3
|
-
import {
|
|
4
|
+
import { validateCodeReviewDefects } from './code-review-report.js';
|
|
4
5
|
const SHA256 = /^[0-9a-f]{64}$/u;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const changedFiles = sortedUniqueStrings(input.changedFiles, 'direct review changed files');
|
|
9
|
-
assertSha256(input.routeDecisionSha256, 'direct review route decision hash');
|
|
6
|
+
export function directReviewCandidateTargetFingerprint(input) {
|
|
7
|
+
const binding = validateCandidateBinding(input.binding, 'direct review candidate binding');
|
|
8
|
+
assertSha256(input.deliveryAuthoritySha256, 'direct review delivery authority hash');
|
|
10
9
|
assertSha256(input.workflowGenerationHash, 'direct review workflow generation hash');
|
|
11
10
|
if (!Number.isSafeInteger(input.cycle) || input.cycle < 1)
|
|
12
11
|
throw new Error('direct review cycle is invalid');
|
|
13
|
-
return createHash('sha256').update(`codex-orchestrator-direct-review-target-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
return createHash('sha256').update(`codex-orchestrator-direct-review-target-v2\0${canonicalJson({
|
|
13
|
+
bindingId: binding.bindingId,
|
|
14
|
+
expectedHeadSha: binding.expectedHeadSha,
|
|
15
|
+
candidateCommitSha: binding.candidateCommitSha,
|
|
16
|
+
candidateTreeSha: binding.candidateTreeSha,
|
|
17
|
+
canonicalChangedFiles: binding.canonicalChangedFiles,
|
|
18
|
+
sourceWorktreeIdentity: binding.sourceWorktreeIdentity,
|
|
19
|
+
deliveryAuthoritySha256: input.deliveryAuthoritySha256,
|
|
17
20
|
workflowGenerationHash: input.workflowGenerationHash,
|
|
18
21
|
cycle: input.cycle,
|
|
19
22
|
frozenCriteria: input.frozenCriteria,
|
|
20
23
|
})}`).digest('hex');
|
|
21
24
|
}
|
|
22
|
-
export function
|
|
23
|
-
if (state.status !== 'active' || state.invocation || state.stage === null || state.stage.endsWith('-repair')) {
|
|
24
|
-
throw new Error('direct review is not ready to prepare an invocation');
|
|
25
|
-
}
|
|
26
|
-
if (invocation.targetRevision !== state.targetRevision || invocation.targetFingerprint !== state.targetFingerprint) {
|
|
27
|
-
throw new Error('direct review invocation target mismatch');
|
|
28
|
-
}
|
|
29
|
-
return {
|
|
30
|
-
...structuredClone(state),
|
|
31
|
-
invocation: { ...structuredClone(invocation), status: 'prepared', pid: null, processGroupId: null },
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
export function launchDirectReviewInvocation(state, input) {
|
|
35
|
-
if (!state.invocation || state.invocation.status !== 'prepared' || state.invocation.attemptId !== input.attemptId
|
|
36
|
-
|| !Number.isSafeInteger(input.pid) || input.pid < 1 || !Number.isSafeInteger(input.processGroupId) || input.processGroupId < 1) {
|
|
37
|
-
throw new Error('direct review prepared invocation mismatch');
|
|
38
|
-
}
|
|
39
|
-
return {
|
|
40
|
-
...structuredClone(state),
|
|
41
|
-
invocation: { ...structuredClone(state.invocation), status: 'launched', pid: input.pid, processGroupId: input.processGroupId },
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
export function acceptApprovedDirectReview(state, report, artifactSha256) {
|
|
25
|
+
export function acceptApprovedDirectReview(state, report, artifactSha256, candidateTreeSha, mode) {
|
|
45
26
|
assertSha256(artifactSha256, 'direct review accepted report hash');
|
|
46
|
-
|
|
27
|
+
assertGitSha(candidateTreeSha, 'direct review approved candidate tree');
|
|
28
|
+
if (state.status !== 'active' || state.stage === null || report.verdict !== 'approved'
|
|
47
29
|
|| report.targetRevision !== state.targetRevision || report.targetFingerprint !== state.targetFingerprint
|
|
48
|
-
|| report.operation !==
|
|
49
|
-
|| report.reviewerSessionId !== state.invocation.reviewerSessionId
|
|
50
|
-
|| report.closureRequestSha256 !== state.invocation.closureRequestSha256) {
|
|
30
|
+
|| report.operation !== 'code-review' || report.reviewerSessionId !== state.review.reviewerSessionId) {
|
|
51
31
|
throw new Error('accepted direct review report correlation mismatch');
|
|
52
32
|
}
|
|
53
33
|
const defects = mergeDefectLedger(state, report);
|
|
@@ -55,49 +35,74 @@ export function acceptApprovedDirectReview(state, report, artifactSha256) {
|
|
|
55
35
|
&& defect.status !== 'verified' && defect.status !== 'superseded')) {
|
|
56
36
|
throw new Error('approved direct review merge has unresolved defects');
|
|
57
37
|
}
|
|
38
|
+
const impactedCoverage = new Set([
|
|
39
|
+
...state.review.defects
|
|
40
|
+
.filter((defect) => defect.status === 'fixed' && defect.statusTargetRevision === state.targetRevision)
|
|
41
|
+
.flatMap((defect) => defect.affectedTargets),
|
|
42
|
+
...state.repairFindings
|
|
43
|
+
.filter((finding) => finding.status === 'fixed')
|
|
44
|
+
.flatMap((finding) => finding.affectedContracts),
|
|
45
|
+
].filter((target) => target.startsWith('contract:')).map((target) => target.slice('contract:'.length)));
|
|
46
|
+
const mergedCoverage = [...new Set([
|
|
47
|
+
...state.review.coverage.filter((coverage) => !impactedCoverage.has(coverage)),
|
|
48
|
+
...report.coverage,
|
|
49
|
+
])].sort();
|
|
58
50
|
const clearTrack = {
|
|
59
51
|
...structuredClone(state.review),
|
|
60
52
|
disposition: 'clear',
|
|
61
|
-
coverage:
|
|
62
|
-
defects,
|
|
63
|
-
affectedDefectIds: [],
|
|
53
|
+
coverage: mergedCoverage,
|
|
54
|
+
defects: defects.filter((defect) => defect.status !== 'verified' && defect.status !== 'superseded'),
|
|
64
55
|
acceptedReportSha256: artifactSha256,
|
|
65
56
|
};
|
|
66
|
-
const { invocation: _invocation, ...withoutInvocation } = structuredClone(state);
|
|
67
57
|
const outcomes = new Map(report.repairFindingOutcomes.map((outcome) => [outcome.id, outcome.status]));
|
|
68
58
|
return {
|
|
69
|
-
...
|
|
59
|
+
...structuredClone(state),
|
|
70
60
|
status: 'clear',
|
|
61
|
+
previousTarget: mode === 'targeted' ? structuredClone(state.previousTarget) : null,
|
|
71
62
|
review: clearTrack,
|
|
72
63
|
repairFindings: state.repairFindings.map((finding) => {
|
|
73
64
|
const outcome = outcomes.get(finding.id);
|
|
74
65
|
return outcome ? { ...structuredClone(finding), status: outcome } : structuredClone(finding);
|
|
75
|
-
}),
|
|
66
|
+
}).filter((finding) => finding.status !== 'verified'),
|
|
76
67
|
};
|
|
77
68
|
}
|
|
78
|
-
export function acceptNeedsWorkDirectReview(state, report, artifactSha256) {
|
|
69
|
+
export function acceptNeedsWorkDirectReview(state, report, artifactSha256, candidateTreeSha) {
|
|
79
70
|
assertSha256(artifactSha256, 'direct review accepted report hash');
|
|
71
|
+
assertGitSha(candidateTreeSha, 'direct review candidate tree');
|
|
80
72
|
const reopenedFinding = report.repairFindingOutcomes.some((outcome) => outcome.status === 'reopened');
|
|
81
|
-
if (state.status !== 'active' ||
|
|
73
|
+
if (state.status !== 'active' || state.stage === null || report.verdict !== 'needs-work'
|
|
82
74
|
|| report.targetRevision !== state.targetRevision || report.targetFingerprint !== state.targetFingerprint
|
|
83
|
-
|| report.operation !==
|
|
84
|
-
|| report.reviewerSessionId !== state.invocation.reviewerSessionId
|
|
85
|
-
|| report.closureRequestSha256 !== state.invocation.closureRequestSha256
|
|
75
|
+
|| report.operation !== 'code-review' || report.reviewerSessionId !== state.review.reviewerSessionId
|
|
86
76
|
|| (!report.defects.some((defect) => defect.status === 'open' || defect.status === 'reopened') && !reopenedFinding)) {
|
|
87
77
|
throw new Error('needs-work direct review report correlation mismatch');
|
|
88
78
|
}
|
|
89
|
-
const { invocation: _invocation, ...withoutInvocation } = structuredClone(state);
|
|
90
79
|
const outcomes = new Map(report.repairFindingOutcomes.map((outcome) => [outcome.id, outcome.status]));
|
|
80
|
+
const blockedContracts = new Set([
|
|
81
|
+
...report.defects
|
|
82
|
+
.filter((defect) => defect.status === 'open' || defect.status === 'reopened')
|
|
83
|
+
.flatMap((defect) => defect.affectedTargets),
|
|
84
|
+
...state.repairFindings
|
|
85
|
+
.filter((finding) => outcomes.get(finding.id) === 'reopened')
|
|
86
|
+
.flatMap((finding) => finding.affectedContracts),
|
|
87
|
+
].filter((target) => target.startsWith('contract:')).map((target) => target.slice('contract:'.length)));
|
|
88
|
+
const mergedCoverage = [...new Set([
|
|
89
|
+
...state.review.coverage,
|
|
90
|
+
...report.coverage,
|
|
91
|
+
])].filter((coverage) => !blockedContracts.has(coverage)).sort();
|
|
91
92
|
return {
|
|
92
|
-
...
|
|
93
|
+
...structuredClone(state),
|
|
93
94
|
status: 'active',
|
|
94
95
|
stage: 'review-repair',
|
|
96
|
+
previousTarget: {
|
|
97
|
+
targetRevision: state.targetRevision,
|
|
98
|
+
targetFingerprint: state.targetFingerprint,
|
|
99
|
+
candidateTreeSha,
|
|
100
|
+
},
|
|
95
101
|
review: {
|
|
96
102
|
...structuredClone(state.review),
|
|
97
103
|
disposition: 'active',
|
|
98
|
-
coverage:
|
|
104
|
+
coverage: mergedCoverage,
|
|
99
105
|
defects: mergeDefectLedger(state, report),
|
|
100
|
-
affectedDefectIds: [],
|
|
101
106
|
acceptedReportSha256: artifactSha256,
|
|
102
107
|
},
|
|
103
108
|
repairFindings: state.repairFindings.map((finding) => {
|
|
@@ -108,26 +113,20 @@ export function acceptNeedsWorkDirectReview(state, report, artifactSha256) {
|
|
|
108
113
|
}
|
|
109
114
|
function mergeDefectLedger(state, report) {
|
|
110
115
|
const current = state.review.defects;
|
|
111
|
-
if (
|
|
112
|
-
if (current.length !== 0)
|
|
113
|
-
throw new Error('Full review cannot replace an existing defect ledger');
|
|
116
|
+
if (current.length === 0)
|
|
114
117
|
return structuredClone(report.defects);
|
|
115
|
-
}
|
|
116
118
|
const reported = new Map(report.defects.map((defect) => [defect.id, defect]));
|
|
117
|
-
const affected = new Set(state.review.affectedDefectIds);
|
|
118
119
|
const merged = current.map((existing) => {
|
|
119
120
|
const next = reported.get(existing.id);
|
|
120
121
|
if (!next) {
|
|
121
|
-
if (
|
|
122
|
-
|
|
123
|
-
return
|
|
122
|
+
if (existing.status === 'fixed')
|
|
123
|
+
throw new Error('review omitted an impacted defect ID');
|
|
124
|
+
return structuredClone(existing);
|
|
124
125
|
}
|
|
125
126
|
reported.delete(existing.id);
|
|
126
|
-
if (
|
|
127
|
-
return structuredClone(existing);
|
|
128
|
-
if (affected.has(existing.id) && existing.status === 'fixed'
|
|
127
|
+
if (existing.status === 'fixed'
|
|
129
128
|
&& !['verified', 'reopened', 'superseded'].includes(next.status)) {
|
|
130
|
-
throw new Error('
|
|
129
|
+
throw new Error('full review defect transition is invalid');
|
|
131
130
|
}
|
|
132
131
|
return {
|
|
133
132
|
...structuredClone(next),
|
|
@@ -141,19 +140,30 @@ function mergeDefectLedger(state, report) {
|
|
|
141
140
|
for (const added of reported.values()) {
|
|
142
141
|
if (added.status !== 'open' || added.introducedTargetRevision !== state.targetRevision
|
|
143
142
|
|| added.statusTargetRevision !== state.targetRevision)
|
|
144
|
-
throw new Error('
|
|
143
|
+
throw new Error('full review introduced defect is invalid');
|
|
145
144
|
merged.push(structuredClone(added));
|
|
146
145
|
}
|
|
147
146
|
return merged.sort((left, right) => left.id.localeCompare(right.id));
|
|
148
147
|
}
|
|
149
|
-
export function beginDirectReviewRepair(state, findings) {
|
|
150
|
-
if (
|
|
148
|
+
export function beginDirectReviewRepair(state, findings, candidateTreeSha) {
|
|
149
|
+
if (findings.length === 0 || (state.status !== 'clear'
|
|
150
|
+
&& !(state.status === 'active' && state.stage === 'review-repair'))) {
|
|
151
151
|
throw new Error('direct review is not clear for repair');
|
|
152
152
|
}
|
|
153
|
+
if (state.status === 'active') {
|
|
154
|
+
return { ...structuredClone(state), repairFindings: mergeRepairFindings(state.repairFindings, findings) };
|
|
155
|
+
}
|
|
156
|
+
if (state.review.disposition !== 'clear')
|
|
157
|
+
throw new Error('direct review is not clear for repair');
|
|
153
158
|
return {
|
|
154
159
|
...structuredClone(state),
|
|
155
160
|
status: 'active',
|
|
156
161
|
stage: 'review-repair',
|
|
162
|
+
previousTarget: candidateTreeSha ? {
|
|
163
|
+
targetRevision: state.targetRevision,
|
|
164
|
+
targetFingerprint: state.targetFingerprint,
|
|
165
|
+
candidateTreeSha,
|
|
166
|
+
} : structuredClone(state.previousTarget),
|
|
157
167
|
review: { ...structuredClone(state.review), disposition: 'active' },
|
|
158
168
|
repairFindings: mergeRepairFindings(state.repairFindings, findings),
|
|
159
169
|
};
|
|
@@ -174,10 +184,11 @@ function mergeRepairFindings(current, incoming) {
|
|
|
174
184
|
}
|
|
175
185
|
return [...merged.values()].sort((left, right) => left.id.localeCompare(right.id));
|
|
176
186
|
}
|
|
177
|
-
export function
|
|
178
|
-
assertSha256(targetFingerprint, 'direct review
|
|
179
|
-
|
|
180
|
-
|
|
187
|
+
export function prepareDirectReview(state, targetFingerprint, reviewerSessionId) {
|
|
188
|
+
assertSha256(targetFingerprint, 'direct review target fingerprint');
|
|
189
|
+
assertText(reviewerSessionId, 'direct review reviewer session ID');
|
|
190
|
+
if (state.status !== 'active' || state.stage !== 'review-repair') {
|
|
191
|
+
throw new Error('direct review is not ready for another review');
|
|
181
192
|
}
|
|
182
193
|
const targetRevision = state.targetRevision + 1;
|
|
183
194
|
const defects = state.review.defects.map((defect) => defect.status === 'open' || defect.status === 'reopened'
|
|
@@ -186,53 +197,32 @@ export function prepareDirectReviewClosure(state, targetFingerprint) {
|
|
|
186
197
|
const repairFindings = state.repairFindings.map((finding) => finding.status === 'open' || finding.status === 'reopened'
|
|
187
198
|
? { ...structuredClone(finding), status: 'fixed' }
|
|
188
199
|
: structuredClone(finding));
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
...repairFindings.filter((finding) => finding.status === 'fixed').map((finding) => finding.id),
|
|
192
|
-
].sort();
|
|
193
|
-
if (affectedIds.length === 0)
|
|
194
|
-
throw new Error('direct review Closure has no fixed targets');
|
|
200
|
+
if (![...defects, ...repairFindings].some((item) => item.status === 'fixed'))
|
|
201
|
+
throw new Error('direct review has no fixed targets');
|
|
195
202
|
const nextState = {
|
|
196
203
|
...structuredClone(state),
|
|
197
|
-
stage: 'review
|
|
204
|
+
stage: 'review',
|
|
198
205
|
targetRevision,
|
|
199
206
|
targetFingerprint,
|
|
207
|
+
previousTarget: structuredClone(state.previousTarget),
|
|
200
208
|
review: {
|
|
201
209
|
...structuredClone(state.review),
|
|
202
|
-
|
|
210
|
+
reviewerSessionId,
|
|
203
211
|
reportRepairs: 0,
|
|
204
212
|
transportRetries: 0,
|
|
205
213
|
defects,
|
|
206
|
-
|
|
214
|
+
acceptedReportSha256: null,
|
|
207
215
|
},
|
|
208
216
|
repairFindings,
|
|
209
217
|
};
|
|
210
|
-
|
|
211
|
-
return {
|
|
212
|
-
state: nextState,
|
|
213
|
-
closureRequestSha256,
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
export function directReviewClosureRequestSha256(state) {
|
|
217
|
-
if (state.status !== 'active' || state.stage !== 'review-closure' || state.review.mode !== 'closure') {
|
|
218
|
-
throw new Error('direct review state is not a code-review Closure');
|
|
219
|
-
}
|
|
220
|
-
return hashClosureRequest({
|
|
221
|
-
operation: 'code-review',
|
|
222
|
-
targetRevision: state.targetRevision,
|
|
223
|
-
targetFingerprint: state.targetFingerprint,
|
|
224
|
-
affectedDefectIds: state.review.affectedDefectIds,
|
|
225
|
-
fixedRepairFindings: state.repairFindings.filter((finding) => finding.status === 'fixed')
|
|
226
|
-
.map((finding) => ({ id: finding.id, affectedContracts: finding.affectedContracts })),
|
|
227
|
-
reviewFocus: ['acceptance-criteria', 'correctness', 'test-quality'],
|
|
228
|
-
});
|
|
218
|
+
return nextState;
|
|
229
219
|
}
|
|
230
220
|
export function projectTerminalDirectReview(state, terminalOutcome, terminalCode) {
|
|
231
221
|
if (state.status === 'terminal')
|
|
232
222
|
throw new Error('direct review terminal projection is immutable');
|
|
233
223
|
if (terminalCode !== undefined)
|
|
234
224
|
assertText(terminalCode, 'direct review terminal code');
|
|
235
|
-
const {
|
|
225
|
+
const { terminalCode: _priorCode, terminalOutcome: _priorOutcome, ...preserved } = structuredClone(state);
|
|
236
226
|
return {
|
|
237
227
|
...preserved,
|
|
238
228
|
status: 'terminal',
|
|
@@ -240,46 +230,27 @@ export function projectTerminalDirectReview(state, terminalOutcome, terminalCode
|
|
|
240
230
|
terminalOutcome: structuredClone(terminalOutcome),
|
|
241
231
|
};
|
|
242
232
|
}
|
|
243
|
-
export function canRecoverTerminalDirectReviewReport(state, options = {}) {
|
|
244
|
-
return state.status === 'terminal' && state.terminalOutcome?.status === 'internal-error'
|
|
245
|
-
&& !state.invocation && state.stage !== null && state.stage !== 'review-repair'
|
|
246
|
-
&& state.review.disposition === 'active' && state.review.reportRepairs > 0
|
|
247
|
-
&& state.review.reportRepairs < MAX_DIRECT_REVIEW_REPORT_REPAIRS
|
|
248
|
-
&& (state.terminalCode === 'direct-review-report-malformed'
|
|
249
|
-
|| (state.terminalCode === undefined && options.allowLegacyMalformed === true));
|
|
250
|
-
}
|
|
251
|
-
export function recoverTerminalDirectReviewReport(state, options = {}) {
|
|
252
|
-
if (!canRecoverTerminalDirectReviewReport(state, options)) {
|
|
253
|
-
throw new Error('direct review terminal report is not recoverable');
|
|
254
|
-
}
|
|
255
|
-
const { terminalCode: _terminalCode, terminalOutcome: _terminalOutcome, ...withoutTerminal } = structuredClone(state);
|
|
256
|
-
return {
|
|
257
|
-
...withoutTerminal,
|
|
258
|
-
status: 'active',
|
|
259
|
-
review: { ...withoutTerminal.review, transportRetries: 0 },
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
233
|
export function createInitialDirectReview(input) {
|
|
263
234
|
assertSha256(input.targetFingerprint, 'direct review target fingerprint');
|
|
264
235
|
assertText(input.codeReviewerSessionId, 'code reviewer session ID');
|
|
265
236
|
return {
|
|
266
237
|
version: 1,
|
|
267
238
|
status: 'active',
|
|
268
|
-
stage: 'review
|
|
239
|
+
stage: 'review',
|
|
269
240
|
targetRevision: 1,
|
|
270
241
|
targetFingerprint: input.targetFingerprint,
|
|
271
|
-
|
|
242
|
+
previousTarget: null,
|
|
243
|
+
review: activeTrack(input.codeReviewerSessionId),
|
|
272
244
|
repairFindings: [],
|
|
273
245
|
};
|
|
274
246
|
}
|
|
275
247
|
export function validateDirectReview(value, context) {
|
|
276
248
|
const optional = [
|
|
277
|
-
...(hasOwn(value, 'invocation') ? ['invocation'] : []),
|
|
278
249
|
...(hasOwn(value, 'terminalCode') ? ['terminalCode'] : []),
|
|
279
250
|
...(hasOwn(value, 'terminalOutcome') ? ['terminalOutcome'] : []),
|
|
280
251
|
];
|
|
281
252
|
assertExactObject(value, [
|
|
282
|
-
'version', 'status', 'stage', 'targetRevision', 'targetFingerprint', 'review', 'repairFindings', ...optional,
|
|
253
|
+
'version', 'status', 'stage', 'targetRevision', 'targetFingerprint', 'previousTarget', 'review', 'repairFindings', ...optional,
|
|
283
254
|
], 'direct review');
|
|
284
255
|
if (value.version !== 1 || !['active', 'clear', 'terminal'].includes(value.status)) {
|
|
285
256
|
throw new Error('direct review version/status is invalid');
|
|
@@ -289,8 +260,8 @@ export function validateDirectReview(value, context) {
|
|
|
289
260
|
if (!Number.isSafeInteger(value.targetRevision) || value.targetRevision < 0)
|
|
290
261
|
throw new Error('direct review target revision is invalid');
|
|
291
262
|
assertSha256(value.targetFingerprint, 'direct review target fingerprint');
|
|
263
|
+
const previousTarget = validatePreviousTarget(value.previousTarget, value.targetRevision);
|
|
292
264
|
const review = validateTrack(value.review, 'review', value.targetRevision);
|
|
293
|
-
const invocation = hasOwn(value, 'invocation') ? validateInvocation(value.invocation) : undefined;
|
|
294
265
|
const repairFindings = validateRepairFindings(value.repairFindings, value.targetRevision);
|
|
295
266
|
const terminalCode = hasOwn(value, 'terminalCode') ? value.terminalCode : undefined;
|
|
296
267
|
if (terminalCode !== undefined)
|
|
@@ -304,7 +275,6 @@ export function validateDirectReview(value, context) {
|
|
|
304
275
|
stage: value.stage,
|
|
305
276
|
targetRevision: value.targetRevision,
|
|
306
277
|
review,
|
|
307
|
-
invocation,
|
|
308
278
|
repairFindings,
|
|
309
279
|
terminalCode: terminalCode,
|
|
310
280
|
terminalOutcome,
|
|
@@ -315,19 +285,43 @@ export function validateDirectReview(value, context) {
|
|
|
315
285
|
stage: value.stage,
|
|
316
286
|
targetRevision: value.targetRevision,
|
|
317
287
|
targetFingerprint: value.targetFingerprint,
|
|
288
|
+
previousTarget,
|
|
318
289
|
review,
|
|
319
|
-
...(invocation ? { invocation } : {}),
|
|
320
290
|
repairFindings,
|
|
321
291
|
...(terminalCode ? { terminalCode: terminalCode } : {}),
|
|
322
292
|
...(terminalOutcome ? { terminalOutcome } : {}),
|
|
323
293
|
};
|
|
324
294
|
}
|
|
295
|
+
function validatePreviousTarget(value, targetRevision) {
|
|
296
|
+
if (value === null)
|
|
297
|
+
return null;
|
|
298
|
+
assertExactObject(value, ['targetRevision', 'targetFingerprint', 'candidateTreeSha'], 'direct review previous target');
|
|
299
|
+
if (!Number.isSafeInteger(value.targetRevision)
|
|
300
|
+
|| (value.targetRevision !== targetRevision && value.targetRevision !== targetRevision - 1)) {
|
|
301
|
+
throw new Error('direct review previous target revision is invalid');
|
|
302
|
+
}
|
|
303
|
+
assertSha256(value.targetFingerprint, 'direct review previous target fingerprint');
|
|
304
|
+
assertGitSha(value.candidateTreeSha, 'direct review previous target tree');
|
|
305
|
+
return {
|
|
306
|
+
targetRevision: value.targetRevision,
|
|
307
|
+
targetFingerprint: value.targetFingerprint,
|
|
308
|
+
candidateTreeSha: value.candidateTreeSha,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
325
311
|
function validateComposite(value, context) {
|
|
326
312
|
if (value.status === 'terminal') {
|
|
327
|
-
if (value.stage === null || value.targetRevision < 1 ||
|
|
313
|
+
if (value.stage === null || value.targetRevision < 1 || !value.terminalOutcome
|
|
328
314
|
|| !['blocked', 'transport-failed', 'cancelled', 'internal-error'].includes(context.lifecycle)) {
|
|
329
315
|
throw new Error('terminal direct review composite is invalid');
|
|
330
316
|
}
|
|
317
|
+
if (value.terminalOutcome.status === 'internal-error') {
|
|
318
|
+
if (value.terminalCode === undefined || value.terminalCode !== context.terminalCode) {
|
|
319
|
+
throw new Error('terminal direct review code does not match run terminal outcome');
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
else if (value.terminalCode !== undefined || context.terminalCode !== undefined) {
|
|
323
|
+
throw new Error('non-internal terminal direct review has a terminal code');
|
|
324
|
+
}
|
|
331
325
|
return;
|
|
332
326
|
}
|
|
333
327
|
if (value.terminalOutcome || value.terminalCode)
|
|
@@ -335,36 +329,26 @@ function validateComposite(value, context) {
|
|
|
335
329
|
if (value.stage === null || value.targetRevision < 1)
|
|
336
330
|
throw new Error('active direct review requires a stage and revision');
|
|
337
331
|
if (value.status === 'active') {
|
|
338
|
-
|
|
332
|
+
const allowed = value.stage === 'review'
|
|
333
|
+
? ['checking', 'proving', 'reviewing', 'safe-halt']
|
|
334
|
+
: ['implementing', 'checking', 'proving', 'safe-halt'];
|
|
335
|
+
if (!allowed.includes(context.lifecycle))
|
|
339
336
|
throw new Error('active direct review lifecycle is invalid');
|
|
340
337
|
if (value.review.disposition !== 'active')
|
|
341
338
|
throw new Error('active direct review stage has no active track');
|
|
342
|
-
validateStageFields(value.stage, value.review, value.
|
|
339
|
+
validateStageFields(value.stage, value.review, value.repairFindings);
|
|
343
340
|
}
|
|
344
341
|
else {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
if ((!['checking', 'proving', 'publishing', 'review-ready'].includes(context.lifecycle) && !proofSafeHalt)
|
|
348
|
-
|| value.invocation || value.review.disposition !== 'clear') {
|
|
342
|
+
if (!['publishing', 'safe-halt', 'review-ready'].includes(context.lifecycle)
|
|
343
|
+
|| value.review.disposition !== 'clear') {
|
|
349
344
|
throw new Error('clear direct review composite is invalid');
|
|
350
345
|
}
|
|
351
346
|
}
|
|
352
|
-
if (value.status === 'active' && context.lifecycle === 'safe-halt') {
|
|
353
|
-
const reviewProcess = context.process?.purpose === 'code-review'
|
|
354
|
-
&& context.process.resumeLifecycle === 'implementing'
|
|
355
|
-
&& context.process.resumeReviewStage === value.stage;
|
|
356
|
-
const feedbackImplementationProcess = value.stage === 'review-repair'
|
|
357
|
-
&& context.process?.purpose === 'implementation'
|
|
358
|
-
&& context.process.resumeLifecycle === 'implementing'
|
|
359
|
-
&& context.process.resumeReviewStage === null;
|
|
360
|
-
if (!reviewProcess && !feedbackImplementationProcess)
|
|
361
|
-
throw new Error('safe-halt review stage/process mismatch');
|
|
362
|
-
}
|
|
363
347
|
}
|
|
364
348
|
function validateTerminalProjection(value, lifecycle) {
|
|
365
349
|
if (lifecycle === 'blocked') {
|
|
366
350
|
assertExactObject(value, ['status', 'kind'], 'direct review terminal outcome');
|
|
367
|
-
if (value.status !== 'blocked' || !['external', 'safety', '
|
|
351
|
+
if (value.status !== 'blocked' || !['external', 'safety', 'decision-delta', 'out-of-scope', 'authority-boundary'].includes(value.kind)) {
|
|
368
352
|
throw new Error('direct review blocked projection is invalid');
|
|
369
353
|
}
|
|
370
354
|
}
|
|
@@ -376,97 +360,53 @@ function validateTerminalProjection(value, lifecycle) {
|
|
|
376
360
|
}
|
|
377
361
|
return structuredClone(value);
|
|
378
362
|
}
|
|
379
|
-
function validateStageFields(stage, track,
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
throw new Error('Full review stage fields are invalid');
|
|
384
|
-
}
|
|
363
|
+
function validateStageFields(stage, track, findings) {
|
|
364
|
+
if (stage === 'review') {
|
|
365
|
+
if (track.acceptedReportSha256 !== null)
|
|
366
|
+
throw new Error('review stage fields are invalid');
|
|
385
367
|
}
|
|
386
|
-
else
|
|
387
|
-
if (
|
|
368
|
+
else {
|
|
369
|
+
if (track.acceptedReportSha256 === null || (track.defects.every((defect) => defect.status !== 'open' && defect.status !== 'reopened')
|
|
388
370
|
&& findings.every((finding) => finding.status !== 'open' && finding.status !== 'reopened'))) {
|
|
389
371
|
throw new Error('review repair stage fields are invalid');
|
|
390
372
|
}
|
|
391
373
|
}
|
|
392
|
-
else if (track.mode !== 'closure' || track.acceptedReportSha256 === null || track.affectedDefectIds.length === 0
|
|
393
|
-
|| !track.affectedDefectIds.every((id) => track.defects.some((defect) => defect.id === id && defect.status === 'fixed')
|
|
394
|
-
|| findings.some((finding) => finding.id === id && finding.status === 'fixed'))) {
|
|
395
|
-
throw new Error('Closure review stage fields are invalid');
|
|
396
|
-
}
|
|
397
|
-
if (invocation) {
|
|
398
|
-
if (invocation.operation !== 'code-review' || invocation.mode !== track.mode || invocation.reviewerSessionId !== track.reviewerSessionId) {
|
|
399
|
-
throw new Error('review invocation does not match active stage');
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
374
|
}
|
|
403
375
|
function validateTrack(value, field, targetRevision) {
|
|
404
376
|
assertExactObject(value, [
|
|
405
|
-
'version', 'disposition', 'profile', 'reviewerSessionId', '
|
|
406
|
-
'coverage', 'defects', '
|
|
377
|
+
'version', 'disposition', 'profile', 'reviewerSessionId', 'reportRepairs', 'transportRetries',
|
|
378
|
+
'coverage', 'defects', 'acceptedReportSha256',
|
|
407
379
|
], `direct review ${field} track`);
|
|
408
380
|
if (value.version !== 1 || !['active', 'clear'].includes(value.disposition)
|
|
409
381
|
|| !['simple', 'medium', 'high'].includes(value.profile)
|
|
410
|
-
|| !
|
|
382
|
+
|| !isNonNegativeInteger(value.reportRepairs) || !isNonNegativeInteger(value.transportRetries))
|
|
411
383
|
throw new Error(`direct review ${field} track is invalid`);
|
|
412
384
|
if (value.reviewerSessionId !== null)
|
|
413
385
|
assertText(value.reviewerSessionId, `${field} reviewer session ID`);
|
|
414
|
-
if (value.mode !== null && value.mode !== 'full' && value.mode !== 'closure')
|
|
415
|
-
throw new Error(`${field} review mode is invalid`);
|
|
416
386
|
const coverage = sortedUniqueStrings(value.coverage, `${field} coverage`);
|
|
417
|
-
const affectedDefectIds = sortedUniqueStrings(value.affectedDefectIds, `${field} affected defect IDs`);
|
|
418
387
|
const defects = validateCodeReviewDefects(value.defects, targetRevision);
|
|
419
388
|
if (value.acceptedReportSha256 !== null)
|
|
420
389
|
assertSha256(value.acceptedReportSha256, `${field} accepted report hash`);
|
|
421
|
-
if (value.disposition === 'active' &&
|
|
390
|
+
if (value.disposition === 'active' && value.reviewerSessionId === null)
|
|
422
391
|
throw new Error(`${field} active track lacks identity`);
|
|
423
|
-
if (value.disposition === 'clear' && (value.reviewerSessionId === null || value.
|
|
424
|
-
||
|
|
425
|
-
|
|
392
|
+
if (value.disposition === 'clear' && (value.reviewerSessionId === null || value.acceptedReportSha256 === null
|
|
393
|
+
|| defects.some((defect) => (defect.class === 'blocker' || defect.class === 'execution-risk')
|
|
394
|
+
&& defect.status !== 'verified' && defect.status !== 'superseded')))
|
|
426
395
|
throw new Error(`${field} clear track is invalid`);
|
|
427
396
|
return {
|
|
428
397
|
version: 1,
|
|
429
398
|
disposition: value.disposition,
|
|
430
399
|
profile: value.profile,
|
|
431
400
|
reviewerSessionId: value.reviewerSessionId,
|
|
432
|
-
mode: value.mode,
|
|
433
401
|
reportRepairs: value.reportRepairs,
|
|
434
402
|
transportRetries: value.transportRetries,
|
|
435
403
|
coverage,
|
|
436
404
|
defects,
|
|
437
|
-
affectedDefectIds,
|
|
438
405
|
acceptedReportSha256: value.acceptedReportSha256,
|
|
439
406
|
};
|
|
440
407
|
}
|
|
441
|
-
function validateInvocation(value) {
|
|
442
|
-
assertExactObject(value, [
|
|
443
|
-
'attemptId', 'operation', 'mode', 'reviewerSessionId', 'targetRevision', 'targetFingerprint',
|
|
444
|
-
'closureRequestSha256', 'status', 'pid', 'processGroupId',
|
|
445
|
-
], 'review invocation');
|
|
446
|
-
for (const field of ['attemptId', 'reviewerSessionId'])
|
|
447
|
-
assertText(value[field], `review invocation.${field}`);
|
|
448
|
-
if (value.operation !== 'code-review')
|
|
449
|
-
throw new Error('review invocation operation is invalid');
|
|
450
|
-
if (value.mode !== 'full' && value.mode !== 'closure')
|
|
451
|
-
throw new Error('review invocation mode is invalid');
|
|
452
|
-
if (!Number.isSafeInteger(value.targetRevision) || value.targetRevision < 1)
|
|
453
|
-
throw new Error('review invocation target revision is invalid');
|
|
454
|
-
assertSha256(value.targetFingerprint, 'review invocation target fingerprint');
|
|
455
|
-
if (value.closureRequestSha256 !== null)
|
|
456
|
-
assertSha256(value.closureRequestSha256, 'review invocation Closure hash');
|
|
457
|
-
if (!['prepared', 'launched', 'abandoned'].includes(value.status))
|
|
458
|
-
throw new Error('review invocation status is invalid');
|
|
459
|
-
const launched = value.status === 'launched';
|
|
460
|
-
if (launched !== (Number.isSafeInteger(value.pid) && value.pid > 0
|
|
461
|
-
&& Number.isSafeInteger(value.processGroupId) && value.processGroupId > 0)) {
|
|
462
|
-
throw new Error('review invocation process identity is invalid');
|
|
463
|
-
}
|
|
464
|
-
if (!launched && (value.pid !== null || value.processGroupId !== null))
|
|
465
|
-
throw new Error('non-launched invocation has process identity');
|
|
466
|
-
return structuredClone(value);
|
|
467
|
-
}
|
|
468
408
|
function validateRepairFindings(value, targetRevision) {
|
|
469
|
-
if (!Array.isArray(value)
|
|
409
|
+
if (!Array.isArray(value))
|
|
470
410
|
throw new Error('direct repair findings are invalid');
|
|
471
411
|
const findings = value.map((finding, index) => {
|
|
472
412
|
assertExactObject(finding, ['id', 'provenance', 'sourceId', 'targetRevision', 'summary', 'affectedContracts', 'status'], `repair finding[${index}]`);
|
|
@@ -485,21 +425,20 @@ function validateRepairFindings(value, targetRevision) {
|
|
|
485
425
|
assertUnique(findings.map((finding) => finding.id), 'repair finding IDs');
|
|
486
426
|
return findings;
|
|
487
427
|
}
|
|
488
|
-
function activeTrack(reviewerSessionId
|
|
428
|
+
function activeTrack(reviewerSessionId) {
|
|
489
429
|
return {
|
|
490
|
-
version: 1, disposition: 'active', profile: 'high', reviewerSessionId,
|
|
491
|
-
reportRepairs: 0, transportRetries: 0, coverage: [], defects: [],
|
|
430
|
+
version: 1, disposition: 'active', profile: 'high', reviewerSessionId,
|
|
431
|
+
reportRepairs: 0, transportRetries: 0, coverage: [], defects: [], acceptedReportSha256: null,
|
|
492
432
|
};
|
|
493
433
|
}
|
|
494
434
|
function isStage(value) {
|
|
495
|
-
return
|
|
435
|
+
return value === 'review' || value === 'review-repair';
|
|
496
436
|
}
|
|
497
|
-
function
|
|
498
|
-
|
|
499
|
-
return Number.isSafeInteger(value) && value >= 0 && value <= MAX_DIRECT_REVIEW_REPORT_REPAIRS;
|
|
437
|
+
function isNonNegativeInteger(value) {
|
|
438
|
+
return Number.isSafeInteger(value) && value >= 0;
|
|
500
439
|
}
|
|
501
440
|
function sortedUniqueStrings(value, field) {
|
|
502
|
-
if (!Array.isArray(value)
|
|
441
|
+
if (!Array.isArray(value))
|
|
503
442
|
throw new Error(`${field} is invalid`);
|
|
504
443
|
for (const item of value)
|
|
505
444
|
assertText(item, `${field} entry`);
|
|
@@ -515,6 +454,10 @@ function assertSha256(value, field) {
|
|
|
515
454
|
if (typeof value !== 'string' || !SHA256.test(value))
|
|
516
455
|
throw new Error(`${field} is invalid`);
|
|
517
456
|
}
|
|
457
|
+
function assertGitSha(value, field) {
|
|
458
|
+
if (typeof value !== 'string' || !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/u.test(value))
|
|
459
|
+
throw new Error(`${field} is invalid`);
|
|
460
|
+
}
|
|
518
461
|
function assertText(value, field) {
|
|
519
462
|
if (typeof value !== 'string' || value.length === 0 || value.length > 16 * 1024)
|
|
520
463
|
throw new Error(`${field} is invalid`);
|