codex-orchestrator 2.0.11 → 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 +15 -0
- package/README.md +25 -51
- package/dist/src/index.d.ts +2 -8
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -4
- package/dist/src/index.js.map +1 -1
- package/dist/src/v2/acceptance-proof.d.ts +46 -31
- package/dist/src/v2/acceptance-proof.d.ts.map +1 -1
- package/dist/src/v2/acceptance-proof.js +157 -195
- package/dist/src/v2/acceptance-proof.js.map +1 -1
- package/dist/src/v2/active-attempt.d.ts +94 -0
- package/dist/src/v2/active-attempt.d.ts.map +1 -0
- package/dist/src/v2/active-attempt.js +200 -0
- package/dist/src/v2/active-attempt.js.map +1 -0
- package/dist/src/v2/adapters/command.d.ts +6 -0
- package/dist/src/v2/adapters/command.d.ts.map +1 -1
- package/dist/src/v2/adapters/command.js +43 -2
- package/dist/src/v2/adapters/command.js.map +1 -1
- package/dist/src/v2/candidate.d.ts +15 -31
- package/dist/src/v2/candidate.d.ts.map +1 -1
- package/dist/src/v2/candidate.js +7 -29
- package/dist/src/v2/candidate.js.map +1 -1
- package/dist/src/v2/checked-change.d.ts +3 -2
- package/dist/src/v2/checked-change.d.ts.map +1 -1
- package/dist/src/v2/checked-change.js +4 -3
- package/dist/src/v2/checked-change.js.map +1 -1
- package/dist/src/v2/cli-contract.d.ts +1 -1
- package/dist/src/v2/cli-contract.d.ts.map +1 -1
- package/dist/src/v2/cli-contract.js +4 -6
- package/dist/src/v2/cli-contract.js.map +1 -1
- package/dist/src/v2/cli.d.ts +8 -0
- package/dist/src/v2/cli.d.ts.map +1 -1
- package/dist/src/v2/cli.js +13 -0
- package/dist/src/v2/cli.js.map +1 -1
- package/dist/src/v2/code-review-report.d.ts +10 -18
- package/dist/src/v2/code-review-report.d.ts.map +1 -1
- package/dist/src/v2/code-review-report.js +63 -60
- package/dist/src/v2/code-review-report.js.map +1 -1
- package/dist/src/v2/codex-process.d.ts +6 -2
- package/dist/src/v2/codex-process.d.ts.map +1 -1
- package/dist/src/v2/codex-process.js +25 -9
- package/dist/src/v2/codex-process.js.map +1 -1
- package/dist/src/v2/config.d.ts +0 -2
- package/dist/src/v2/config.d.ts.map +1 -1
- package/dist/src/v2/config.js +3 -6
- package/dist/src/v2/config.js.map +1 -1
- package/dist/src/v2/contained-report-operation.d.ts +41 -196
- package/dist/src/v2/contained-report-operation.d.ts.map +1 -1
- package/dist/src/v2/contained-report-operation.js +139 -466
- package/dist/src/v2/contained-report-operation.js.map +1 -1
- package/dist/src/v2/containment.d.ts +1 -0
- package/dist/src/v2/containment.d.ts.map +1 -1
- package/dist/src/v2/containment.js +12 -2
- package/dist/src/v2/containment.js.map +1 -1
- package/dist/src/v2/delivery-authority.d.ts +26 -0
- package/dist/src/v2/delivery-authority.d.ts.map +1 -0
- package/dist/src/v2/delivery-authority.js +44 -0
- package/dist/src/v2/delivery-authority.js.map +1 -0
- package/dist/src/v2/direct-delivery.d.ts +16 -36
- package/dist/src/v2/direct-delivery.d.ts.map +1 -1
- package/dist/src/v2/direct-delivery.js +135 -122
- package/dist/src/v2/direct-delivery.js.map +1 -1
- package/dist/src/v2/immutable-workflow-publisher.d.ts.map +1 -1
- package/dist/src/v2/immutable-workflow-publisher.js +3 -1
- package/dist/src/v2/immutable-workflow-publisher.js.map +1 -1
- package/dist/src/v2/implementation-report.d.ts +3 -1
- package/dist/src/v2/implementation-report.d.ts.map +1 -1
- package/dist/src/v2/implementation-report.js +17 -4
- package/dist/src/v2/implementation-report.js.map +1 -1
- package/dist/src/v2/implementation-reviewer.d.ts +41 -12
- package/dist/src/v2/implementation-reviewer.d.ts.map +1 -1
- package/dist/src/v2/implementation-reviewer.js +114 -42
- package/dist/src/v2/implementation-reviewer.js.map +1 -1
- package/dist/src/v2/pending-effect-settlement.d.ts +44 -0
- package/dist/src/v2/pending-effect-settlement.d.ts.map +1 -0
- package/dist/src/v2/pending-effect-settlement.js +69 -0
- package/dist/src/v2/pending-effect-settlement.js.map +1 -0
- package/dist/src/v2/process-identity.d.ts +45 -0
- package/dist/src/v2/process-identity.d.ts.map +1 -0
- package/dist/src/v2/process-identity.js +118 -0
- package/dist/src/v2/process-identity.js.map +1 -0
- package/dist/src/v2/proof-report.d.ts +2 -1
- package/dist/src/v2/proof-report.d.ts.map +1 -1
- package/dist/src/v2/proof-report.js +10 -4
- package/dist/src/v2/proof-report.js.map +1 -1
- package/dist/src/v2/review-feedback-coordinator.d.ts +1 -1
- package/dist/src/v2/review-feedback-coordinator.d.ts.map +1 -1
- package/dist/src/v2/review-feedback-coordinator.js +1 -1
- package/dist/src/v2/review-feedback-coordinator.js.map +1 -1
- package/dist/src/v2/review-feedback.d.ts +14 -20
- package/dist/src/v2/review-feedback.d.ts.map +1 -1
- package/dist/src/v2/review-feedback.js +45 -87
- package/dist/src/v2/review-feedback.js.map +1 -1
- package/dist/src/v2/run-issue.d.ts +129 -88
- package/dist/src/v2/run-issue.d.ts.map +1 -1
- package/dist/src/v2/run-issue.js +1965 -2381
- package/dist/src/v2/run-issue.js.map +1 -1
- package/dist/src/v2/run-state-projections.d.ts +84 -0
- package/dist/src/v2/run-state-projections.d.ts.map +1 -0
- package/dist/src/v2/run-state-projections.js +142 -0
- package/dist/src/v2/run-state-projections.js.map +1 -0
- package/dist/src/v2/run-store.d.ts +99 -81
- package/dist/src/v2/run-store.d.ts.map +1 -1
- package/dist/src/v2/run-store.js +245 -542
- package/dist/src/v2/run-store.js.map +1 -1
- package/dist/src/v2/runtime-assets.d.ts +3 -0
- package/dist/src/v2/runtime-assets.d.ts.map +1 -1
- package/dist/src/v2/runtime-assets.js +104 -0
- package/dist/src/v2/runtime-assets.js.map +1 -1
- package/dist/src/v2/runtime.d.ts +56 -44
- package/dist/src/v2/runtime.d.ts.map +1 -1
- package/dist/src/v2/runtime.js +383 -503
- package/dist/src/v2/runtime.js.map +1 -1
- package/dist/src/v2/setup.js +0 -2
- package/dist/src/v2/setup.js.map +1 -1
- package/dist/src/v2/validation-progression.d.ts +70 -0
- package/dist/src/v2/validation-progression.d.ts.map +1 -0
- package/dist/src/v2/validation-progression.js +247 -0
- package/dist/src/v2/validation-progression.js.map +1 -0
- package/dist/src/v2/workflow-assets.d.ts +9 -3
- package/dist/src/v2/workflow-assets.d.ts.map +1 -1
- package/dist/src/v2/workflow-assets.js +256 -43
- package/dist/src/v2/workflow-assets.js.map +1 -1
- package/internal-workflow/docs/agents/bug-workflow-routing.md +9 -7
- package/internal-workflow/docs/agents/coding-skill-routing.md +170 -120
- package/internal-workflow/docs/agents/tool-usage.md +23 -12
- package/internal-workflow/manifest.json +1 -1
- package/internal-workflow/operations/code-review/SKILL.md +34 -15
- package/internal-workflow/operations/implementation/SKILL.md +21 -16
- package/internal-workflow/profiles/implementer.toml +9 -0
- package/internal-workflow/profiles/review_coordinator.toml +9 -0
- package/internal-workflow/profiles/spec_reviewer.toml +9 -0
- package/internal-workflow/profiles/standards_reviewer.toml +9 -0
- package/internal-workflow/schemas/code-review-v1.json +1 -1
- package/internal-workflow/schemas/implementation-report-v1.json +1 -1
- package/internal-workflow/schemas/proof-report-v1.json +1 -1
- package/internal-workflow/skills/bug-root-cause-explainer/SKILL.md +114 -0
- package/internal-workflow/skills/bug-root-cause-explainer/agents/openai.yaml +7 -0
- package/internal-workflow/skills/bug-root-cause-explainer/evals/evals.json +18 -0
- package/internal-workflow/skills/code-review/SKILL.md +84 -306
- package/internal-workflow/skills/code-review/agents/openai.yaml +5 -3
- package/internal-workflow/skills/code-review/evals/evals.json +83 -0
- package/internal-workflow/skills/code-review/references/standards-smells.md +41 -0
- package/internal-workflow/skills/diagnosing-bugs/SKILL.md +69 -32
- package/internal-workflow/skills/diagnosing-bugs/agents/openai.yaml +2 -2
- package/internal-workflow/skills/diagnosing-bugs/evals/evals.json +63 -0
- package/internal-workflow/skills/grilling/SKILL.md +51 -0
- package/internal-workflow/skills/grilling/agents/openai.yaml +6 -0
- package/internal-workflow/skills/grilling/evals/evals.json +47 -0
- package/internal-workflow/skills/implement/SKILL.md +135 -0
- package/internal-workflow/skills/implement/agents/openai.yaml +6 -0
- package/internal-workflow/skills/implement/evals/evals.json +150 -0
- package/internal-workflow/skills/plan/SKILL.md +59 -0
- package/internal-workflow/skills/plan/agents/openai.yaml +6 -0
- package/internal-workflow/skills/plan/evals/evals.json +36 -0
- package/internal-workflow/skills/prototype/LOGIC.md +130 -0
- package/internal-workflow/skills/prototype/SKILL.md +69 -0
- package/internal-workflow/skills/prototype/UI.md +157 -0
- package/internal-workflow/skills/prototype/agents/openai.yaml +6 -0
- package/internal-workflow/skills/prototype/evals/evals.json +67 -0
- package/internal-workflow/skills/research/SKILL.md +110 -0
- package/internal-workflow/skills/research/agents/openai.yaml +6 -0
- package/internal-workflow/skills/research/evals/evals.json +49 -0
- package/internal-workflow/skills/tdd/SKILL.md +72 -67
- package/internal-workflow/skills/tdd/agents/openai.yaml +2 -2
- package/internal-workflow/skills/tdd/evals/evals.json +12 -0
- package/internal-workflow/skills/tdd/mocking.md +48 -1
- package/internal-workflow/skills/tdd/refactoring.md +3 -3
- package/internal-workflow/skills/tickets-orchestrator/SKILL.md +199 -0
- package/internal-workflow/skills/tickets-orchestrator/agents/openai.yaml +6 -0
- package/internal-workflow/skills/tickets-orchestrator/evals/evals.json +126 -0
- package/internal-workflow/skills/tickets-orchestrator/references/delegate-integrate.md +83 -0
- package/internal-workflow/skills/tickets-orchestrator/references/finish-delivery.md +69 -0
- package/internal-workflow/skills/tickets-orchestrator/references/stop-completion.md +63 -0
- package/internal-workflow/skills/to-spec/SKILL.md +133 -0
- package/internal-workflow/skills/to-spec/agents/openai.yaml +6 -0
- package/internal-workflow/skills/to-spec/evals/evals.json +24 -0
- package/internal-workflow/skills/to-tickets/SKILL.md +189 -0
- package/internal-workflow/skills/to-tickets/agents/openai.yaml +6 -0
- package/internal-workflow/skills/to-tickets/evals/evals.json +79 -0
- package/internal-workflow/skills/to-tickets/references/publishing-details.md +117 -0
- package/package.json +1 -1
- package/dist/src/v2/proof-store.d.ts +0 -54
- package/dist/src/v2/proof-store.d.ts.map +0 -1
- package/dist/src/v2/proof-store.js +0 -301
- package/dist/src/v2/proof-store.js.map +0 -1
- package/dist/src/v2/route-continuations.d.ts +0 -32
- package/dist/src/v2/route-continuations.d.ts.map +0 -1
- package/dist/src/v2/route-continuations.js +0 -2
- package/dist/src/v2/route-continuations.js.map +0 -1
- package/dist/src/v2/route-coordinator.d.ts +0 -72
- package/dist/src/v2/route-coordinator.d.ts.map +0 -1
- package/dist/src/v2/route-coordinator.js +0 -275
- package/dist/src/v2/route-coordinator.js.map +0 -1
- package/dist/src/v2/route-decision.d.ts +0 -120
- package/dist/src/v2/route-decision.d.ts.map +0 -1
- package/dist/src/v2/route-decision.js +0 -380
- package/dist/src/v2/route-decision.js.map +0 -1
- package/dist/src/v2/spec-coordinator.d.ts +0 -73
- package/dist/src/v2/spec-coordinator.d.ts.map +0 -1
- package/dist/src/v2/spec-coordinator.js +0 -126
- package/dist/src/v2/spec-coordinator.js.map +0 -1
- package/dist/src/v2/spec-delivery.d.ts +0 -112
- package/dist/src/v2/spec-delivery.d.ts.map +0 -1
- package/dist/src/v2/spec-delivery.js +0 -336
- package/dist/src/v2/spec-delivery.js.map +0 -1
- package/dist/src/v2/triage-route.d.ts +0 -68
- package/dist/src/v2/triage-route.d.ts.map +0 -1
- package/dist/src/v2/triage-route.js +0 -223
- package/dist/src/v2/triage-route.js.map +0 -1
- package/dist/src/v2/waiting-human-coordinator.d.ts +0 -49
- package/dist/src/v2/waiting-human-coordinator.d.ts.map +0 -1
- package/dist/src/v2/waiting-human-coordinator.js +0 -509
- package/dist/src/v2/waiting-human-coordinator.js.map +0 -1
- package/dist/src/v2/waiting-human.d.ts +0 -143
- package/dist/src/v2/waiting-human.d.ts.map +0 -1
- package/dist/src/v2/waiting-human.js +0 -408
- package/dist/src/v2/waiting-human.js.map +0 -1
- package/internal-workflow/docs/agents/contract-test-ledger.md +0 -71
- package/internal-workflow/docs/agents/review-gates.md +0 -42
- package/internal-workflow/docs/agents/review-protocol.md +0 -98
- package/internal-workflow/evals/coding-skill-evals.json +0 -373
- package/internal-workflow/operations/ambiguity-review/SKILL.md +0 -5
- package/internal-workflow/operations/qualification-repair/SKILL.md +0 -17
- package/internal-workflow/operations/spec-author/SKILL.md +0 -12
- package/internal-workflow/operations/spec-review/SKILL.md +0 -12
- package/internal-workflow/operations/triage/SKILL.md +0 -12
- package/internal-workflow/profiles/analyst_deep.toml +0 -9
- package/internal-workflow/profiles/implementer_standard.toml +0 -9
- package/internal-workflow/profiles/proof_agent.toml +0 -8
- package/internal-workflow/profiles/reviewer_deep.toml +0 -9
- package/internal-workflow/profiles/reviewer_standard.toml +0 -9
- package/internal-workflow/schemas/ambiguity-review-v1.json +0 -1
- package/internal-workflow/schemas/spec-author-v1.json +0 -1
- package/internal-workflow/schemas/spec-review-v1.json +0 -30
- package/internal-workflow/schemas/triage-route-v1.json +0 -1
- package/internal-workflow/skills/agent-auto/SKILL.md +0 -19
- package/internal-workflow/skills/agent-auto/agents/openai.yaml +0 -6
- package/internal-workflow/skills/code-debugger/SKILL.md +0 -122
- package/internal-workflow/skills/code-debugger/agents/openai.yaml +0 -7
- package/internal-workflow/skills/code-review/references/bug-classes.md +0 -56
- package/internal-workflow/skills/code-review/references/cleanup-lens.md +0 -52
- package/internal-workflow/skills/code-review/references/framework-lenses.md +0 -34
- package/internal-workflow/skills/code-review/references/targeted-recipes.md +0 -49
- package/internal-workflow/skills/implementation-spec-maker/SKILL.md +0 -107
- package/internal-workflow/skills/implementation-spec-maker/agents/openai.yaml +0 -6
- package/internal-workflow/skills/implementation-spec-maker/references/source-modes.md +0 -32
- package/internal-workflow/skills/implementation-spec-maker/references/spec-template.md +0 -146
- package/internal-workflow/skills/implementation-spec-review/SKILL.md +0 -131
- package/internal-workflow/skills/implementation-spec-review/agents/openai.yaml +0 -6
- package/internal-workflow/skills/implementation-spec-review/evals/evals.json +0 -78
- package/internal-workflow/skills/implementation-spec-review/references/review-loop.md +0 -121
- package/internal-workflow/skills/small-task-implementer/SKILL.md +0 -112
- package/internal-workflow/skills/small-task-implementer/agents/openai.yaml +0 -6
- package/internal-workflow/skills/spec-implementer/SKILL.md +0 -133
- package/internal-workflow/skills/spec-implementer/agents/openai.yaml +0 -6
- package/internal-workflow/skills/spec-implementer/evals/evals.json +0 -30
- package/internal-workflow/skills/spec-implementer/references/review-loop.md +0 -100
- package/internal-workflow/skills/triage/AGENT-BRIEF.md +0 -192
- package/internal-workflow/skills/triage/OUT-OF-SCOPE.md +0 -101
- package/internal-workflow/skills/triage/SKILL.md +0 -134
- package/internal-workflow/skills/triage/agents/openai.yaml +0 -6
package/dist/src/v2/runtime.js
CHANGED
|
@@ -1,35 +1,29 @@
|
|
|
1
1
|
import { constants } from 'node:fs';
|
|
2
2
|
import { createHash, randomUUID } from 'node:crypto';
|
|
3
|
-
import {
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
4
|
import { access, link, lstat, mkdir, mkdtemp, open, readFile, readdir, readlink, realpath, rm, unlink } from 'node:fs/promises';
|
|
5
|
-
import { homedir, hostname
|
|
5
|
+
import { homedir, hostname } from 'node:os';
|
|
6
6
|
import { basename, dirname, isAbsolute, join, posix, relative, resolve } from 'node:path';
|
|
7
7
|
import { writeDurableAtomicFile } from './adapters/durable-atomic-file.js';
|
|
8
8
|
import { GitWorktreeManager } from './adapters/worktree.js';
|
|
9
|
-
import {
|
|
9
|
+
import { ReviewFeedbackObserver } from './review-feedback-coordinator.js';
|
|
10
10
|
import { defaultProcessExecutor } from './adapters/command.js';
|
|
11
11
|
import { RunnerAndroidProofController } from './android-proof-runner.js';
|
|
12
12
|
import { AcceptanceProof, CandidateProofInspectionError } from './acceptance-proof.js';
|
|
13
13
|
import { createCheckedChangeCapabilities } from './checked-change.js';
|
|
14
|
-
import {
|
|
14
|
+
import { InjectedContainedReportOperation } from './contained-report-operation.js';
|
|
15
15
|
import { ContainedImplementationReviewer } from './implementation-reviewer.js';
|
|
16
16
|
import { parseAgentAutoConfig } from './config.js';
|
|
17
|
-
import { WaitingHumanCoordinator } from './waiting-human-coordinator.js';
|
|
18
17
|
import { canonicalJson, parseJsonWithoutDuplicateKeys, sha256, } from './containment.js';
|
|
19
|
-
import { FileProofRecordWriter } from './proof-store.js';
|
|
20
18
|
import { CheckProcessQuiescenceError, parseIssueCheckInvocation, resolveIssueCheckPolicy } from './issue-check-policy.js';
|
|
21
19
|
import { acquireOwnerControlLock, OwnerControlLockBlockedError } from './owner-control-lock.js';
|
|
22
20
|
import { decodeAgentReportForValidation } from './report-envelope.js';
|
|
23
21
|
import { CodexProcess, ProcessQuiescenceError } from './codex-process.js';
|
|
24
22
|
import { FileAndroidLeaseVerifier, FileIosLeaseVerifier } from './mobile-lease.js';
|
|
25
23
|
import { publishRuntimeAssetSnapshot } from './runtime-assets.js';
|
|
26
|
-
import {
|
|
27
|
-
import { SpecCoordinator } from './spec-coordinator.js';
|
|
28
|
-
import { createSpecRevision } from './spec-delivery.js';
|
|
29
|
-
import { validateCodeReviewDefects } from './code-review-report.js';
|
|
30
|
-
import { hashRouteDecision, validateRouteReceipt } from './route-decision.js';
|
|
31
|
-
import { OwnerLockContentionError, OwnerLockSafetyError, RunIssue } from './run-issue.js';
|
|
24
|
+
import { OwnerLockContentionError, OwnerLockSafetyError, RunIssue, } from './run-issue.js';
|
|
32
25
|
import { FileRunRecordWriter } from './run-store.js';
|
|
26
|
+
import { captureProcessStartIdentity, observeProcessGroup, observeProcessIdentity } from './process-identity.js';
|
|
33
27
|
import { parseWorkflowExecutionProfile, verifyWorkflowGeneration, } from './workflow-assets.js';
|
|
34
28
|
import { candidateBindingId, candidateBindingSeed, candidateRef, } from './candidate.js';
|
|
35
29
|
export class LocalGitRunIssueAdapter {
|
|
@@ -51,12 +45,9 @@ export class LocalGitRunIssueAdapter {
|
|
|
51
45
|
throw new Error('candidate expected HEAD diverged');
|
|
52
46
|
await this.git(['-C', input.worktreePath, 'read-tree', input.expectedHeadSha]);
|
|
53
47
|
}),
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}),
|
|
58
|
-
inspectExecution: (input) => this.inspectCandidateExecution(input),
|
|
59
|
-
removeExecution: (input) => this.removeCandidateExecution(input.lease),
|
|
48
|
+
prepareMaterialization: (input) => this.prepareCandidateMaterialization(input),
|
|
49
|
+
inspectMaterialization: (input) => this.inspectCandidateMaterialization(input),
|
|
50
|
+
removeMaterialization: (input) => this.removeCandidateMaterialization(input.materialization),
|
|
60
51
|
copyProofArtifacts: (input) => this.copyCandidateProofArtifacts(input),
|
|
61
52
|
createOrObserveCommit: (input) => this.createOrObserveCandidateCommit(input),
|
|
62
53
|
releasePin: (input) => this.releaseCandidatePin(input.binding, input.expectedPinnedCommitSha),
|
|
@@ -98,23 +89,23 @@ export class LocalGitRunIssueAdapter {
|
|
|
98
89
|
throw new Error('orphan candidate ref cleanup failed');
|
|
99
90
|
}
|
|
100
91
|
}
|
|
101
|
-
const
|
|
102
|
-
const
|
|
103
|
-
canonicalFilesystemPath(
|
|
92
|
+
const materializationRoot = canonicalFilesystemPath(resolve(input.workspaceRoot, '.candidate-materializations'));
|
|
93
|
+
const activeMaterializations = new Map(input.activeMaterializations.map((materialization) => [
|
|
94
|
+
canonicalFilesystemPath(materialization.path), materialization.candidateCommitSha,
|
|
104
95
|
]));
|
|
105
96
|
for (const worktree of await this.worktrees.listWorktrees(root)) {
|
|
106
97
|
const path = canonicalFilesystemPath(worktree.path);
|
|
107
|
-
const relativePath = relative(
|
|
98
|
+
const relativePath = relative(materializationRoot, path);
|
|
108
99
|
if (relativePath === '' || (!relativePath.startsWith('..') && !isAbsolute(relativePath))) {
|
|
109
|
-
const expectedCommit =
|
|
100
|
+
const expectedCommit = activeMaterializations.get(path);
|
|
110
101
|
if (expectedCommit) {
|
|
111
102
|
if (worktree.branch !== undefined || await this.getHead(path).catch(() => '') !== expectedCommit) {
|
|
112
|
-
throw new Error('persisted candidate
|
|
103
|
+
throw new Error('persisted candidate materialization diverged');
|
|
113
104
|
}
|
|
114
105
|
}
|
|
115
106
|
else {
|
|
116
107
|
if (worktree.branch !== undefined)
|
|
117
|
-
throw new Error('orphan candidate
|
|
108
|
+
throw new Error('orphan candidate materialization unexpectedly owns a branch');
|
|
118
109
|
await this.git(['-C', root, 'worktree', 'remove', '--force', path]);
|
|
119
110
|
}
|
|
120
111
|
}
|
|
@@ -215,11 +206,11 @@ export class LocalGitRunIssueAdapter {
|
|
|
215
206
|
&& await this.commitMatches(root, observed, binding.expectedHeadSha, binding.candidateTreeSha) ? 'matching' : 'diverged';
|
|
216
207
|
});
|
|
217
208
|
}
|
|
218
|
-
async
|
|
209
|
+
async prepareCandidateMaterialization(input) {
|
|
219
210
|
return this.candidateOperation('candidate-materialization-io-failed', async () => {
|
|
220
211
|
const root = await this.repositoryFromWorkspaceRoot(input.workspaceRoot);
|
|
221
212
|
this.candidateRepositories.set(input.binding.candidateRef, root);
|
|
222
|
-
const path = join(input.workspaceRoot, '.candidate-
|
|
213
|
+
const path = join(input.workspaceRoot, '.candidate-materializations', input.runId, input.binding.bindingId, input.materializationId);
|
|
223
214
|
const existing = (await this.worktrees.listWorktrees(root)).find((worktree) => sameFilesystemPath(worktree.path, path));
|
|
224
215
|
if (existing) {
|
|
225
216
|
const clean = await this.worktrees.isWorktreeClean(path);
|
|
@@ -231,17 +222,16 @@ export class LocalGitRunIssueAdapter {
|
|
|
231
222
|
await mkdir(dirname(path), { recursive: true });
|
|
232
223
|
await this.git(['-C', root, 'worktree', 'add', '--detach', path, input.binding.candidateCommitSha]);
|
|
233
224
|
}
|
|
234
|
-
return { kind: 'prepared',
|
|
225
|
+
return { kind: 'prepared', materialization: {
|
|
235
226
|
version: 2, bindingId: input.binding.bindingId, candidateCommitSha: input.binding.candidateCommitSha,
|
|
236
|
-
path,
|
|
237
|
-
processGroupId: null, preparedAt: new Date().toISOString(), launchedAt: null,
|
|
227
|
+
path,
|
|
238
228
|
} };
|
|
239
229
|
});
|
|
240
230
|
}
|
|
241
|
-
async
|
|
231
|
+
async inspectCandidateMaterialization(input) {
|
|
242
232
|
return this.candidateOperation('candidate-materialization-io-failed', async () => {
|
|
243
233
|
try {
|
|
244
|
-
const stat = await lstat(input.
|
|
234
|
+
const stat = await lstat(input.materialization.path);
|
|
245
235
|
if (stat.isSymbolicLink() || !stat.isDirectory())
|
|
246
236
|
return 'mutated';
|
|
247
237
|
}
|
|
@@ -251,43 +241,41 @@ export class LocalGitRunIssueAdapter {
|
|
|
251
241
|
throw error;
|
|
252
242
|
}
|
|
253
243
|
const [head, tree, changed] = await Promise.all([
|
|
254
|
-
this.getHead(input.
|
|
255
|
-
this.git(['-C', input.
|
|
256
|
-
this.worktrees.listChangedFilesIgnoringUntrackedRoot(input.
|
|
244
|
+
this.getHead(input.materialization.path),
|
|
245
|
+
this.git(['-C', input.materialization.path, 'rev-parse', 'HEAD^{tree}']).then((value) => value.trim()),
|
|
246
|
+
this.worktrees.listChangedFilesIgnoringUntrackedRoot(input.materialization.path, input.artifactDir),
|
|
257
247
|
]);
|
|
258
248
|
return head === input.binding.candidateCommitSha && tree === input.binding.candidateTreeSha && changed.length === 0 ? 'matching' : 'mutated';
|
|
259
249
|
});
|
|
260
250
|
}
|
|
261
|
-
async
|
|
251
|
+
async removeCandidateMaterialization(materialization) {
|
|
262
252
|
return this.candidateOperation('candidate-materialization-io-failed', async () => {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
const root = await this.repositoryFromWorkspaceRoot(lease.path);
|
|
266
|
-
const registered = (await this.worktrees.listWorktrees(root)).find((worktree) => sameFilesystemPath(worktree.path, lease.path));
|
|
253
|
+
const root = await this.repositoryFromWorkspaceRoot(materialization.path);
|
|
254
|
+
const registered = (await this.worktrees.listWorktrees(root)).find((worktree) => sameFilesystemPath(worktree.path, materialization.path));
|
|
267
255
|
if (!registered)
|
|
268
256
|
return;
|
|
269
|
-
if (registered.branch !== undefined || await this.getHead(
|
|
270
|
-
throw new Error('candidate
|
|
271
|
-
await this.git(['-C', root, 'worktree', 'remove', '--force',
|
|
257
|
+
if (registered.branch !== undefined || await this.getHead(materialization.path) !== materialization.candidateCommitSha)
|
|
258
|
+
throw new Error('candidate materialization diverged');
|
|
259
|
+
await this.git(['-C', root, 'worktree', 'remove', '--force', materialization.path]);
|
|
272
260
|
});
|
|
273
261
|
}
|
|
274
262
|
async copyCandidateProofArtifacts(input) {
|
|
275
263
|
return this.candidateOperation('candidate-materialization-io-failed', async () => {
|
|
276
264
|
const proofRoot = `${input.artifactDir}/${input.proofId}`;
|
|
277
|
-
const repositoryRoot = await this.repositoryFromWorkspaceRoot(input.
|
|
265
|
+
const repositoryRoot = await this.repositoryFromWorkspaceRoot(input.materialization.path);
|
|
278
266
|
const registered = (await this.worktrees.listWorktrees(repositoryRoot))
|
|
279
|
-
.find((worktree) => sameFilesystemPath(worktree.path, input.
|
|
267
|
+
.find((worktree) => sameFilesystemPath(worktree.path, input.materialization.path));
|
|
280
268
|
if (!registered || registered.branch !== undefined
|
|
281
|
-
|| await this.getHead(input.
|
|
269
|
+
|| await this.getHead(input.materialization.path).catch(() => '') !== input.materialization.candidateCommitSha) {
|
|
282
270
|
return { kind: 'artifact-conflict', relativePath: proofRoot };
|
|
283
271
|
}
|
|
284
|
-
const sourceRoot = await inspectSafeArtifactRoot(input.
|
|
272
|
+
const sourceRoot = await inspectSafeArtifactRoot(input.materialization.path);
|
|
285
273
|
const destinationRoot = await inspectSafeArtifactRoot(input.issueWorktreePath);
|
|
286
274
|
for (const artifact of input.artifacts) {
|
|
287
275
|
if (artifact.relativePath !== proofRoot && !artifact.relativePath.startsWith(`${proofRoot}/`))
|
|
288
276
|
return { kind: 'artifact-conflict', relativePath: artifact.relativePath };
|
|
289
277
|
try {
|
|
290
|
-
const source = join(input.
|
|
278
|
+
const source = join(input.materialization.path, artifact.relativePath);
|
|
291
279
|
const destination = join(input.issueWorktreePath, artifact.relativePath);
|
|
292
280
|
const sourceBytes = await readRegularFileWithoutSymlinkAncestors(sourceRoot, source);
|
|
293
281
|
if (sha256(sourceBytes) !== artifact.sha256)
|
|
@@ -518,6 +506,13 @@ export class LocalGitRunIssueAdapter {
|
|
|
518
506
|
listChangedFiles(worktreePath) {
|
|
519
507
|
return this.worktrees.listChangedFiles(worktreePath);
|
|
520
508
|
}
|
|
509
|
+
async diffTrees(worktreePath, previousTreeSha, candidateTreeSha) {
|
|
510
|
+
const [names, patch] = await Promise.all([
|
|
511
|
+
this.git(['-C', worktreePath, 'diff-tree', '--no-commit-id', '--name-only', '-r', '-z', previousTreeSha, candidateTreeSha], { maxOutputBytes: 1024 * 1024 }),
|
|
512
|
+
this.git(['-C', worktreePath, 'diff', '--binary', '--full-index', '--no-ext-diff', previousTreeSha, candidateTreeSha, '--'], { maxOutputBytes: 1024 * 1024 }),
|
|
513
|
+
]);
|
|
514
|
+
return { changedFiles: names.split('\0').filter(Boolean).sort(), patch };
|
|
515
|
+
}
|
|
521
516
|
listChangedFilesIgnoringUntrackedRoot(worktreePath, ignoredRoot) {
|
|
522
517
|
return this.worktrees.listChangedFilesIgnoringUntrackedRoot(worktreePath, ignoredRoot);
|
|
523
518
|
}
|
|
@@ -784,106 +779,84 @@ export class ContainedImplementationAgent {
|
|
|
784
779
|
async run(input) {
|
|
785
780
|
const config = this.dependencies.config();
|
|
786
781
|
const canonicalRepository = `${config.github.owner.toLowerCase()}/${config.github.repo.toLowerCase()}`;
|
|
787
|
-
const
|
|
788
|
-
const
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
782
|
+
const attemptId = input.attemptId;
|
|
783
|
+
const attempt = await prepareContainedAttempt({
|
|
784
|
+
orchestratorHome: this.dependencies.orchestratorHome,
|
|
785
|
+
canonicalRepository,
|
|
786
|
+
runId: input.runId,
|
|
787
|
+
attemptId,
|
|
788
|
+
operationId: input.operation,
|
|
789
|
+
workflowGeneration: input.workflowGeneration,
|
|
790
|
+
bootId: this.dependencies.bootId,
|
|
794
791
|
});
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
return { kind: 'transport-failed', resumable: true, code: result.code };
|
|
802
|
-
if (result.status === 'safe-halt')
|
|
803
|
-
return { kind: 'safe-halt', code: result.code };
|
|
804
|
-
return result.kind === 'external'
|
|
805
|
-
? { kind: 'transport-failed', resumable: true, code: result.code }
|
|
806
|
-
: { kind: 'safe-halt', code: result.code };
|
|
807
|
-
}
|
|
808
|
-
async settle(input) {
|
|
809
|
-
const config = this.dependencies.config();
|
|
810
|
-
const canonicalRepository = `${config.github.owner.toLowerCase()}/${config.github.repo.toLowerCase()}`;
|
|
811
|
-
const promptFacts = implementationPromptFacts(input);
|
|
812
|
-
const result = await this.operation(config, canonicalRepository, promptFacts).settle({
|
|
813
|
-
operation: input.operation, runId: input.runId, worktreePath: input.worktreePath,
|
|
814
|
-
workflowGeneration: input.workflowGeneration, promptFacts, signal: input.signal,
|
|
815
|
-
context: { repairOnly: input.repairOnly, reworkFindings: [...input.reworkFindings] },
|
|
816
|
-
invocationState: input.invocationState,
|
|
792
|
+
const baseline = await this.dependencies.git.snapshot(input.worktreePath);
|
|
793
|
+
await input.onPrepared?.({
|
|
794
|
+
attemptId,
|
|
795
|
+
reportPath: attempt.reportPath,
|
|
796
|
+
preparedAt: (this.dependencies.now ?? (() => new Date().toISOString()))(),
|
|
797
|
+
baseline,
|
|
817
798
|
});
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
799
|
+
try {
|
|
800
|
+
try {
|
|
801
|
+
const recovered = await readRegularFile(attempt.reportPath);
|
|
802
|
+
return { kind: 'completed', attemptId, report: decodeAgentReportForValidation(recovered) };
|
|
803
|
+
}
|
|
804
|
+
catch (error) {
|
|
805
|
+
if (!isErrorCode(error, 'ENOENT'))
|
|
806
|
+
return { kind: 'internal-error' };
|
|
807
|
+
}
|
|
808
|
+
const result = await (this.dependencies.process ?? new CodexProcess()).run({
|
|
809
|
+
codexPath: config.codex.command,
|
|
810
|
+
cwd: input.worktreePath,
|
|
811
|
+
schemaPath: attempt.schemaPath,
|
|
812
|
+
reportPath: attempt.reportPath,
|
|
813
|
+
toolHome: attempt.toolHome,
|
|
814
|
+
tmpDir: attempt.tmpDir,
|
|
815
|
+
safePath: this.dependencies.safePath,
|
|
816
|
+
parentCodexHome: this.dependencies.parentCodexHome,
|
|
817
|
+
parentEnv: process.env,
|
|
818
|
+
prompt: [
|
|
819
|
+
`Package profile instructions: ${attempt.profile.developerInstructions}`,
|
|
820
|
+
`Follow the exact operation at ${attempt.operationPath}.`,
|
|
821
|
+
`The operation's immutable workflow root is ${attempt.workflowRoot}.`,
|
|
822
|
+
`Implement issue #${input.issue.number}: ${input.issue.title}`,
|
|
823
|
+
`Implementation cycle: ${input.cycle}.`,
|
|
824
|
+
...(input.reviewFeedbackRound ? [`Pull-request feedback repair round: ${input.reviewFeedbackRound}.`] : []),
|
|
825
|
+
...(input.reviewFeedback?.length ? [`Frozen trusted pull-request feedback: ${canonicalJson(input.reviewFeedback)}`] : []),
|
|
826
|
+
`Exact delivery authority: ${canonicalJson(input.deliveryAuthority)}`,
|
|
827
|
+
`Frozen acceptance criteria: ${canonicalJson(input.frozenCriteria)}`,
|
|
828
|
+
...(input.reworkFindings.length > 0 ? [`Repair these verified findings: ${canonicalJson(input.reworkFindings)}`] : []),
|
|
829
|
+
...(input.repairOnly ? ['Report repair only: do not modify any worktree file; emit a schema-valid implementation report for the existing change.'] : []),
|
|
830
|
+
'Do not commit, push, publish, or print credentials or local auth paths.',
|
|
831
|
+
].join('\n'),
|
|
832
|
+
timeoutMs: config.codex.timeoutMs,
|
|
833
|
+
idleTimeoutMs: config.codex.idleTimeoutMs,
|
|
834
|
+
operationPolicy: attempt.policy,
|
|
835
|
+
executionProfile: attempt.profile,
|
|
836
|
+
onSpawned: async ({ pid, processGroupId }) => input.onLaunched?.({
|
|
837
|
+
attemptId, pid, processGroupId, launchedAt: (this.dependencies.now ?? (() => new Date().toISOString()))(),
|
|
830
838
|
}),
|
|
831
|
-
})
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
timeoutMs: config.codex.timeoutMs, idleTimeoutMs: config.codex.idleTimeoutMs,
|
|
851
|
-
operationPolicy: attempt.policy, executionProfile: attempt.profile, onSpawned,
|
|
852
|
-
}, signal);
|
|
853
|
-
if (result.kind === 'cancelled')
|
|
854
|
-
return { status: 'cancelled' };
|
|
855
|
-
if (['spawn-failed', 'transport-failed', 'timeout', 'idle-timeout'].includes(result.kind))
|
|
856
|
-
return { status: 'retryable', code: `mutable-operation-${result.kind}` };
|
|
857
|
-
if (result.kind !== 'completed' || result.report.kind !== 'available')
|
|
858
|
-
return { status: 'retryable', code: 'mutable-operation-report-unavailable' };
|
|
859
|
-
return { status: 'completed', reportBytes: result.report.bytes };
|
|
860
|
-
}
|
|
861
|
-
catch (error) {
|
|
862
|
-
return error instanceof ProcessQuiescenceError
|
|
863
|
-
? { status: 'safe-halt' }
|
|
864
|
-
: { status: 'retryable', code: 'mutable-operation-launch-failed' };
|
|
865
|
-
}
|
|
866
|
-
},
|
|
867
|
-
});
|
|
839
|
+
}, input.signal);
|
|
840
|
+
if (result.kind === 'cancelled')
|
|
841
|
+
return { kind: 'cancelled' };
|
|
842
|
+
if (['spawn-failed', 'transport-failed', 'timeout', 'idle-timeout', 'launch-gate-failed'].includes(result.kind)) {
|
|
843
|
+
return { kind: 'transport-failed', resumable: true };
|
|
844
|
+
}
|
|
845
|
+
if (result.kind !== 'completed' || result.report.kind !== 'available')
|
|
846
|
+
return { kind: 'internal-error' };
|
|
847
|
+
return {
|
|
848
|
+
kind: 'completed',
|
|
849
|
+
attemptId,
|
|
850
|
+
report: decodeAgentReportForValidation(result.report.bytes),
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
catch (error) {
|
|
854
|
+
if (!(error instanceof ProcessQuiescenceError))
|
|
855
|
+
return { kind: 'internal-error' };
|
|
856
|
+
return { kind: 'safe-halt' };
|
|
857
|
+
}
|
|
868
858
|
}
|
|
869
859
|
}
|
|
870
|
-
function implementationPromptFacts(input) {
|
|
871
|
-
const task = input.operation === 'qualification-repair' ? [
|
|
872
|
-
`Pre-implementation qualification repair for issue #${input.issue.number}: ${input.issue.title}`,
|
|
873
|
-
'Do not implement the issue acceptance criteria yet. Repair only the reported scoped-check failures so the existing worktree qualifies for implementation.',
|
|
874
|
-
'You may modify the product files required by those failures. Return changedFiles as the complete cumulative worktree change set.',
|
|
875
|
-
] : [`Implement issue #${input.issue.number}: ${input.issue.title}`];
|
|
876
|
-
return [...task,
|
|
877
|
-
`Implementation cycle: ${input.cycle}.`,
|
|
878
|
-
...(input.reviewFeedbackRound ? [`Pull-request feedback repair round: ${input.reviewFeedbackRound}.`] : []),
|
|
879
|
-
...(input.reviewFeedback?.length ? [`Frozen trusted pull-request feedback: ${canonicalJson(input.reviewFeedback)}`] : []),
|
|
880
|
-
...(input.operation !== 'qualification-repair' ? [`Frozen acceptance criteria: ${canonicalJson(input.frozenCriteria)}`] : []),
|
|
881
|
-
...(input.reworkFindings.length > 0 ? [`Repair these verified findings: ${canonicalJson(input.reworkFindings)}`] : []),
|
|
882
|
-
...(input.phaseFacts?.length ? [`Phase-owned invocation correlation: ${canonicalJson(input.phaseFacts)}`] : []),
|
|
883
|
-
...(input.repairOnly ? ['Report repair only: do not modify any worktree file; emit a schema-valid implementation report for the existing change.'] : []),
|
|
884
|
-
'Do not commit, push, publish, or print credentials or local auth paths.',
|
|
885
|
-
];
|
|
886
|
-
}
|
|
887
860
|
export class ContainedProofAgent {
|
|
888
861
|
dependencies;
|
|
889
862
|
constructor(dependencies) {
|
|
@@ -892,54 +865,150 @@ export class ContainedProofAgent {
|
|
|
892
865
|
async run(input) {
|
|
893
866
|
if (!input.workflowGeneration)
|
|
894
867
|
throw new Error('proof workflow generation is required');
|
|
895
|
-
if (!input.iosProofInputs)
|
|
896
|
-
throw new Error('runner-owned iOS proof inputs are required');
|
|
897
868
|
const config = this.dependencies.config();
|
|
869
|
+
const canonicalRepository = `${config.github.owner.toLowerCase()}/${config.github.repo.toLowerCase()}`;
|
|
898
870
|
const issueWorktreePath = resolve(this.dependencies.targetRoot, config.runner.workspaceRoot, `issue-${input.issue.number}`);
|
|
899
871
|
const worktreePath = input.worktreePath ? resolve(input.worktreePath) : issueWorktreePath;
|
|
872
|
+
const attempt = await prepareContainedAttempt({
|
|
873
|
+
orchestratorHome: this.dependencies.orchestratorHome,
|
|
874
|
+
canonicalRepository,
|
|
875
|
+
runId: input.runId,
|
|
876
|
+
attemptId: input.attemptId ?? (this.dependencies.createAttemptId ?? randomUUID)(),
|
|
877
|
+
operationId: 'acceptance-proof',
|
|
878
|
+
workflowGeneration: input.workflowGeneration,
|
|
879
|
+
bootId: this.dependencies.bootId,
|
|
880
|
+
});
|
|
900
881
|
const artifactRoot = resolve(worktreePath, config.proof.artifactDir);
|
|
882
|
+
const snapshotRoot = dirname(attempt.sourceSkillPath ?? attempt.operationPath);
|
|
883
|
+
const iosLeaseRoot = join(resolve(this.dependencies.orchestratorHome), 'v2', sha256(canonicalRepository), 'leases');
|
|
884
|
+
const iosLeaseArtifact = join(artifactRoot, input.proofId, 'ios-lease.json');
|
|
885
|
+
const iosTooling = await discoverIosTooling(this.dependencies.processExecutor, this.dependencies.iosXcrunPath);
|
|
901
886
|
const before = await artifactInventory(artifactRoot, config.proof.artifactDir);
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
887
|
+
try {
|
|
888
|
+
try {
|
|
889
|
+
const recoveredReport = await readRegularFile(attempt.reportPath);
|
|
890
|
+
const recoveredInventory = await artifactInventory(artifactRoot, config.proof.artifactDir);
|
|
891
|
+
const runnerPrepared = new Set(input.runnerPreparedArtifactPaths);
|
|
892
|
+
return {
|
|
893
|
+
kind: 'report',
|
|
894
|
+
report: decodeAgentReportForValidation(recoveredReport, ['visualEvidence', 'blocker']),
|
|
895
|
+
proofPhaseChangedFiles: [...recoveredInventory.keys()].filter((path) => !runnerPrepared.has(path)).sort(),
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
catch (error) {
|
|
899
|
+
if (!isErrorCode(error, 'ENOENT'))
|
|
900
|
+
throw error;
|
|
901
|
+
}
|
|
902
|
+
if (input.recoverOnly)
|
|
903
|
+
return { kind: 'internal-error' };
|
|
904
|
+
const result = await (this.dependencies.process ?? new CodexProcess()).run({
|
|
905
|
+
codexPath: config.codex.command,
|
|
906
|
+
cwd: worktreePath,
|
|
907
|
+
schemaPath: attempt.schemaPath,
|
|
908
|
+
reportPath: attempt.reportPath,
|
|
909
|
+
toolHome: attempt.toolHome,
|
|
910
|
+
tmpDir: attempt.tmpDir,
|
|
911
|
+
safePath: this.dependencies.safePath,
|
|
912
|
+
parentCodexHome: this.dependencies.parentCodexHome,
|
|
913
|
+
parentEnv: process.env,
|
|
914
|
+
prompt: [
|
|
915
|
+
`Package profile instructions: ${attempt.profile.developerInstructions}`,
|
|
916
|
+
`Follow the exact operation at ${attempt.operationPath}.`,
|
|
917
|
+
`The operation's immutable workflow root is ${attempt.workflowRoot}.`,
|
|
918
|
+
`Independently prove issue #${input.issue.number}.`,
|
|
919
|
+
`Frozen acceptance criteria: ${canonicalJson(input.frozenCriteria)}`,
|
|
920
|
+
`Checked change digest: ${input.checkedChangeSha256}.`,
|
|
921
|
+
`Checked changed files: ${canonicalJson(input.changedFiles)}.`,
|
|
922
|
+
`Configured check receipts: ${canonicalJson(input.checks)}.`,
|
|
923
|
+
`Write evidence only below ${config.proof.artifactDir}.`,
|
|
924
|
+
'When a frozen criterion has a browser surface, follow references/browser.md from the exact acceptance-proof skill snapshot.',
|
|
925
|
+
'When a frozen criterion has an Android surface, follow references/android.md from the exact acceptance-proof skill snapshot.',
|
|
926
|
+
...(config.proof.android ? [
|
|
927
|
+
`Runner-owned Android artifact paths: ${canonicalJson(input.runnerPreparedArtifactPaths)}.`,
|
|
928
|
+
`Runner-owned Android preparation warnings: ${canonicalJson(input.runnerPreparationWarnings)}.`,
|
|
929
|
+
'The trusted Runner owns emulator, build, adb, lease, capture, and cleanup actions. Do not invoke adb, emulator, Flutter run, or an Android lease helper.',
|
|
930
|
+
'Inspect Runner artifacts when present. If preparation warnings are present, continue with all available non-visual evidence and preserve the warning as a residual risk; Android infrastructure failure alone must not block delivery.',
|
|
931
|
+
] : [
|
|
932
|
+
'Runner-owned Android proof is not configured for this repository. Do not invoke adb, emulator, Flutter run, or an Android lease helper; return a typed tool blocker for Android criteria.',
|
|
933
|
+
]),
|
|
934
|
+
'When a frozen criterion has an iOS surface, follow references/ios.md from the exact acceptance-proof skill snapshot.',
|
|
935
|
+
`iOS lease helper: ${join(snapshotRoot, 'tools', 'ios-lease.mjs')}.`,
|
|
936
|
+
`iOS lease root: ${iosLeaseRoot}.`,
|
|
937
|
+
`iOS lease artifact: ${iosLeaseArtifact}.`,
|
|
938
|
+
`iOS lease proof ID: ${input.proofId}.`,
|
|
939
|
+
`iOS lease owner PID: ${process.pid}.`,
|
|
940
|
+
`iOS xcrun path: ${this.dependencies.iosXcrunPath}.`,
|
|
941
|
+
...(iosTooling ? [
|
|
942
|
+
`iOS runtime ID: ${iosTooling.runtimeId}.`,
|
|
943
|
+
`iOS device type ID: ${iosTooling.deviceTypeId}.`,
|
|
944
|
+
] : ['iOS Simulator tooling discovery is unavailable; return a typed tool blocker for an iOS surface.']),
|
|
945
|
+
...(input.repairOnly ? [`Proof Report repair only: ${canonicalJson(input.repairFindings)} Do not modify product or evidence files.`] : []),
|
|
946
|
+
'Do not modify product files, commit, push, publish, or print credentials or local auth paths.',
|
|
947
|
+
].join('\n'),
|
|
948
|
+
timeoutMs: config.codex.timeoutMs,
|
|
949
|
+
idleTimeoutMs: config.codex.idleTimeoutMs,
|
|
950
|
+
operationPolicy: attempt.policy,
|
|
951
|
+
executionProfile: attempt.profile,
|
|
952
|
+
onSpawned: async ({ pid, processGroupId }) => input.onLaunched?.({
|
|
953
|
+
pid, processGroupId, launchedAt: new Date().toISOString(),
|
|
954
|
+
}),
|
|
955
|
+
}, input.signal);
|
|
956
|
+
if (result.kind === 'cancelled')
|
|
957
|
+
return { kind: 'cancelled' };
|
|
958
|
+
if (['spawn-failed', 'transport-failed', 'timeout', 'idle-timeout', 'launch-gate-failed'].includes(result.kind)) {
|
|
959
|
+
return { kind: 'transport-failed', resumable: true };
|
|
930
960
|
}
|
|
931
|
-
|
|
961
|
+
if (result.kind !== 'completed' || result.report.kind !== 'available')
|
|
962
|
+
return { kind: 'internal-error' };
|
|
963
|
+
const after = await artifactInventory(artifactRoot, config.proof.artifactDir);
|
|
964
|
+
return {
|
|
965
|
+
kind: 'report',
|
|
966
|
+
report: decodeAgentReportForValidation(result.report.bytes, ['visualEvidence', 'blocker']),
|
|
967
|
+
proofPhaseChangedFiles: changedArtifactPaths(before, after),
|
|
968
|
+
};
|
|
932
969
|
}
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
970
|
+
catch (error) {
|
|
971
|
+
return error instanceof ProcessQuiescenceError ? { kind: 'safe-halt' } : { kind: 'internal-error' };
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
export async function observeAttemptReadViewCleanup(input) {
|
|
976
|
+
const runtimeRoot = join(resolve(input.orchestratorHome), 'v2', sha256(input.canonicalRepository));
|
|
977
|
+
const attemptRoot = join(runtimeRoot, 'runs', input.identity.runId, 'attempts', input.identity.attemptId);
|
|
978
|
+
const expectedResultPath = join(attemptRoot, 'report.json');
|
|
979
|
+
if (input.identity.resultPath !== expectedResultPath)
|
|
980
|
+
return 'pending';
|
|
981
|
+
let canonicalRuntimeRoot;
|
|
982
|
+
let canonicalAttemptRoot;
|
|
983
|
+
try {
|
|
984
|
+
canonicalRuntimeRoot = await realpath(runtimeRoot);
|
|
985
|
+
}
|
|
986
|
+
catch (error) {
|
|
987
|
+
return isErrorCode(error, 'ENOENT') ? 'confirmed' : 'pending';
|
|
988
|
+
}
|
|
989
|
+
try {
|
|
990
|
+
canonicalAttemptRoot = await realpath(attemptRoot);
|
|
942
991
|
}
|
|
992
|
+
catch (error) {
|
|
993
|
+
return isErrorCode(error, 'ENOENT') ? 'confirmed' : 'pending';
|
|
994
|
+
}
|
|
995
|
+
const expectedRelative = relative(runtimeRoot, attemptRoot);
|
|
996
|
+
if (!isPathWithin(canonicalRuntimeRoot, canonicalAttemptRoot)
|
|
997
|
+
|| relative(canonicalRuntimeRoot, canonicalAttemptRoot) !== expectedRelative)
|
|
998
|
+
return 'pending';
|
|
999
|
+
await rm(join(attemptRoot, 'read-view'), { recursive: true, force: true });
|
|
1000
|
+
return 'confirmed';
|
|
1001
|
+
}
|
|
1002
|
+
export function selectedConfiguredCheckPolicySha256(selectedChecks, configuredAuthority) {
|
|
1003
|
+
const selected = {};
|
|
1004
|
+
for (const check of selectedChecks) {
|
|
1005
|
+
if (Object.prototype.hasOwnProperty.call(selected, check.id)
|
|
1006
|
+
|| configuredAuthority[check.id] !== check.command) {
|
|
1007
|
+
throw new CandidateProofInspectionError('selected check no longer matches current configured authority');
|
|
1008
|
+
}
|
|
1009
|
+
selected[check.id] = check.command;
|
|
1010
|
+
}
|
|
1011
|
+
return sha256(canonicalJson(selected));
|
|
943
1012
|
}
|
|
944
1013
|
export async function resolveCodexExecutable(command, safePath) {
|
|
945
1014
|
if (command.length === 0 || safePath.length === 0)
|
|
@@ -1006,6 +1075,13 @@ export function createV2Runtime(input) {
|
|
|
1006
1075
|
git,
|
|
1007
1076
|
now,
|
|
1008
1077
|
});
|
|
1078
|
+
const proofAgent = input.proofAgent ?? new ContainedProofAgent({
|
|
1079
|
+
...containedDependencies(),
|
|
1080
|
+
targetRoot,
|
|
1081
|
+
androidAdbPath,
|
|
1082
|
+
iosXcrunPath,
|
|
1083
|
+
processExecutor: commandExecutor,
|
|
1084
|
+
});
|
|
1009
1085
|
const androidProofController = new RunnerAndroidProofController({
|
|
1010
1086
|
adbPath: androidAdbPath,
|
|
1011
1087
|
emulatorPath: androidEmulatorPath,
|
|
@@ -1013,10 +1089,6 @@ export function createV2Runtime(input) {
|
|
|
1013
1089
|
now: () => new Date(now()),
|
|
1014
1090
|
});
|
|
1015
1091
|
const reportOperation = new InjectedContainedReportOperation({
|
|
1016
|
-
host: hostname(),
|
|
1017
|
-
bootId: input.bootId,
|
|
1018
|
-
now,
|
|
1019
|
-
createAttemptId: input.createAttemptId ?? randomUUID,
|
|
1020
1092
|
prepare: async ({ operation, attemptId, runId, workflowGeneration }) => ({
|
|
1021
1093
|
operation,
|
|
1022
1094
|
generationHash: workflowGeneration.generationHash,
|
|
@@ -1030,44 +1102,38 @@ export function createV2Runtime(input) {
|
|
|
1030
1102
|
bootId: input.bootId,
|
|
1031
1103
|
}),
|
|
1032
1104
|
}),
|
|
1033
|
-
snapshot: (worktreePath
|
|
1034
|
-
|
|
1035
|
-
: git.snapshot(worktreePath),
|
|
1036
|
-
readReport: async (path) => readRegularFile(path).then((bytes) => ({ status: 'available', bytes }))
|
|
1037
|
-
.catch((error) => isErrorCode(error, 'ENOENT') ? { status: 'absent' } : { status: 'unknown' }),
|
|
1038
|
-
settleAttempt: async (attempt) => attempt.tmpDir
|
|
1039
|
-
? rm(join(dirname(attempt.tmpDir), 'read-view'), { recursive: true, force: true })
|
|
1040
|
-
: Promise.reject(new Error('report operation attempt temporary directory is missing')),
|
|
1041
|
-
processStartIdentity: readProcessStartIdentity,
|
|
1042
|
-
inspectProcess: inspectReportProcess,
|
|
1043
|
-
launch: async ({ operation, attempt, worktreePath, promptFacts, signal, onSpawned }) => {
|
|
1105
|
+
snapshot: (worktreePath) => git.snapshot(worktreePath),
|
|
1106
|
+
launch: async ({ attempt, worktreePath, promptFacts, signal, onLaunched }) => {
|
|
1044
1107
|
const config = requireConfig(currentConfig);
|
|
1045
1108
|
if (!attempt.schemaPath || !attempt.reportPath || !attempt.toolHome || !attempt.tmpDir
|
|
1046
1109
|
|| !attempt.profile || !attempt.operationPath || !attempt.workflowRoot) {
|
|
1047
1110
|
return { status: 'blocked', kind: 'safety', code: 'report-operation-attempt-incomplete' };
|
|
1048
1111
|
}
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
deniedPaths: config.deny.readPaths,
|
|
1056
|
-
});
|
|
1057
|
-
}
|
|
1058
|
-
catch {
|
|
1059
|
-
return { status: 'blocked', kind: 'safety', code: 'report-operation-read-view-failed' };
|
|
1112
|
+
try {
|
|
1113
|
+
return { status: 'completed', reportBytes: await readRegularFile(attempt.reportPath) };
|
|
1114
|
+
}
|
|
1115
|
+
catch (error) {
|
|
1116
|
+
if (!isErrorCode(error, 'ENOENT')) {
|
|
1117
|
+
return { status: 'blocked', kind: 'safety', code: 'report-operation-result-read-failed' };
|
|
1060
1118
|
}
|
|
1061
1119
|
}
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1120
|
+
let readView;
|
|
1121
|
+
try {
|
|
1122
|
+
readView = await materializeReportReadView({
|
|
1123
|
+
worktreePath,
|
|
1124
|
+
destination: join(dirname(attempt.tmpDir), 'read-view'),
|
|
1125
|
+
deniedPaths: config.deny.readPaths,
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
catch {
|
|
1129
|
+
return { status: 'blocked', kind: 'safety', code: 'report-operation-read-view-failed' };
|
|
1130
|
+
}
|
|
1065
1131
|
let result;
|
|
1066
1132
|
let cleanupAfterSafeHalt = false;
|
|
1067
1133
|
try {
|
|
1068
1134
|
result = await containedProcess.run({
|
|
1069
1135
|
codexPath: config.codex.command,
|
|
1070
|
-
cwd:
|
|
1136
|
+
cwd: readView,
|
|
1071
1137
|
schemaPath: attempt.schemaPath,
|
|
1072
1138
|
reportPath: attempt.reportPath,
|
|
1073
1139
|
toolHome: attempt.toolHome,
|
|
@@ -1075,25 +1141,12 @@ export function createV2Runtime(input) {
|
|
|
1075
1141
|
safePath: requireRuntimeString(input.safePath, 'safePath'),
|
|
1076
1142
|
parentCodexHome: requireRuntimeString(input.parentCodexHome, 'parentCodexHome'),
|
|
1077
1143
|
parentEnv: process.env,
|
|
1078
|
-
prompt:
|
|
1079
|
-
`Package profile instructions: ${attempt.profile.developerInstructions}`,
|
|
1080
|
-
`Follow the exact operation at ${attempt.operationPath}.`,
|
|
1081
|
-
`The operation's immutable workflow root is ${attempt.workflowRoot}.`,
|
|
1082
|
-
`Runner-provided facts: ${canonicalJson(promptFacts)}`,
|
|
1083
|
-
`Write the complete new immutable revision only to ${revisionPath}. Return that exact absolute path and its SHA-256 in the report.`,
|
|
1084
|
-
'Do not modify the product worktree, prior revisions, external state, or any .env file.',
|
|
1085
|
-
].join('\n') : operation === 'acceptance-proof' ? [
|
|
1086
|
-
`Package profile instructions: ${attempt.profile.developerInstructions}`,
|
|
1087
|
-
`Follow the exact operation at ${attempt.operationPath}.`,
|
|
1088
|
-
`The operation's immutable workflow root is ${attempt.workflowRoot}.`,
|
|
1089
|
-
`Runner-provided proof facts:\n${promptFacts.join('\n')}`,
|
|
1090
|
-
`Write evidence only below ${config.proof.artifactDir}. CheckedChange is the sole authority for completed checks.`,
|
|
1091
|
-
'Do not modify product files, commit, push, publish, use network or MCP tools, or print credentials or local auth paths.',
|
|
1092
|
-
].join('\n') : [
|
|
1144
|
+
prompt: [
|
|
1093
1145
|
`Package profile instructions: ${attempt.profile.developerInstructions}`,
|
|
1094
1146
|
`Follow the exact operation at ${attempt.operationPath}.`,
|
|
1095
1147
|
`The operation's immutable workflow root is ${attempt.workflowRoot}.`,
|
|
1096
1148
|
`Runner-provided facts: ${canonicalJson(promptFacts)}`,
|
|
1149
|
+
`Available reviewer roles: ${canonicalJson(attempt.reviewers ?? [])}`,
|
|
1097
1150
|
'This is a read-only, report-only operation. Do not edit files or external state, use network or MCP tools, or request additional authority.',
|
|
1098
1151
|
'Do not read .env or any .env* file. The runner has removed repository credential paths from this read view.',
|
|
1099
1152
|
].join('\n'),
|
|
@@ -1101,24 +1154,30 @@ export function createV2Runtime(input) {
|
|
|
1101
1154
|
idleTimeoutMs: config.codex.idleTimeoutMs,
|
|
1102
1155
|
operationPolicy: attempt.policy,
|
|
1103
1156
|
executionProfile: attempt.profile,
|
|
1104
|
-
|
|
1157
|
+
agentProfilePaths: attempt.reviewerProfiles,
|
|
1158
|
+
onSpawned: async ({ pid, processGroupId }) => onLaunched?.({ pid, processGroupId }),
|
|
1105
1159
|
}, signal);
|
|
1106
1160
|
}
|
|
1107
1161
|
catch (error) {
|
|
1108
1162
|
if (error instanceof ProcessQuiescenceError) {
|
|
1109
1163
|
cleanupAfterSafeHalt = true;
|
|
1110
|
-
return {
|
|
1164
|
+
return {
|
|
1165
|
+
status: 'safe-halt',
|
|
1166
|
+
pid: error.pid,
|
|
1167
|
+
processGroupId: error.processGroupId,
|
|
1168
|
+
startedAt: now(),
|
|
1169
|
+
};
|
|
1111
1170
|
}
|
|
1112
1171
|
throw error;
|
|
1113
1172
|
}
|
|
1114
1173
|
finally {
|
|
1115
|
-
if (
|
|
1174
|
+
if (!cleanupAfterSafeHalt)
|
|
1116
1175
|
await rm(readView, { recursive: true, force: true });
|
|
1117
1176
|
}
|
|
1118
1177
|
if (result.kind === 'cancelled')
|
|
1119
1178
|
return { status: 'cancelled' };
|
|
1120
1179
|
if (result.kind === 'launch-gate-failed') {
|
|
1121
|
-
return { status: '
|
|
1180
|
+
return { status: 'retryable', code: 'review-operation-launch-gate-failed' };
|
|
1122
1181
|
}
|
|
1123
1182
|
if (['spawn-failed', 'transport-failed', 'timeout', 'idle-timeout'].includes(result.kind)) {
|
|
1124
1183
|
return { status: 'retryable', code: `report-operation-${result.kind}` };
|
|
@@ -1129,104 +1188,9 @@ export function createV2Runtime(input) {
|
|
|
1129
1188
|
return { status: 'completed', reportBytes: result.report.bytes };
|
|
1130
1189
|
},
|
|
1131
1190
|
});
|
|
1132
|
-
const proofAgent = input.proofAgent ?? new ContainedProofAgent({
|
|
1133
|
-
config: () => requireConfig(currentConfig), targetRoot, operation: reportOperation,
|
|
1134
|
-
});
|
|
1135
1191
|
const implementationReviewer = new ContainedImplementationReviewer({
|
|
1136
1192
|
operation: reportOperation,
|
|
1137
1193
|
});
|
|
1138
|
-
const specOperation = {
|
|
1139
|
-
author: async ({ context, state, mode, authorSessionId: sessionId, signal, invocationState }) => {
|
|
1140
|
-
let completedAttemptId;
|
|
1141
|
-
try {
|
|
1142
|
-
const result = await reportOperation.run({
|
|
1143
|
-
operation: 'spec-author', runId: context.runId, worktreePath: context.worktreePath,
|
|
1144
|
-
workflowGeneration: context.workflowGeneration, signal, invocationState,
|
|
1145
|
-
promptFacts: [
|
|
1146
|
-
String(state.revisions.length + 1),
|
|
1147
|
-
`Author session ID: ${sessionId}. Author mode: ${mode}.`,
|
|
1148
|
-
`Issue authority and frozen criteria: ${canonicalJson({ issue: context.issue, frozenCriteria: context.frozenCriteria })}.`,
|
|
1149
|
-
`Prior revisions and review state: ${canonicalJson({ revisions: state.revisions, review: state.review })}.`,
|
|
1150
|
-
],
|
|
1151
|
-
});
|
|
1152
|
-
if (result.status === 'cancelled')
|
|
1153
|
-
return { status: 'cancelled' };
|
|
1154
|
-
if (result.status === 'retryable' || result.status === 'safe-halt')
|
|
1155
|
-
return { status: 'retryable', code: result.code };
|
|
1156
|
-
if (result.status === 'blocked')
|
|
1157
|
-
return { status: 'blocked', kind: result.kind, code: result.code };
|
|
1158
|
-
completedAttemptId = result.attemptId;
|
|
1159
|
-
const invocation = await invocationState.read();
|
|
1160
|
-
if (!invocation || invocation.operation !== 'spec-author' || invocation.attemptId !== result.attemptId) {
|
|
1161
|
-
return { status: 'blocked', kind: 'safety', code: 'spec-author-invocation-correlation-missing' };
|
|
1162
|
-
}
|
|
1163
|
-
const revisionPath = join(dirname(invocation.reportPath), `revision-${state.revisions.length + 1}.md`);
|
|
1164
|
-
const report = decodeAgentReportForValidation(result.reportBytes);
|
|
1165
|
-
if (report.status !== 'ready' || report.specPath !== revisionPath || report.specSha256 === null) {
|
|
1166
|
-
return { status: 'invalid', code: 'spec-author-report-invalid', attemptId: result.attemptId };
|
|
1167
|
-
}
|
|
1168
|
-
const content = await readRegularFile(revisionPath);
|
|
1169
|
-
if (report.specSha256 !== sha256(content))
|
|
1170
|
-
return { status: 'invalid', code: 'spec-author-report-invalid', attemptId: result.attemptId };
|
|
1171
|
-
const previous = state.revisions.at(-1) ?? null;
|
|
1172
|
-
return { status: 'completed', value: createSpecRevision({
|
|
1173
|
-
revision: state.revisions.length + 1, path: revisionPath, content: content.toString('utf8'),
|
|
1174
|
-
evidence: [{ path: context.issue.url, sha256: sha256(canonicalJson(context.issue)), description: 'Frozen issue authority' }],
|
|
1175
|
-
author: { attemptId: result.attemptId, sessionId }, previousRevision: previous,
|
|
1176
|
-
}) };
|
|
1177
|
-
}
|
|
1178
|
-
catch (error) {
|
|
1179
|
-
return completedAttemptId
|
|
1180
|
-
? { status: 'invalid', code: 'spec-author-report-invalid', attemptId: completedAttemptId }
|
|
1181
|
-
: { status: 'retryable', code: 'spec-author-report-invalid' };
|
|
1182
|
-
}
|
|
1183
|
-
},
|
|
1184
|
-
review: async ({ context, state, mode, reviewerSessionId: sessionId, signal, invocationState }) => {
|
|
1185
|
-
let completedAttemptId;
|
|
1186
|
-
try {
|
|
1187
|
-
const result = await reportOperation.run({
|
|
1188
|
-
operation: 'spec-review', runId: context.runId, worktreePath: context.worktreePath,
|
|
1189
|
-
workflowGeneration: context.workflowGeneration, signal, invocationState,
|
|
1190
|
-
forbiddenAttemptIds: state.revisions.map((revision) => revision.author.attemptId),
|
|
1191
|
-
promptFacts: [
|
|
1192
|
-
`Reviewer session ID: ${sessionId}. Review mode: ${mode}.`,
|
|
1193
|
-
`Issue authority and frozen criteria: ${canonicalJson({ issue: context.issue, frozenCriteria: context.frozenCriteria })}.`,
|
|
1194
|
-
`Immutable spec delivery state: ${canonicalJson(state)}.`,
|
|
1195
|
-
],
|
|
1196
|
-
});
|
|
1197
|
-
if (result.status === 'cancelled')
|
|
1198
|
-
return { status: 'cancelled' };
|
|
1199
|
-
if (result.status === 'retryable' || result.status === 'safe-halt')
|
|
1200
|
-
return { status: 'retryable', code: result.code };
|
|
1201
|
-
if (result.status === 'blocked')
|
|
1202
|
-
return { status: 'blocked', kind: result.kind, code: result.code };
|
|
1203
|
-
completedAttemptId = result.attemptId;
|
|
1204
|
-
const raw = decodeAgentReportForValidation(result.reportBytes);
|
|
1205
|
-
if (raw.mode !== mode || raw.reviewerSessionId !== sessionId || !Array.isArray(raw.coverage) || !Array.isArray(raw.defects)
|
|
1206
|
-
|| !Array.isArray(raw.affectedDefectIds) || !Array.isArray(raw.affectedContracts) || !Array.isArray(raw.acceptedRisks)
|
|
1207
|
-
|| typeof raw.coverageInvalidated !== 'boolean'
|
|
1208
|
-
|| !['approved', 'needs-work', 'rejected'].includes(raw.verdict))
|
|
1209
|
-
return { status: 'invalid', code: 'spec-review-report-invalid', attemptId: result.attemptId };
|
|
1210
|
-
const target = state.revisions.at(-1);
|
|
1211
|
-
const defects = validateCodeReviewDefects(raw.defects, target.revision);
|
|
1212
|
-
const report = {
|
|
1213
|
-
version: 1, targetRevision: target.revision, targetSha256: target.revisionSha256, mode,
|
|
1214
|
-
verdict: raw.verdict, reviewer: { attemptId: result.attemptId, sessionId },
|
|
1215
|
-
coverage: raw.coverage, defects,
|
|
1216
|
-
affectedDefectIds: raw.affectedDefectIds,
|
|
1217
|
-
affectedContracts: raw.affectedContracts,
|
|
1218
|
-
closureRequestSha256: mode === 'closure' ? state.review.closureRequestSha256 : null,
|
|
1219
|
-
acceptedRisks: [], coverageInvalidated: raw.coverageInvalidated,
|
|
1220
|
-
};
|
|
1221
|
-
return { status: 'completed', value: report, reportSha256: result.reportSha256 };
|
|
1222
|
-
}
|
|
1223
|
-
catch {
|
|
1224
|
-
return completedAttemptId
|
|
1225
|
-
? { status: 'invalid', code: 'spec-review-report-invalid', attemptId: completedAttemptId }
|
|
1226
|
-
: { status: 'retryable', code: 'spec-review-report-invalid' };
|
|
1227
|
-
}
|
|
1228
|
-
},
|
|
1229
|
-
};
|
|
1230
1194
|
const readConfig = async (requestedRoot) => {
|
|
1231
1195
|
if (resolve(requestedRoot) !== targetRoot)
|
|
1232
1196
|
throw new Error('runtime target root mismatch');
|
|
@@ -1237,31 +1201,17 @@ export function createV2Runtime(input) {
|
|
|
1237
1201
|
codex: { ...parsed.codex, command: await resolveRuntimeCodex(parsed.codex.command) },
|
|
1238
1202
|
};
|
|
1239
1203
|
const path = join(targetRoot, config.runner.stateDir, 'v2', 'run-state.json');
|
|
1240
|
-
if (!runRecords)
|
|
1241
|
-
|
|
1242
|
-
if (!processStartIdentity)
|
|
1243
|
-
throw new Error('run-store owner process identity is unavailable');
|
|
1244
|
-
runRecords = new FileRunRecordWriter(path, {
|
|
1245
|
-
host: hostname(), bootId: input.bootId, pid: process.pid, processStartIdentity,
|
|
1246
|
-
inspectProcessIdentity: async (pid) => {
|
|
1247
|
-
const identity = await readProcessStartIdentity(pid);
|
|
1248
|
-
if (identity)
|
|
1249
|
-
return { processStartIdentity: identity };
|
|
1250
|
-
try {
|
|
1251
|
-
process.kill(pid, 0);
|
|
1252
|
-
return 'unknown';
|
|
1253
|
-
}
|
|
1254
|
-
catch (error) {
|
|
1255
|
-
return isErrorCode(error, 'ESRCH') ? 'absent' : 'unknown';
|
|
1256
|
-
}
|
|
1257
|
-
},
|
|
1258
|
-
exclusiveRepositoryOwnership: true,
|
|
1259
|
-
});
|
|
1260
|
-
}
|
|
1204
|
+
if (!runRecords)
|
|
1205
|
+
runRecords = new FileRunRecordWriter(path);
|
|
1261
1206
|
currentConfig = config;
|
|
1262
1207
|
return { bytes, config };
|
|
1263
1208
|
};
|
|
1264
1209
|
const records = {
|
|
1210
|
+
inspect: async () => {
|
|
1211
|
+
if (!runRecords)
|
|
1212
|
+
throw new Error('run store used before config');
|
|
1213
|
+
return runRecords.inspect();
|
|
1214
|
+
},
|
|
1265
1215
|
read: async () => {
|
|
1266
1216
|
if (!runRecords)
|
|
1267
1217
|
throw new Error('run store used before config');
|
|
@@ -1272,11 +1222,6 @@ export function createV2Runtime(input) {
|
|
|
1272
1222
|
throw new Error('run store used before config');
|
|
1273
1223
|
return runRecords.compareAndSwap(generation, next);
|
|
1274
1224
|
},
|
|
1275
|
-
markPublicationEffectPossible: async () => {
|
|
1276
|
-
if (!runRecords?.markPublicationEffectPossible)
|
|
1277
|
-
return;
|
|
1278
|
-
await runRecords.markPublicationEffectPossible();
|
|
1279
|
-
},
|
|
1280
1225
|
};
|
|
1281
1226
|
const capabilities = createCheckedChangeCapabilities();
|
|
1282
1227
|
const proof = {
|
|
@@ -1284,27 +1229,8 @@ export function createV2Runtime(input) {
|
|
|
1284
1229
|
const config = requireConfig(currentConfig);
|
|
1285
1230
|
const checked = capabilities.verifyAndRead(proofInput.checkedChange);
|
|
1286
1231
|
const repoKey = sha256(checked.payload.canonicalRepository);
|
|
1287
|
-
const ownerProcessIdentity = await readProcessStartIdentity(process.pid);
|
|
1288
|
-
if (!ownerProcessIdentity)
|
|
1289
|
-
throw new Error('proof-store owner process identity is unavailable');
|
|
1290
|
-
const proofRecords = new FileProofRecordWriter(join(orchestratorHome, 'v2', repoKey, 'proofs'), {
|
|
1291
|
-
host: hostname(), bootId: input.bootId, pid: process.pid, processStartIdentity: ownerProcessIdentity,
|
|
1292
|
-
inspectProcessIdentity: async (pid) => {
|
|
1293
|
-
const identity = await readProcessStartIdentity(pid);
|
|
1294
|
-
if (identity)
|
|
1295
|
-
return { processStartIdentity: identity };
|
|
1296
|
-
try {
|
|
1297
|
-
process.kill(pid, 0);
|
|
1298
|
-
return 'unknown';
|
|
1299
|
-
}
|
|
1300
|
-
catch (error) {
|
|
1301
|
-
return isErrorCode(error, 'ESRCH') ? 'absent' : 'unknown';
|
|
1302
|
-
}
|
|
1303
|
-
},
|
|
1304
|
-
exclusiveRepositoryOwnership: true,
|
|
1305
|
-
});
|
|
1306
1232
|
const issueWorktreePath = resolve(targetRoot, config.runner.workspaceRoot, `issue-${checked.payload.issueNumber}`);
|
|
1307
|
-
const worktreePath = proofInput.
|
|
1233
|
+
const worktreePath = proofInput.materialization?.path ?? issueWorktreePath;
|
|
1308
1234
|
const androidLease = new FileAndroidLeaseVerifier({
|
|
1309
1235
|
leaseRoot: join(orchestratorHome, 'v2', repoKey, 'leases'),
|
|
1310
1236
|
worktreeRoot: worktreePath,
|
|
@@ -1323,10 +1249,9 @@ export function createV2Runtime(input) {
|
|
|
1323
1249
|
});
|
|
1324
1250
|
const acceptanceProof = new AcceptanceProof({
|
|
1325
1251
|
checkedChangeReader: capabilities,
|
|
1326
|
-
proofRecords,
|
|
1327
1252
|
proofAgent,
|
|
1328
|
-
inspectFreshness: async (payload,
|
|
1329
|
-
const checkPolicySha256 =
|
|
1253
|
+
inspectFreshness: async (payload, materialization) => {
|
|
1254
|
+
const checkPolicySha256 = selectedConfiguredCheckPolicySha256(payload.checks, resolveIssueCheckPolicy(proofInput.issue.body, config.checks).checks);
|
|
1330
1255
|
if (payload.version === 1)
|
|
1331
1256
|
return {
|
|
1332
1257
|
...await (proofFreshnessGit.snapshotIgnoringUntrackedRoot
|
|
@@ -1334,11 +1259,11 @@ export function createV2Runtime(input) {
|
|
|
1334
1259
|
: git.snapshot(worktreePath)),
|
|
1335
1260
|
checkPolicySha256,
|
|
1336
1261
|
};
|
|
1337
|
-
if (!
|
|
1262
|
+
if (!materialization || !git.candidateV2)
|
|
1338
1263
|
throw new CandidateProofInspectionError('candidate-git-v2-required');
|
|
1339
|
-
const inspection = await git.candidateV2.
|
|
1264
|
+
const inspection = await git.candidateV2.inspectMaterialization({
|
|
1340
1265
|
binding: payload.binding,
|
|
1341
|
-
|
|
1266
|
+
materialization,
|
|
1342
1267
|
artifactDir: config.proof.artifactDir,
|
|
1343
1268
|
});
|
|
1344
1269
|
if (inspection.kind === 'failed')
|
|
@@ -1352,7 +1277,6 @@ export function createV2Runtime(input) {
|
|
|
1352
1277
|
androidLease,
|
|
1353
1278
|
iosLease,
|
|
1354
1279
|
proofArtifactDir: config.proof.artifactDir,
|
|
1355
|
-
now,
|
|
1356
1280
|
signal: controller.signal,
|
|
1357
1281
|
});
|
|
1358
1282
|
const androidRelevant = !!config.proof.android && isAndroidProofRelevant(proofInput.issue, proofInput.frozenCriteria, checked.payload.changedFiles);
|
|
@@ -1360,22 +1284,11 @@ export function createV2Runtime(input) {
|
|
|
1360
1284
|
const runnerPreparedArtifactSha256 = {};
|
|
1361
1285
|
const runnerPreparationWarnings = [];
|
|
1362
1286
|
let preparationAttempted = false;
|
|
1363
|
-
if (!proofInput.workflowGeneration)
|
|
1364
|
-
throw new Error('proof workflow generation is required');
|
|
1365
|
-
const iosTooling = await discoverIosTooling(commandExecutor, iosXcrunPath);
|
|
1366
|
-
const iosProofInputs = {
|
|
1367
|
-
helperPath: join(proofInput.workflowGeneration.generationRoot, 'skills', 'acceptance-proof', 'tools', 'ios-lease.mjs'),
|
|
1368
|
-
leaseRoot: join(orchestratorHome, 'v2', repoKey, 'leases'),
|
|
1369
|
-
leaseArtifactPath: resolve(worktreePath, config.proof.artifactDir, proofInput.proofId, 'ios-lease.json'),
|
|
1370
|
-
proofId: proofInput.proofId, ownerPid: process.pid, xcrunPath: iosXcrunPath,
|
|
1371
|
-
runtimeId: iosTooling?.runtimeId ?? null, deviceTypeId: iosTooling?.deviceTypeId ?? null,
|
|
1372
|
-
};
|
|
1373
1287
|
const result = await acceptanceProof.proveChange({
|
|
1374
1288
|
...proofInput,
|
|
1375
1289
|
runnerPreparedArtifactPaths,
|
|
1376
1290
|
runnerPreparedArtifactSha256,
|
|
1377
1291
|
runnerPreparationWarnings,
|
|
1378
|
-
iosProofInputs,
|
|
1379
1292
|
beforeAgentLaunch: async () => {
|
|
1380
1293
|
await proofInput.beforeAgentLaunch?.();
|
|
1381
1294
|
if (!androidRelevant || preparationAttempted)
|
|
@@ -1401,10 +1314,10 @@ export function createV2Runtime(input) {
|
|
|
1401
1314
|
}
|
|
1402
1315
|
},
|
|
1403
1316
|
});
|
|
1404
|
-
if (checked.payload.version === 2 && proofInput.
|
|
1317
|
+
if (checked.payload.version === 2 && proofInput.materialization && git.candidateV2 && result.status === 'passed') {
|
|
1405
1318
|
const artifacts = result.receipt.publishableEvidence.map((artifact) => ({ relativePath: artifact.ref, sha256: artifact.sha256 }));
|
|
1406
1319
|
const copied = await git.candidateV2.copyProofArtifacts({
|
|
1407
|
-
|
|
1320
|
+
materialization: proofInput.materialization,
|
|
1408
1321
|
issueWorktreePath,
|
|
1409
1322
|
artifactDir: config.proof.artifactDir,
|
|
1410
1323
|
proofId: proofInput.proofId,
|
|
@@ -1449,6 +1362,8 @@ export function createV2Runtime(input) {
|
|
|
1449
1362
|
id: comment.id,
|
|
1450
1363
|
body: comment.body,
|
|
1451
1364
|
authorAssociation: comment.authorAssociation,
|
|
1365
|
+
author: comment.author.login,
|
|
1366
|
+
authorId: comment.author.id,
|
|
1452
1367
|
createdAt: comment.createdAt,
|
|
1453
1368
|
updatedAt: comment.updatedAt,
|
|
1454
1369
|
})),
|
|
@@ -1461,9 +1376,10 @@ export function createV2Runtime(input) {
|
|
|
1461
1376
|
removeLabels: current.filter((label) => !labels.includes(label)),
|
|
1462
1377
|
});
|
|
1463
1378
|
},
|
|
1379
|
+
getRepositoryPermission: (login, expectedUserId) => input.issues.getRepositoryPermission(login, expectedUserId),
|
|
1464
1380
|
transitionToBlocked: async (issueNumber, labels) => {
|
|
1465
1381
|
const current = await input.issues.getLabels(issueNumber);
|
|
1466
|
-
if (current.includes(labels.review)
|
|
1382
|
+
if (current.includes(labels.review))
|
|
1467
1383
|
return;
|
|
1468
1384
|
const hasRunnerStatus = current.some((label) => (label === labels.auto || label === labels.running || label === labels.blocked));
|
|
1469
1385
|
if (!hasRunnerStatus)
|
|
@@ -1480,16 +1396,17 @@ export function createV2Runtime(input) {
|
|
|
1480
1396
|
const matches = (await input.pullRequests.listAllByHeadBranch(headBranch))
|
|
1481
1397
|
.filter((pullRequest) => pullRequest.state === 'OPEN' && pullRequest.baseRefName === baseBranch);
|
|
1482
1398
|
if (matches.length > 1)
|
|
1483
|
-
throw new Error('multiple open pull requests match publication
|
|
1399
|
+
throw new Error('multiple open pull requests match publication pendingEffect');
|
|
1484
1400
|
const match = matches[0];
|
|
1485
1401
|
if (!match)
|
|
1486
1402
|
return undefined;
|
|
1403
|
+
const reviewTarget = await input.pullRequests.getReviewTarget(match.number);
|
|
1487
1404
|
return {
|
|
1488
1405
|
url: match.url,
|
|
1489
1406
|
body: match.body,
|
|
1490
1407
|
number: match.number,
|
|
1491
1408
|
nodeId: match.nodeId,
|
|
1492
|
-
...(
|
|
1409
|
+
...(reviewTarget ? { headSha: reviewTarget.headRefOid } : {}),
|
|
1493
1410
|
};
|
|
1494
1411
|
},
|
|
1495
1412
|
createDraft: async ({ title, body, headBranch, baseBranch }) => input.pullRequests.createDraftPullRequest({ title, body, headBranch, baseBranch }),
|
|
@@ -1500,54 +1417,10 @@ export function createV2Runtime(input) {
|
|
|
1500
1417
|
return { id: comment.id, body: comment.body };
|
|
1501
1418
|
},
|
|
1502
1419
|
},
|
|
1503
|
-
reviewFeedback: new
|
|
1420
|
+
reviewFeedback: new ReviewFeedbackObserver({ pullRequests: input.pullRequests, issues: input.issues, now }),
|
|
1504
1421
|
git,
|
|
1505
|
-
routeCoordinator: {
|
|
1506
|
-
run: ({ state, ...routeInput }) => new RouteCoordinator({
|
|
1507
|
-
state,
|
|
1508
|
-
operation: reportOperation,
|
|
1509
|
-
now,
|
|
1510
|
-
createReceipt: ({ artifact, triage, review, decidedAt }) => {
|
|
1511
|
-
if (artifact.status === 'blocked')
|
|
1512
|
-
throw new Error('blocked triage cannot create a route receipt');
|
|
1513
|
-
const receipt = {
|
|
1514
|
-
version: 1,
|
|
1515
|
-
route: artifact.status,
|
|
1516
|
-
triage,
|
|
1517
|
-
review,
|
|
1518
|
-
artifact,
|
|
1519
|
-
decisionSha256: '',
|
|
1520
|
-
decidedAt,
|
|
1521
|
-
assumptions: [...artifact.assumptions],
|
|
1522
|
-
};
|
|
1523
|
-
receipt.decisionSha256 = hashRouteDecision(receipt);
|
|
1524
|
-
return validateRouteReceipt(receipt, triage.generationHash);
|
|
1525
|
-
},
|
|
1526
|
-
}).run(routeInput),
|
|
1527
|
-
},
|
|
1528
|
-
routeContinuations: {
|
|
1529
|
-
direct: async () => ({ status: 'completed' }),
|
|
1530
|
-
specRequired: (context, state, signal) => new SpecCoordinator({
|
|
1531
|
-
state, operation: specOperation, createAuthorSessionId: randomUUID, createReviewerSessionId: randomUUID,
|
|
1532
|
-
}).run(context, signal),
|
|
1533
|
-
awaitingUser: async (context, state, signal) => {
|
|
1534
|
-
const config = requireConfig(currentConfig);
|
|
1535
|
-
return new WaitingHumanCoordinator({
|
|
1536
|
-
issues: input.issues,
|
|
1537
|
-
labels: {
|
|
1538
|
-
auto: config.github.labels.auto.name,
|
|
1539
|
-
running: config.github.labels.running.name,
|
|
1540
|
-
blocked: config.github.labels.blocked.name,
|
|
1541
|
-
review: config.github.labels.review.name,
|
|
1542
|
-
waitingHuman: config.github.labels.waitingHuman.name,
|
|
1543
|
-
},
|
|
1544
|
-
now,
|
|
1545
|
-
}).run(context, state, signal);
|
|
1546
|
-
},
|
|
1547
|
-
},
|
|
1548
1422
|
implementationAgent,
|
|
1549
1423
|
implementationReviewer,
|
|
1550
|
-
waitForReviewProcessAbsence: waitForProcessGroupAbsent,
|
|
1551
1424
|
checks: {
|
|
1552
1425
|
supportsLaunchOwnership: true,
|
|
1553
1426
|
run: async ({ source, command, cwd, signal, onLaunched }) => {
|
|
@@ -1566,12 +1439,56 @@ export function createV2Runtime(input) {
|
|
|
1566
1439
|
await writeDurableAtomicFile(resolve(targetRoot, relativePath), `${canonicalJson({ version: 1, runId, code, summary, recordedAt: now() })}\n`);
|
|
1567
1440
|
return { id: `evidence:${runId}:${code}`, path: relativePath };
|
|
1568
1441
|
},
|
|
1442
|
+
outcomeEvidencePath: (runId, code, summarySha256) => `${requireConfig(currentConfig).runner.stateDir}/v2/evidence/${runId}/${sha256(code)}-${summarySha256}.json`,
|
|
1443
|
+
inspectOutcomeEvidence: async (path) => {
|
|
1444
|
+
try {
|
|
1445
|
+
return { sha256: sha256(await readRegularFile(resolve(targetRoot, path))) };
|
|
1446
|
+
}
|
|
1447
|
+
catch (error) {
|
|
1448
|
+
if (isErrorCode(error, 'ENOENT'))
|
|
1449
|
+
return undefined;
|
|
1450
|
+
throw error;
|
|
1451
|
+
}
|
|
1452
|
+
},
|
|
1453
|
+
writeOutcomeEvidence: ({ path, bytes, sha256: expectedSha256 }) => writeExactDurableBytes(resolve(targetRoot, path), bytes, expectedSha256),
|
|
1569
1454
|
packageVersion: input.packageVersion,
|
|
1570
1455
|
createWorkflowGeneration: input.createWorkflowGeneration,
|
|
1571
1456
|
verifyWorkflowGeneration,
|
|
1572
1457
|
createRunId: input.createRunId ?? randomUUID,
|
|
1573
1458
|
createProofId: input.createProofId ?? randomUUID,
|
|
1574
1459
|
createReviewSessionId: input.createAttemptId ?? randomUUID,
|
|
1460
|
+
processIdentity: {
|
|
1461
|
+
host: hostname(),
|
|
1462
|
+
bootId: input.bootId,
|
|
1463
|
+
capture: async (pid, processGroupId) => {
|
|
1464
|
+
const captured = await captureProcessStartIdentity({ platform: process.platform, pid, processGroupId });
|
|
1465
|
+
return captured.status === 'available' ? captured.identity : undefined;
|
|
1466
|
+
},
|
|
1467
|
+
observe: async (identity) => ({
|
|
1468
|
+
leader: await observeProcessIdentity({ platform: process.platform, ...identity }),
|
|
1469
|
+
group: observeProcessGroup(identity.processGroupId),
|
|
1470
|
+
}),
|
|
1471
|
+
},
|
|
1472
|
+
inspectAttemptResult: async (path) => {
|
|
1473
|
+
try {
|
|
1474
|
+
const bytes = await readRegularFile(path);
|
|
1475
|
+
return { bytes, sha256: sha256(bytes) };
|
|
1476
|
+
}
|
|
1477
|
+
catch (error) {
|
|
1478
|
+
if (isErrorCode(error, 'ENOENT'))
|
|
1479
|
+
return undefined;
|
|
1480
|
+
throw error;
|
|
1481
|
+
}
|
|
1482
|
+
},
|
|
1483
|
+
observeAttemptCleanup: async (attempt) => {
|
|
1484
|
+
return observeAttemptReadViewCleanup({
|
|
1485
|
+
orchestratorHome,
|
|
1486
|
+
canonicalRepository: requireCanonicalRepository(currentConfig),
|
|
1487
|
+
identity: attempt,
|
|
1488
|
+
});
|
|
1489
|
+
},
|
|
1490
|
+
writeAttemptResult: ({ path, bytes, sha256: expectedSha256 }) => writeExactDurableBytes(path, bytes, expectedSha256),
|
|
1491
|
+
attemptResultPath: ({ canonicalRepository, runId, attemptId }) => join(resolve(orchestratorHome), 'v2', sha256(canonicalRepository), 'runs', runId, 'attempts', attemptId, 'report.json'),
|
|
1575
1492
|
now,
|
|
1576
1493
|
signal: controller.signal,
|
|
1577
1494
|
});
|
|
@@ -1624,6 +1541,24 @@ async function readRegularFile(path) {
|
|
|
1624
1541
|
await handle.close();
|
|
1625
1542
|
}
|
|
1626
1543
|
}
|
|
1544
|
+
async function writeExactDurableBytes(path, bytes, expectedSha256) {
|
|
1545
|
+
if (sha256(bytes) !== expectedSha256)
|
|
1546
|
+
throw new Error('durable result bytes do not match expected SHA-256');
|
|
1547
|
+
try {
|
|
1548
|
+
const existing = await readRegularFile(path);
|
|
1549
|
+
if (sha256(existing) !== expectedSha256)
|
|
1550
|
+
throw new Error('durable result path already contains different bytes');
|
|
1551
|
+
return;
|
|
1552
|
+
}
|
|
1553
|
+
catch (error) {
|
|
1554
|
+
if (!isErrorCode(error, 'ENOENT'))
|
|
1555
|
+
throw error;
|
|
1556
|
+
}
|
|
1557
|
+
await writeDurableAtomicFile(path, bytes, 0o600);
|
|
1558
|
+
const observed = await readRegularFile(path);
|
|
1559
|
+
if (sha256(observed) !== expectedSha256)
|
|
1560
|
+
throw new Error('durable result write postcondition failed');
|
|
1561
|
+
}
|
|
1627
1562
|
async function inspectRegularFile(path) {
|
|
1628
1563
|
const handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
1629
1564
|
try {
|
|
@@ -1735,6 +1670,10 @@ async function runSpawnCheck(file, args, cwd, signal, timeoutMs, onLaunched) {
|
|
|
1735
1670
|
status: code === 0 ? 'passed' : 'failed',
|
|
1736
1671
|
output: Buffer.concat(chunks),
|
|
1737
1672
|
outputSha256: outputHash.digest('hex'),
|
|
1673
|
+
observation: {
|
|
1674
|
+
leader: 'absent',
|
|
1675
|
+
group: child.pid ? observeProcessGroup(child.pid) : 'unknown',
|
|
1676
|
+
},
|
|
1738
1677
|
});
|
|
1739
1678
|
})().catch(rejectCheck);
|
|
1740
1679
|
});
|
|
@@ -1845,8 +1784,7 @@ async function prepareContainedAttempt(input) {
|
|
|
1845
1784
|
operation: input.operationId,
|
|
1846
1785
|
bootId: input.bootId,
|
|
1847
1786
|
});
|
|
1848
|
-
const reportOnly = input.operationId === '
|
|
1849
|
-
|| input.operationId === 'code-review' || input.operationId === 'spec-review';
|
|
1787
|
+
const reportOnly = input.operationId === 'code-review';
|
|
1850
1788
|
if ((reportOnly
|
|
1851
1789
|
? snapshot.policy.sandboxMode !== 'read-only'
|
|
1852
1790
|
|| snapshot.policy.worktreeAccess !== 'read-only'
|
|
@@ -1882,6 +1820,8 @@ async function prepareContainedAttempt(input) {
|
|
|
1882
1820
|
tmpDir,
|
|
1883
1821
|
policy: structuredClone(snapshot.policy),
|
|
1884
1822
|
profile,
|
|
1823
|
+
reviewers: Object.keys(snapshot.reviewerProfiles).sort(),
|
|
1824
|
+
reviewerProfiles: structuredClone(snapshot.reviewerProfiles),
|
|
1885
1825
|
};
|
|
1886
1826
|
}
|
|
1887
1827
|
async function artifactInventory(root, logicalRoot) {
|
|
@@ -1921,9 +1861,6 @@ function changedArtifactPaths(before, after) {
|
|
|
1921
1861
|
.filter((path) => before.get(path) !== after.get(path))
|
|
1922
1862
|
.sort();
|
|
1923
1863
|
}
|
|
1924
|
-
function artifactInventoryMatches(actual, expected) {
|
|
1925
|
-
return canonicalJson(Object.fromEntries([...actual.entries()].sort())) === canonicalJson(expected ?? null);
|
|
1926
|
-
}
|
|
1927
1864
|
async function fingerprintRepositoryPath(root, relativePath) {
|
|
1928
1865
|
const segments = relativePath.split('/');
|
|
1929
1866
|
let current = root;
|
|
@@ -1960,63 +1897,6 @@ async function fingerprintDeniedEntry(path, existingInfo) {
|
|
|
1960
1897
|
}
|
|
1961
1898
|
return { kind: 'directory', entries };
|
|
1962
1899
|
}
|
|
1963
|
-
async function waitForProcessGroupAbsent(processGroupId) {
|
|
1964
|
-
while (true) {
|
|
1965
|
-
try {
|
|
1966
|
-
process.kill(-processGroupId, 0);
|
|
1967
|
-
}
|
|
1968
|
-
catch (error) {
|
|
1969
|
-
if (isErrorCode(error, 'ESRCH'))
|
|
1970
|
-
return;
|
|
1971
|
-
if (!isErrorCode(error, 'EPERM'))
|
|
1972
|
-
throw error;
|
|
1973
|
-
}
|
|
1974
|
-
await new Promise((resolveWait) => setTimeout(resolveWait, 1_000));
|
|
1975
|
-
}
|
|
1976
|
-
}
|
|
1977
|
-
async function readProcessStartIdentity(pid) {
|
|
1978
|
-
if (!Number.isSafeInteger(pid) || pid <= 0)
|
|
1979
|
-
return undefined;
|
|
1980
|
-
if (platform() === 'linux') {
|
|
1981
|
-
try {
|
|
1982
|
-
const text = await readFile(`/proc/${pid}/stat`, 'utf8');
|
|
1983
|
-
const close = text.lastIndexOf(') ');
|
|
1984
|
-
const fields = close < 0 ? [] : text.slice(close + 2).trim().split(/\s+/u);
|
|
1985
|
-
return fields[19] ? `linux:${fields[19]}` : undefined;
|
|
1986
|
-
}
|
|
1987
|
-
catch {
|
|
1988
|
-
return undefined;
|
|
1989
|
-
}
|
|
1990
|
-
}
|
|
1991
|
-
return await new Promise((resolveIdentity) => {
|
|
1992
|
-
execFile('/bin/ps', ['-o', 'lstart=', '-p', String(pid)], { encoding: 'utf8' }, (error, stdout) => {
|
|
1993
|
-
const value = error ? '' : stdout.trim().replace(/\s+/gu, ' ');
|
|
1994
|
-
resolveIdentity(value ? `${platform()}:${value}` : undefined);
|
|
1995
|
-
});
|
|
1996
|
-
});
|
|
1997
|
-
}
|
|
1998
|
-
async function inspectReportProcess(invocation) {
|
|
1999
|
-
const processStartIdentity = await readProcessStartIdentity(invocation.pid);
|
|
2000
|
-
let processGroupAlive;
|
|
2001
|
-
try {
|
|
2002
|
-
process.kill(-invocation.processGroupId, 0);
|
|
2003
|
-
processGroupAlive = true;
|
|
2004
|
-
}
|
|
2005
|
-
catch (error) {
|
|
2006
|
-
if (isErrorCode(error, 'ESRCH'))
|
|
2007
|
-
processGroupAlive = false;
|
|
2008
|
-
else if (isErrorCode(error, 'EPERM'))
|
|
2009
|
-
processGroupAlive = true;
|
|
2010
|
-
else
|
|
2011
|
-
processGroupAlive = 'unknown';
|
|
2012
|
-
}
|
|
2013
|
-
if (!processStartIdentity) {
|
|
2014
|
-
return processGroupAlive === false
|
|
2015
|
-
? { status: 'absent', processGroupAlive: false }
|
|
2016
|
-
: { status: 'unknown' };
|
|
2017
|
-
}
|
|
2018
|
-
return { status: 'present', processStartIdentity, processGroupAlive };
|
|
2019
|
-
}
|
|
2020
1900
|
export async function materializeReportReadView(input) {
|
|
2021
1901
|
await assertDisjointReadViewPaths(input.worktreePath, input.destination);
|
|
2022
1902
|
const deniedPaths = normalizeReadViewDeniedPaths(input.deniedPaths);
|