codex-orchestrator 2.0.11 → 2.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/README.md +25 -51
- package/dist/src/index.d.ts +2 -8
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -4
- package/dist/src/index.js.map +1 -1
- package/dist/src/v2/acceptance-proof.d.ts +46 -31
- package/dist/src/v2/acceptance-proof.d.ts.map +1 -1
- package/dist/src/v2/acceptance-proof.js +157 -195
- package/dist/src/v2/acceptance-proof.js.map +1 -1
- package/dist/src/v2/active-attempt.d.ts +94 -0
- package/dist/src/v2/active-attempt.d.ts.map +1 -0
- package/dist/src/v2/active-attempt.js +200 -0
- package/dist/src/v2/active-attempt.js.map +1 -0
- package/dist/src/v2/adapters/command.d.ts +6 -0
- package/dist/src/v2/adapters/command.d.ts.map +1 -1
- package/dist/src/v2/adapters/command.js +43 -2
- package/dist/src/v2/adapters/command.js.map +1 -1
- package/dist/src/v2/candidate.d.ts +15 -31
- package/dist/src/v2/candidate.d.ts.map +1 -1
- package/dist/src/v2/candidate.js +7 -29
- package/dist/src/v2/candidate.js.map +1 -1
- package/dist/src/v2/checked-change.d.ts +3 -2
- package/dist/src/v2/checked-change.d.ts.map +1 -1
- package/dist/src/v2/checked-change.js +4 -3
- package/dist/src/v2/checked-change.js.map +1 -1
- package/dist/src/v2/cli-contract.d.ts +1 -1
- package/dist/src/v2/cli-contract.d.ts.map +1 -1
- package/dist/src/v2/cli-contract.js +4 -6
- package/dist/src/v2/cli-contract.js.map +1 -1
- package/dist/src/v2/cli.d.ts +8 -0
- package/dist/src/v2/cli.d.ts.map +1 -1
- package/dist/src/v2/cli.js +13 -0
- package/dist/src/v2/cli.js.map +1 -1
- package/dist/src/v2/code-review-report.d.ts +10 -18
- package/dist/src/v2/code-review-report.d.ts.map +1 -1
- package/dist/src/v2/code-review-report.js +63 -60
- package/dist/src/v2/code-review-report.js.map +1 -1
- package/dist/src/v2/codex-process.d.ts +6 -2
- package/dist/src/v2/codex-process.d.ts.map +1 -1
- package/dist/src/v2/codex-process.js +25 -9
- package/dist/src/v2/codex-process.js.map +1 -1
- package/dist/src/v2/config.d.ts +0 -2
- package/dist/src/v2/config.d.ts.map +1 -1
- package/dist/src/v2/config.js +3 -6
- package/dist/src/v2/config.js.map +1 -1
- package/dist/src/v2/contained-report-operation.d.ts +41 -196
- package/dist/src/v2/contained-report-operation.d.ts.map +1 -1
- package/dist/src/v2/contained-report-operation.js +139 -466
- package/dist/src/v2/contained-report-operation.js.map +1 -1
- package/dist/src/v2/containment.d.ts +1 -0
- package/dist/src/v2/containment.d.ts.map +1 -1
- package/dist/src/v2/containment.js +12 -2
- package/dist/src/v2/containment.js.map +1 -1
- package/dist/src/v2/delivery-authority.d.ts +26 -0
- package/dist/src/v2/delivery-authority.d.ts.map +1 -0
- package/dist/src/v2/delivery-authority.js +44 -0
- package/dist/src/v2/delivery-authority.js.map +1 -0
- package/dist/src/v2/direct-delivery.d.ts +16 -36
- package/dist/src/v2/direct-delivery.d.ts.map +1 -1
- package/dist/src/v2/direct-delivery.js +135 -122
- package/dist/src/v2/direct-delivery.js.map +1 -1
- package/dist/src/v2/immutable-workflow-publisher.d.ts.map +1 -1
- package/dist/src/v2/immutable-workflow-publisher.js +3 -1
- package/dist/src/v2/immutable-workflow-publisher.js.map +1 -1
- package/dist/src/v2/implementation-report.d.ts +3 -1
- package/dist/src/v2/implementation-report.d.ts.map +1 -1
- package/dist/src/v2/implementation-report.js +28 -10
- package/dist/src/v2/implementation-report.js.map +1 -1
- package/dist/src/v2/implementation-reviewer.d.ts +41 -12
- package/dist/src/v2/implementation-reviewer.d.ts.map +1 -1
- package/dist/src/v2/implementation-reviewer.js +114 -42
- package/dist/src/v2/implementation-reviewer.js.map +1 -1
- package/dist/src/v2/pending-effect-settlement.d.ts +44 -0
- package/dist/src/v2/pending-effect-settlement.d.ts.map +1 -0
- package/dist/src/v2/pending-effect-settlement.js +69 -0
- package/dist/src/v2/pending-effect-settlement.js.map +1 -0
- package/dist/src/v2/process-identity.d.ts +45 -0
- package/dist/src/v2/process-identity.d.ts.map +1 -0
- package/dist/src/v2/process-identity.js +118 -0
- package/dist/src/v2/process-identity.js.map +1 -0
- package/dist/src/v2/proof-report.d.ts +2 -1
- package/dist/src/v2/proof-report.d.ts.map +1 -1
- package/dist/src/v2/proof-report.js +10 -4
- package/dist/src/v2/proof-report.js.map +1 -1
- package/dist/src/v2/review-feedback-coordinator.d.ts +1 -1
- package/dist/src/v2/review-feedback-coordinator.d.ts.map +1 -1
- package/dist/src/v2/review-feedback-coordinator.js +1 -1
- package/dist/src/v2/review-feedback-coordinator.js.map +1 -1
- package/dist/src/v2/review-feedback.d.ts +14 -20
- package/dist/src/v2/review-feedback.d.ts.map +1 -1
- package/dist/src/v2/review-feedback.js +45 -87
- package/dist/src/v2/review-feedback.js.map +1 -1
- package/dist/src/v2/run-issue.d.ts +129 -88
- package/dist/src/v2/run-issue.d.ts.map +1 -1
- package/dist/src/v2/run-issue.js +1965 -2381
- package/dist/src/v2/run-issue.js.map +1 -1
- package/dist/src/v2/run-state-projections.d.ts +84 -0
- package/dist/src/v2/run-state-projections.d.ts.map +1 -0
- package/dist/src/v2/run-state-projections.js +142 -0
- package/dist/src/v2/run-state-projections.js.map +1 -0
- package/dist/src/v2/run-store.d.ts +99 -81
- package/dist/src/v2/run-store.d.ts.map +1 -1
- package/dist/src/v2/run-store.js +245 -542
- package/dist/src/v2/run-store.js.map +1 -1
- package/dist/src/v2/runtime-assets.d.ts +3 -0
- package/dist/src/v2/runtime-assets.d.ts.map +1 -1
- package/dist/src/v2/runtime-assets.js +104 -0
- package/dist/src/v2/runtime-assets.js.map +1 -1
- package/dist/src/v2/runtime.d.ts +56 -44
- package/dist/src/v2/runtime.d.ts.map +1 -1
- package/dist/src/v2/runtime.js +383 -503
- package/dist/src/v2/runtime.js.map +1 -1
- package/dist/src/v2/setup.js +0 -2
- package/dist/src/v2/setup.js.map +1 -1
- package/dist/src/v2/validation-progression.d.ts +70 -0
- package/dist/src/v2/validation-progression.d.ts.map +1 -0
- package/dist/src/v2/validation-progression.js +247 -0
- package/dist/src/v2/validation-progression.js.map +1 -0
- package/dist/src/v2/workflow-assets.d.ts +9 -3
- package/dist/src/v2/workflow-assets.d.ts.map +1 -1
- package/dist/src/v2/workflow-assets.js +256 -43
- package/dist/src/v2/workflow-assets.js.map +1 -1
- package/internal-workflow/docs/agents/bug-workflow-routing.md +9 -7
- package/internal-workflow/docs/agents/coding-skill-routing.md +170 -120
- package/internal-workflow/docs/agents/tool-usage.md +23 -12
- package/internal-workflow/manifest.json +1 -1
- package/internal-workflow/operations/code-review/SKILL.md +34 -15
- package/internal-workflow/operations/implementation/SKILL.md +21 -16
- package/internal-workflow/profiles/implementer.toml +9 -0
- package/internal-workflow/profiles/review_coordinator.toml +9 -0
- package/internal-workflow/profiles/spec_reviewer.toml +9 -0
- package/internal-workflow/profiles/standards_reviewer.toml +9 -0
- package/internal-workflow/schemas/code-review-v1.json +1 -1
- package/internal-workflow/schemas/implementation-report-v1.json +1 -1
- package/internal-workflow/schemas/proof-report-v1.json +1 -1
- package/internal-workflow/skills/bug-root-cause-explainer/SKILL.md +114 -0
- package/internal-workflow/skills/bug-root-cause-explainer/agents/openai.yaml +7 -0
- package/internal-workflow/skills/bug-root-cause-explainer/evals/evals.json +18 -0
- package/internal-workflow/skills/code-review/SKILL.md +84 -306
- package/internal-workflow/skills/code-review/agents/openai.yaml +5 -3
- package/internal-workflow/skills/code-review/evals/evals.json +83 -0
- package/internal-workflow/skills/code-review/references/standards-smells.md +41 -0
- package/internal-workflow/skills/diagnosing-bugs/SKILL.md +69 -32
- package/internal-workflow/skills/diagnosing-bugs/agents/openai.yaml +2 -2
- package/internal-workflow/skills/diagnosing-bugs/evals/evals.json +63 -0
- package/internal-workflow/skills/grilling/SKILL.md +51 -0
- package/internal-workflow/skills/grilling/agents/openai.yaml +6 -0
- package/internal-workflow/skills/grilling/evals/evals.json +47 -0
- package/internal-workflow/skills/implement/SKILL.md +135 -0
- package/internal-workflow/skills/implement/agents/openai.yaml +6 -0
- package/internal-workflow/skills/implement/evals/evals.json +150 -0
- package/internal-workflow/skills/plan/SKILL.md +59 -0
- package/internal-workflow/skills/plan/agents/openai.yaml +6 -0
- package/internal-workflow/skills/plan/evals/evals.json +36 -0
- package/internal-workflow/skills/prototype/LOGIC.md +130 -0
- package/internal-workflow/skills/prototype/SKILL.md +69 -0
- package/internal-workflow/skills/prototype/UI.md +157 -0
- package/internal-workflow/skills/prototype/agents/openai.yaml +6 -0
- package/internal-workflow/skills/prototype/evals/evals.json +67 -0
- package/internal-workflow/skills/research/SKILL.md +110 -0
- package/internal-workflow/skills/research/agents/openai.yaml +6 -0
- package/internal-workflow/skills/research/evals/evals.json +49 -0
- package/internal-workflow/skills/tdd/SKILL.md +72 -67
- package/internal-workflow/skills/tdd/agents/openai.yaml +2 -2
- package/internal-workflow/skills/tdd/evals/evals.json +12 -0
- package/internal-workflow/skills/tdd/mocking.md +48 -1
- package/internal-workflow/skills/tdd/refactoring.md +3 -3
- package/internal-workflow/skills/tickets-orchestrator/SKILL.md +199 -0
- package/internal-workflow/skills/tickets-orchestrator/agents/openai.yaml +6 -0
- package/internal-workflow/skills/tickets-orchestrator/evals/evals.json +126 -0
- package/internal-workflow/skills/tickets-orchestrator/references/delegate-integrate.md +83 -0
- package/internal-workflow/skills/tickets-orchestrator/references/finish-delivery.md +69 -0
- package/internal-workflow/skills/tickets-orchestrator/references/stop-completion.md +63 -0
- package/internal-workflow/skills/to-spec/SKILL.md +133 -0
- package/internal-workflow/skills/to-spec/agents/openai.yaml +6 -0
- package/internal-workflow/skills/to-spec/evals/evals.json +24 -0
- package/internal-workflow/skills/to-tickets/SKILL.md +189 -0
- package/internal-workflow/skills/to-tickets/agents/openai.yaml +6 -0
- package/internal-workflow/skills/to-tickets/evals/evals.json +79 -0
- package/internal-workflow/skills/to-tickets/references/publishing-details.md +117 -0
- package/package.json +1 -1
- package/dist/src/v2/proof-store.d.ts +0 -54
- package/dist/src/v2/proof-store.d.ts.map +0 -1
- package/dist/src/v2/proof-store.js +0 -301
- package/dist/src/v2/proof-store.js.map +0 -1
- package/dist/src/v2/route-continuations.d.ts +0 -32
- package/dist/src/v2/route-continuations.d.ts.map +0 -1
- package/dist/src/v2/route-continuations.js +0 -2
- package/dist/src/v2/route-continuations.js.map +0 -1
- package/dist/src/v2/route-coordinator.d.ts +0 -72
- package/dist/src/v2/route-coordinator.d.ts.map +0 -1
- package/dist/src/v2/route-coordinator.js +0 -275
- package/dist/src/v2/route-coordinator.js.map +0 -1
- package/dist/src/v2/route-decision.d.ts +0 -120
- package/dist/src/v2/route-decision.d.ts.map +0 -1
- package/dist/src/v2/route-decision.js +0 -380
- package/dist/src/v2/route-decision.js.map +0 -1
- package/dist/src/v2/spec-coordinator.d.ts +0 -73
- package/dist/src/v2/spec-coordinator.d.ts.map +0 -1
- package/dist/src/v2/spec-coordinator.js +0 -126
- package/dist/src/v2/spec-coordinator.js.map +0 -1
- package/dist/src/v2/spec-delivery.d.ts +0 -112
- package/dist/src/v2/spec-delivery.d.ts.map +0 -1
- package/dist/src/v2/spec-delivery.js +0 -336
- package/dist/src/v2/spec-delivery.js.map +0 -1
- package/dist/src/v2/triage-route.d.ts +0 -68
- package/dist/src/v2/triage-route.d.ts.map +0 -1
- package/dist/src/v2/triage-route.js +0 -223
- package/dist/src/v2/triage-route.js.map +0 -1
- package/dist/src/v2/waiting-human-coordinator.d.ts +0 -49
- package/dist/src/v2/waiting-human-coordinator.d.ts.map +0 -1
- package/dist/src/v2/waiting-human-coordinator.js +0 -509
- package/dist/src/v2/waiting-human-coordinator.js.map +0 -1
- package/dist/src/v2/waiting-human.d.ts +0 -143
- package/dist/src/v2/waiting-human.d.ts.map +0 -1
- package/dist/src/v2/waiting-human.js +0 -408
- package/dist/src/v2/waiting-human.js.map +0 -1
- package/internal-workflow/docs/agents/contract-test-ledger.md +0 -71
- package/internal-workflow/docs/agents/review-gates.md +0 -42
- package/internal-workflow/docs/agents/review-protocol.md +0 -98
- package/internal-workflow/evals/coding-skill-evals.json +0 -373
- package/internal-workflow/operations/ambiguity-review/SKILL.md +0 -5
- package/internal-workflow/operations/qualification-repair/SKILL.md +0 -17
- package/internal-workflow/operations/spec-author/SKILL.md +0 -12
- package/internal-workflow/operations/spec-review/SKILL.md +0 -12
- package/internal-workflow/operations/triage/SKILL.md +0 -12
- package/internal-workflow/profiles/analyst_deep.toml +0 -9
- package/internal-workflow/profiles/implementer_standard.toml +0 -9
- package/internal-workflow/profiles/proof_agent.toml +0 -8
- package/internal-workflow/profiles/reviewer_deep.toml +0 -9
- package/internal-workflow/profiles/reviewer_standard.toml +0 -9
- package/internal-workflow/schemas/ambiguity-review-v1.json +0 -1
- package/internal-workflow/schemas/spec-author-v1.json +0 -1
- package/internal-workflow/schemas/spec-review-v1.json +0 -30
- package/internal-workflow/schemas/triage-route-v1.json +0 -1
- package/internal-workflow/skills/agent-auto/SKILL.md +0 -19
- package/internal-workflow/skills/agent-auto/agents/openai.yaml +0 -6
- package/internal-workflow/skills/code-debugger/SKILL.md +0 -122
- package/internal-workflow/skills/code-debugger/agents/openai.yaml +0 -7
- package/internal-workflow/skills/code-review/references/bug-classes.md +0 -56
- package/internal-workflow/skills/code-review/references/cleanup-lens.md +0 -52
- package/internal-workflow/skills/code-review/references/framework-lenses.md +0 -34
- package/internal-workflow/skills/code-review/references/targeted-recipes.md +0 -49
- package/internal-workflow/skills/implementation-spec-maker/SKILL.md +0 -107
- package/internal-workflow/skills/implementation-spec-maker/agents/openai.yaml +0 -6
- package/internal-workflow/skills/implementation-spec-maker/references/source-modes.md +0 -32
- package/internal-workflow/skills/implementation-spec-maker/references/spec-template.md +0 -146
- package/internal-workflow/skills/implementation-spec-review/SKILL.md +0 -131
- package/internal-workflow/skills/implementation-spec-review/agents/openai.yaml +0 -6
- package/internal-workflow/skills/implementation-spec-review/evals/evals.json +0 -78
- package/internal-workflow/skills/implementation-spec-review/references/review-loop.md +0 -121
- package/internal-workflow/skills/small-task-implementer/SKILL.md +0 -112
- package/internal-workflow/skills/small-task-implementer/agents/openai.yaml +0 -6
- package/internal-workflow/skills/spec-implementer/SKILL.md +0 -133
- package/internal-workflow/skills/spec-implementer/agents/openai.yaml +0 -6
- package/internal-workflow/skills/spec-implementer/evals/evals.json +0 -30
- package/internal-workflow/skills/spec-implementer/references/review-loop.md +0 -100
- package/internal-workflow/skills/triage/AGENT-BRIEF.md +0 -192
- package/internal-workflow/skills/triage/OUT-OF-SCOPE.md +0 -101
- package/internal-workflow/skills/triage/SKILL.md +0 -134
- package/internal-workflow/skills/triage/agents/openai.yaml +0 -6
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import { canonicalJson, sha256 } from './containment.js';
|
|
1
|
+
import { canonicalJson, containsCredentialEvidence, sha256 } from './containment.js';
|
|
2
|
+
import { decodeAgentReportForValidation } from './report-envelope.js';
|
|
3
|
+
import { hashCodeReviewReport, validateCodeReviewReport, } from './code-review-report.js';
|
|
4
|
+
const MAX_STRING_LENGTH = 16 * 1024;
|
|
2
5
|
const POLICY_KEYS = [
|
|
3
6
|
'sandboxMode', 'cwdClass', 'worktreeAccess', 'writableRootClasses', 'runnerPostcondition',
|
|
4
7
|
'network', 'networkHosts', 'mcpTools', 'approvalCeiling', 'externalWrite',
|
|
@@ -9,494 +12,160 @@ export class InjectedContainedReportOperation {
|
|
|
9
12
|
this.dependencies = dependencies;
|
|
10
13
|
}
|
|
11
14
|
async run(input) {
|
|
12
|
-
|
|
13
|
-
if (existing)
|
|
14
|
-
return this.recover(input, validateDurableReportInvocation(existing));
|
|
15
|
-
return this.launch(input);
|
|
16
|
-
}
|
|
17
|
-
async launch(input) {
|
|
18
|
-
let baseline;
|
|
15
|
+
let before;
|
|
19
16
|
try {
|
|
20
|
-
|
|
17
|
+
before = requireReportSnapshot(await this.dependencies.snapshot(input.worktreePath));
|
|
21
18
|
}
|
|
22
19
|
catch {
|
|
23
|
-
return blocked
|
|
20
|
+
return { status: 'blocked', kind: 'safety', code: 'report-operation-snapshot-failed' };
|
|
24
21
|
}
|
|
25
|
-
const attemptId = this.dependencies.createAttemptId();
|
|
26
|
-
if (!attemptId || input.forbiddenAttemptIds?.includes(attemptId))
|
|
27
|
-
return blocked('safety', 'report-operation-attempt-identity-invalid');
|
|
28
22
|
let attempt;
|
|
29
23
|
try {
|
|
30
|
-
attempt = await this.prepare(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
: blocked('external', 'report-operation-prepare-failed');
|
|
36
|
-
}
|
|
37
|
-
const prepared = {
|
|
38
|
-
version: 1, operation: input.operation, attemptId, generationHash: input.workflowGeneration.generationHash,
|
|
39
|
-
promptFactsSha256: promptFactsSha256(input.promptFacts),
|
|
40
|
-
reportPath: attempt.reportPath, phase: 'prepared', host: this.dependencies.host, bootId: this.dependencies.bootId,
|
|
41
|
-
preparedAt: this.dependencies.now(), launchedAt: null, pid: null, processStartIdentity: null,
|
|
42
|
-
processGroupId: null, baseline,
|
|
43
|
-
};
|
|
44
|
-
if (!await input.invocationState.compareAndSwap(undefined, prepared))
|
|
45
|
-
return blocked('safety', 'report-operation-state-conflict');
|
|
46
|
-
let current = prepared;
|
|
47
|
-
let result;
|
|
48
|
-
try {
|
|
49
|
-
await input.beforeLaunch?.();
|
|
50
|
-
}
|
|
51
|
-
catch (error) {
|
|
52
|
-
const abandoned = await this.abandon(input, current, attempt);
|
|
53
|
-
if (abandoned)
|
|
54
|
-
return abandoned;
|
|
55
|
-
throw error;
|
|
56
|
-
}
|
|
57
|
-
try {
|
|
58
|
-
result = await this.dependencies.launch({
|
|
59
|
-
...input,
|
|
60
|
-
attempt,
|
|
61
|
-
onSpawned: async ({ pid, processGroupId }) => {
|
|
62
|
-
const processStartIdentity = await this.dependencies.processStartIdentity(pid);
|
|
63
|
-
if (!processStartIdentity)
|
|
64
|
-
throw new Error('report process identity unavailable');
|
|
65
|
-
const launched = {
|
|
66
|
-
...prepared, phase: 'launched', launchedAt: this.dependencies.now(), pid, processStartIdentity, processGroupId,
|
|
67
|
-
};
|
|
68
|
-
if (!await input.invocationState.compareAndSwap(prepared, launched))
|
|
69
|
-
throw new Error('report launch state conflict');
|
|
70
|
-
current = launched;
|
|
71
|
-
},
|
|
24
|
+
attempt = await this.dependencies.prepare({
|
|
25
|
+
operation: input.operation,
|
|
26
|
+
attemptId: input.attemptId,
|
|
27
|
+
runId: input.runId,
|
|
28
|
+
workflowGeneration: input.workflowGeneration,
|
|
72
29
|
});
|
|
73
30
|
}
|
|
74
31
|
catch {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
return this.finish(input, current, attempt, result.reportBytes);
|
|
79
|
-
if (result.status === 'safe-halt')
|
|
80
|
-
return { status: 'safe-halt', code: 'report-operation-process-unresolved' };
|
|
81
|
-
if (result.status === 'cancelled' || result.status === 'blocked') {
|
|
82
|
-
const abandoned = await this.abandon(input, current, attempt);
|
|
83
|
-
if (abandoned)
|
|
84
|
-
return abandoned;
|
|
85
|
-
return result;
|
|
86
|
-
}
|
|
87
|
-
const recovered = await this.observeReport(current.reportPath);
|
|
88
|
-
if (recovered.status === 'unknown')
|
|
89
|
-
return { status: 'safe-halt', code: 'report-operation-report-observation-unknown' };
|
|
90
|
-
if (recovered.status === 'available')
|
|
91
|
-
return this.finish(input, current, attempt, recovered.bytes);
|
|
92
|
-
const abandoned = await this.abandon(input, current, attempt);
|
|
93
|
-
if (abandoned)
|
|
94
|
-
return abandoned;
|
|
95
|
-
return result;
|
|
96
|
-
}
|
|
97
|
-
async recover(input, invocation) {
|
|
98
|
-
if (invocation.operation !== input.operation || invocation.generationHash !== input.workflowGeneration.generationHash
|
|
99
|
-
|| invocation.host !== this.dependencies.host || invocation.bootId !== this.dependencies.bootId) {
|
|
100
|
-
return { status: 'safe-halt', code: 'report-operation-owner-identity-unresolved' };
|
|
101
|
-
}
|
|
102
|
-
if (invocation.promptFactsSha256 !== promptFactsSha256(input.promptFacts)) {
|
|
103
|
-
return { status: 'safe-halt', code: 'report-operation-prompt-facts-drift' };
|
|
104
|
-
}
|
|
105
|
-
let attempt;
|
|
106
|
-
try {
|
|
107
|
-
attempt = await this.prepare(input, invocation.attemptId);
|
|
108
|
-
}
|
|
109
|
-
catch (error) {
|
|
110
|
-
return error instanceof ReportAuthorityError
|
|
111
|
-
? blocked('safety', 'report-operation-authority-drift')
|
|
112
|
-
: blocked('external', 'report-operation-prepare-failed');
|
|
113
|
-
}
|
|
114
|
-
if (attempt.reportPath !== invocation.reportPath)
|
|
115
|
-
return blocked('safety', 'report-operation-report-path-drift');
|
|
116
|
-
const report = await this.observeReport(invocation.reportPath);
|
|
117
|
-
if (report.status === 'unknown')
|
|
118
|
-
return { status: 'safe-halt', code: 'report-operation-report-observation-unknown' };
|
|
119
|
-
if (invocation.phase === 'prepared') {
|
|
120
|
-
if (report.status === 'available')
|
|
121
|
-
return this.finish(input, invocation, attempt, report.bytes);
|
|
122
|
-
if (!await this.settleAttempt(attempt))
|
|
123
|
-
return { status: 'safe-halt', code: 'report-operation-attempt-cleanup-failed' };
|
|
124
|
-
if (!await input.invocationState.compareAndSwap(invocation, undefined))
|
|
125
|
-
return blocked('safety', 'report-operation-state-conflict');
|
|
126
|
-
return { status: 'retryable', code: 'report-operation-prepared-attempt-abandoned' };
|
|
127
|
-
}
|
|
128
|
-
const unresolved = await this.unresolvedProcess(invocation);
|
|
129
|
-
if (unresolved)
|
|
130
|
-
return unresolved;
|
|
131
|
-
if (report.status === 'available')
|
|
132
|
-
return this.finish(input, invocation, attempt, report.bytes);
|
|
133
|
-
if (!await this.settleAttempt(attempt))
|
|
134
|
-
return { status: 'safe-halt', code: 'report-operation-attempt-cleanup-failed' };
|
|
135
|
-
if (!await input.invocationState.compareAndSwap(invocation, undefined))
|
|
136
|
-
return blocked('safety', 'report-operation-state-conflict');
|
|
137
|
-
return { status: 'retryable', code: 'report-operation-output-unavailable' };
|
|
138
|
-
}
|
|
139
|
-
async abandon(input, invocation, attempt) {
|
|
140
|
-
if (invocation.phase === 'launched') {
|
|
141
|
-
const unresolved = await this.unresolvedProcess(invocation);
|
|
142
|
-
if (unresolved)
|
|
143
|
-
return unresolved;
|
|
144
|
-
}
|
|
145
|
-
if (!await this.settleAttempt(attempt))
|
|
146
|
-
return { status: 'safe-halt', code: 'report-operation-attempt-cleanup-failed' };
|
|
147
|
-
if (!await input.invocationState.compareAndSwap(invocation, undefined))
|
|
148
|
-
return blocked('safety', 'report-operation-state-conflict');
|
|
149
|
-
return undefined;
|
|
150
|
-
}
|
|
151
|
-
async observeReport(path) {
|
|
152
|
-
try {
|
|
153
|
-
return await this.dependencies.readReport(path);
|
|
154
|
-
}
|
|
155
|
-
catch {
|
|
156
|
-
return { status: 'unknown' };
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
async unresolvedProcess(invocation) {
|
|
160
|
-
const observed = await this.dependencies.inspectProcess(invocation).catch(() => ({ status: 'unknown' }));
|
|
161
|
-
if (observed.status === 'unknown')
|
|
162
|
-
return { status: 'safe-halt', code: 'report-operation-process-observation-unknown' };
|
|
163
|
-
if (observed.status === 'present' && (observed.processStartIdentity === invocation.processStartIdentity || observed.processGroupAlive !== false)) {
|
|
164
|
-
return { status: 'safe-halt', code: 'report-operation-process-active-or-uncertain' };
|
|
165
|
-
}
|
|
166
|
-
return observed.status === 'absent' && observed.processGroupAlive !== false
|
|
167
|
-
? { status: 'safe-halt', code: 'report-operation-process-group-unresolved' } : undefined;
|
|
168
|
-
}
|
|
169
|
-
async settleAttempt(attempt) {
|
|
170
|
-
try {
|
|
171
|
-
await this.dependencies.settleAttempt(attempt);
|
|
172
|
-
return true;
|
|
173
|
-
}
|
|
174
|
-
catch {
|
|
175
|
-
return false;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
async prepare(input, attemptId) {
|
|
179
|
-
const attempt = await this.dependencies.prepare({
|
|
180
|
-
operation: input.operation, attemptId, runId: input.runId, workflowGeneration: input.workflowGeneration,
|
|
181
|
-
});
|
|
182
|
-
if (!hasExactReportAuthority(attempt, input) || !attempt.reportPath)
|
|
183
|
-
throw new ReportAuthorityError();
|
|
184
|
-
return attempt;
|
|
185
|
-
}
|
|
186
|
-
async finish(input, invocation, attempt, reportBytes) {
|
|
187
|
-
let after;
|
|
188
|
-
try {
|
|
189
|
-
after = requireReportSnapshot(await this.dependencies.snapshot(input.worktreePath, input.operation));
|
|
190
|
-
}
|
|
191
|
-
catch {
|
|
192
|
-
return blocked('safety', 'report-operation-snapshot-failed');
|
|
193
|
-
}
|
|
194
|
-
if (canonicalJson(invocation.baseline) !== canonicalJson(after))
|
|
195
|
-
return blocked('safety', 'report-operation-worktree-mutated');
|
|
196
|
-
if (!await this.settleAttempt(attempt))
|
|
197
|
-
return { status: 'safe-halt', code: 'report-operation-attempt-cleanup-failed' };
|
|
198
|
-
return { status: 'completed', attemptId: invocation.attemptId, reportBytes: Buffer.from(reportBytes), reportSha256: sha256(reportBytes) };
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
export class InjectedContainedMutableOperation {
|
|
202
|
-
dependencies;
|
|
203
|
-
constructor(dependencies) {
|
|
204
|
-
this.dependencies = dependencies;
|
|
205
|
-
}
|
|
206
|
-
async run(input) {
|
|
207
|
-
const existing = await input.invocationState.read();
|
|
208
|
-
return existing ? this.recover(input, validateDurableMutableInvocation(existing)) : this.launch(input);
|
|
209
|
-
}
|
|
210
|
-
async settle(input) {
|
|
211
|
-
const existing = await input.invocationState.read();
|
|
212
|
-
if (!existing)
|
|
213
|
-
return { status: 'settled' };
|
|
214
|
-
const recovered = await this.recover(input, validateDurableMutableInvocation(existing));
|
|
215
|
-
if (recovered.status === 'retryable') {
|
|
216
|
-
return await input.invocationState.read() === undefined
|
|
217
|
-
? { status: 'settled' }
|
|
218
|
-
: { status: 'safe-halt', code: 'mutable-operation-state-conflict' };
|
|
32
|
+
return this.finishWithSnapshot(input.worktreePath, before, {
|
|
33
|
+
status: 'blocked', kind: 'external', code: 'report-operation-prepare-failed',
|
|
34
|
+
});
|
|
219
35
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
36
|
+
let launchResult;
|
|
37
|
+
if (!hasExactReadOnlyAuthority(attempt, input)) {
|
|
38
|
+
launchResult = { status: 'blocked', kind: 'safety', code: 'report-operation-authority-drift' };
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
if (isImplementationReview(input.operation)) {
|
|
42
|
+
if (!input.reviewContext || !input.onPrepared || !input.onLaunched) {
|
|
43
|
+
return this.finishWithSnapshot(input.worktreePath, before, {
|
|
44
|
+
status: 'blocked', kind: 'safety', code: 'review-operation-launch-gate-missing',
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
await input.onPrepared();
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return this.finishWithSnapshot(input.worktreePath, before, {
|
|
52
|
+
status: 'blocked', kind: 'safety', code: 'review-operation-prepare-persistence-failed',
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
launchResult = await this.dependencies.launch({ ...input, attempt });
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
launchResult = { status: 'blocked', kind: 'external', code: 'report-operation-launch-failed' };
|
|
224
61
|
}
|
|
225
|
-
return await input.invocationState.compareAndSwap(adopted, undefined)
|
|
226
|
-
? { status: 'settled' }
|
|
227
|
-
: { status: 'safe-halt', code: 'mutable-operation-state-conflict' };
|
|
228
|
-
}
|
|
229
|
-
return { status: 'safe-halt', code: recovered.status === 'cancelled'
|
|
230
|
-
? 'mutable-operation-settlement-cancelled' : recovered.code };
|
|
231
|
-
}
|
|
232
|
-
async launch(input) {
|
|
233
|
-
let baseline;
|
|
234
|
-
try {
|
|
235
|
-
baseline = requireReportSnapshot(await this.dependencies.snapshot(input.worktreePath));
|
|
236
|
-
}
|
|
237
|
-
catch {
|
|
238
|
-
return blocked('safety', 'mutable-operation-snapshot-failed');
|
|
239
|
-
}
|
|
240
|
-
const attemptId = this.dependencies.createAttemptId();
|
|
241
|
-
if (!attemptId)
|
|
242
|
-
return blocked('safety', 'mutable-operation-attempt-identity-invalid');
|
|
243
|
-
let attempt;
|
|
244
|
-
try {
|
|
245
|
-
attempt = await this.prepare(input, attemptId);
|
|
246
|
-
}
|
|
247
|
-
catch (error) {
|
|
248
|
-
return error instanceof ReportAuthorityError
|
|
249
|
-
? blocked('safety', 'mutable-operation-authority-drift') : blocked('external', 'mutable-operation-prepare-failed');
|
|
250
62
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
return blocked('safety', 'mutable-operation-state-conflict');
|
|
260
|
-
let current = prepared;
|
|
261
|
-
let result;
|
|
262
|
-
try {
|
|
263
|
-
await input.beforeLaunch?.();
|
|
264
|
-
result = await this.dependencies.launch({
|
|
265
|
-
...input, attempt,
|
|
266
|
-
onSpawned: async ({ pid, processGroupId }) => {
|
|
267
|
-
const processStartIdentity = await this.dependencies.processStartIdentity(pid);
|
|
268
|
-
if (!processStartIdentity)
|
|
269
|
-
throw new Error('mutable process identity unavailable');
|
|
270
|
-
const launched = { ...prepared, phase: 'launched', launchedAt: this.dependencies.now(),
|
|
271
|
-
pid, processStartIdentity, processGroupId };
|
|
272
|
-
if (!await input.invocationState.compareAndSwap(prepared, launched))
|
|
273
|
-
throw new Error('mutable launch state conflict');
|
|
274
|
-
current = launched;
|
|
63
|
+
if (launchResult.status === 'safe-halt') {
|
|
64
|
+
return {
|
|
65
|
+
status: 'safe-halt',
|
|
66
|
+
process: {
|
|
67
|
+
pid: launchResult.pid,
|
|
68
|
+
processGroupId: launchResult.processGroupId,
|
|
69
|
+
startedAt: launchResult.startedAt,
|
|
70
|
+
baseline: structuredClone(before),
|
|
275
71
|
},
|
|
276
|
-
}
|
|
72
|
+
};
|
|
277
73
|
}
|
|
278
|
-
|
|
279
|
-
return { status: 'retryable', code: 'mutable-operation-launch-failed' };
|
|
280
|
-
}
|
|
281
|
-
if (result.status === 'completed')
|
|
282
|
-
return current.phase === 'launched'
|
|
283
|
-
? this.adopt(input, current, result.reportBytes)
|
|
284
|
-
: { status: 'safe-halt', code: 'mutable-operation-launch-unfenced' };
|
|
285
|
-
if (result.status === 'safe-halt')
|
|
286
|
-
return { status: 'safe-halt', code: 'mutable-operation-process-unresolved' };
|
|
287
|
-
if (result.status === 'cancelled' || result.status === 'blocked') {
|
|
288
|
-
const unresolved = current.phase === 'launched' ? await this.unresolvedProcess(current) : undefined;
|
|
289
|
-
if (unresolved)
|
|
290
|
-
return unresolved;
|
|
291
|
-
const snapshot = await this.stableSnapshot(input.worktreePath);
|
|
292
|
-
if (!snapshot || canonicalJson(snapshot) !== canonicalJson(current.baseline))
|
|
293
|
-
return { status: 'safe-halt', code: 'mutable-operation-abandonment-ambiguous' };
|
|
294
|
-
if (!await input.invocationState.compareAndSwap(current, undefined))
|
|
295
|
-
return blocked('safety', 'mutable-operation-state-conflict');
|
|
296
|
-
}
|
|
297
|
-
return result;
|
|
74
|
+
return this.finishWithSnapshot(input.worktreePath, before, launchResult, input, attempt);
|
|
298
75
|
}
|
|
299
|
-
async
|
|
300
|
-
|
|
301
|
-
|| invocation.generationHash !== input.workflowGeneration.generationHash || invocation.promptFactsSha256 !== promptFactsSha256(input.promptFacts)
|
|
302
|
-
|| canonicalJson(invocation.context) !== canonicalJson(input.context)
|
|
303
|
-
|| invocation.worktreePath !== input.worktreePath || invocation.host !== this.dependencies.host || invocation.bootId !== this.dependencies.bootId)
|
|
304
|
-
return { status: 'safe-halt', code: 'mutable-operation-correlation-drift' };
|
|
305
|
-
let attempt;
|
|
306
|
-
try {
|
|
307
|
-
attempt = await this.prepare(input, invocation.attemptId);
|
|
308
|
-
}
|
|
309
|
-
catch {
|
|
310
|
-
return { status: 'safe-halt', code: 'mutable-operation-prepare-recovery-failed' };
|
|
311
|
-
}
|
|
312
|
-
if (attempt.reportPath !== invocation.reportPath)
|
|
313
|
-
return { status: 'safe-halt', code: 'mutable-operation-report-path-drift' };
|
|
314
|
-
const report = await this.observeReport(invocation.reportPath);
|
|
315
|
-
if (report.status === 'unknown')
|
|
316
|
-
return { status: 'safe-halt', code: 'mutable-operation-report-observation-unknown' };
|
|
317
|
-
if (invocation.phase === 'adopted') {
|
|
318
|
-
if (report.status !== 'available' || sha256(report.bytes) !== invocation.reportSha256)
|
|
319
|
-
return { status: 'safe-halt', code: 'mutable-operation-adopted-report-drift' };
|
|
320
|
-
const snapshot = await this.stableSnapshot(input.worktreePath);
|
|
321
|
-
if (!snapshot || canonicalJson(snapshot) !== canonicalJson(invocation.resultSnapshot))
|
|
322
|
-
return { status: 'safe-halt', code: 'mutable-operation-adopted-worktree-drift' };
|
|
323
|
-
return this.completeAdopted(input, invocation, report.bytes);
|
|
324
|
-
}
|
|
325
|
-
if (invocation.phase === 'prepared') {
|
|
326
|
-
if (report.status === 'available')
|
|
327
|
-
return { status: 'safe-halt', code: 'mutable-operation-prepared-report-ambiguous' };
|
|
328
|
-
const snapshot = await this.stableSnapshot(input.worktreePath);
|
|
329
|
-
if (!snapshot || canonicalJson(snapshot) !== canonicalJson(invocation.baseline))
|
|
330
|
-
return { status: 'safe-halt', code: 'mutable-operation-prepared-worktree-ambiguous' };
|
|
331
|
-
if (!await input.invocationState.compareAndSwap(invocation, undefined))
|
|
332
|
-
return blocked('safety', 'mutable-operation-state-conflict');
|
|
333
|
-
return { status: 'retryable', code: 'mutable-operation-prepared-attempt-abandoned' };
|
|
334
|
-
}
|
|
335
|
-
const unresolved = await this.unresolvedProcess(invocation);
|
|
336
|
-
if (unresolved)
|
|
337
|
-
return unresolved;
|
|
338
|
-
if (report.status === 'available')
|
|
339
|
-
return this.adopt(input, invocation, report.bytes);
|
|
340
|
-
const snapshot = await this.stableSnapshot(input.worktreePath);
|
|
341
|
-
if (!snapshot || canonicalJson(snapshot) !== canonicalJson(invocation.baseline))
|
|
342
|
-
return { status: 'safe-halt', code: 'mutable-operation-worktree-without-report' };
|
|
343
|
-
if (!await input.invocationState.compareAndSwap(invocation, undefined))
|
|
344
|
-
return blocked('safety', 'mutable-operation-state-conflict');
|
|
345
|
-
return { status: 'retryable', code: 'mutable-operation-output-unavailable' };
|
|
346
|
-
}
|
|
347
|
-
async adopt(input, invocation, reportBytes) {
|
|
348
|
-
const resultSnapshot = await this.stableSnapshot(input.worktreePath);
|
|
349
|
-
if (!resultSnapshot || resultSnapshot.worktreeIdentity !== invocation.baseline.worktreeIdentity || resultSnapshot.headSha !== invocation.baseline.headSha)
|
|
350
|
-
return { status: 'safe-halt', code: 'mutable-operation-result-worktree-mismatch' };
|
|
351
|
-
const adopted = { ...invocation, phase: 'adopted', reportSha256: sha256(reportBytes), resultSnapshot };
|
|
352
|
-
if (!await input.invocationState.compareAndSwap(invocation, adopted))
|
|
353
|
-
return blocked('safety', 'mutable-operation-state-conflict');
|
|
354
|
-
return this.completeAdopted(input, adopted, reportBytes);
|
|
355
|
-
}
|
|
356
|
-
async completeAdopted(input, invocation, reportBytes) {
|
|
357
|
-
if (invocation.context.repairOnly && canonicalJson(invocation.baseline) !== canonicalJson(invocation.resultSnapshot)) {
|
|
358
|
-
if (!await input.invocationState.compareAndSwap(invocation, undefined))
|
|
359
|
-
return blocked('safety', 'mutable-operation-state-conflict');
|
|
360
|
-
return blocked('safety', 'report-repair-modified-worktree');
|
|
361
|
-
}
|
|
362
|
-
return completedMutable(invocation, reportBytes);
|
|
363
|
-
}
|
|
364
|
-
async prepare(input, attemptId) {
|
|
365
|
-
const operation = workerOperation(input.operation);
|
|
366
|
-
const attempt = await this.dependencies.prepare({ operation, attemptId, runId: input.runId, workflowGeneration: input.workflowGeneration });
|
|
367
|
-
if (attempt.operation !== operation || attempt.generationHash !== input.workflowGeneration.generationHash || !hasExactMutableAuthority(attempt.policy)
|
|
368
|
-
|| !attempt.reportPath)
|
|
369
|
-
throw new ReportAuthorityError();
|
|
370
|
-
return attempt;
|
|
371
|
-
}
|
|
372
|
-
async observeReport(path) {
|
|
373
|
-
try {
|
|
374
|
-
return await this.dependencies.readReport(path);
|
|
375
|
-
}
|
|
376
|
-
catch {
|
|
377
|
-
return { status: 'unknown' };
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
async stableSnapshot(worktreePath) {
|
|
76
|
+
async finishWithSnapshot(worktreePath, before, launchResult, input, attempt) {
|
|
77
|
+
let after;
|
|
381
78
|
try {
|
|
382
|
-
|
|
383
|
-
const second = requireReportSnapshot(await this.dependencies.snapshot(worktreePath));
|
|
384
|
-
return canonicalJson(first) === canonicalJson(second) ? first : undefined;
|
|
79
|
+
after = requireReportSnapshot(await this.dependencies.snapshot(worktreePath));
|
|
385
80
|
}
|
|
386
81
|
catch {
|
|
387
|
-
return
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
if (
|
|
393
|
-
return
|
|
394
|
-
if (
|
|
395
|
-
return { status: '
|
|
396
|
-
return
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
}
|
|
400
|
-
export function validateDurableMutableInvocation(value) {
|
|
401
|
-
const keys = [
|
|
402
|
-
'version', 'operation', 'attemptId', 'generationHash', 'promptFactsSha256', 'worktreePath', 'reportPath',
|
|
403
|
-
'context', 'phase', 'host', 'bootId', 'preparedAt', 'launchedAt', 'pid', 'processStartIdentity', 'processGroupId', 'baseline',
|
|
404
|
-
'reportSha256', 'resultSnapshot',
|
|
405
|
-
];
|
|
406
|
-
if (!hasExactKeys(value, keys))
|
|
407
|
-
throw new Error('durable mutable invocation is invalid');
|
|
408
|
-
const record = value;
|
|
409
|
-
if (record.version !== 1 || !['qualification-repair', 'implementation', 'review-feedback-implementation'].includes(record.operation)
|
|
410
|
-
|| !nonEmpty(record.attemptId) || !/^[0-9a-f]{64}$/u.test(record.generationHash)
|
|
411
|
-
|| !/^[0-9a-f]{64}$/u.test(record.promptFactsSha256) || !nonEmpty(record.worktreePath) || !nonEmpty(record.reportPath)
|
|
412
|
-
|| !nonEmpty(record.host) || !nonEmpty(record.bootId) || !timestamp(record.preparedAt)
|
|
413
|
-
|| !['prepared', 'launched', 'adopted'].includes(record.phase))
|
|
414
|
-
throw new Error('durable mutable invocation fields are invalid');
|
|
415
|
-
const context = record.context;
|
|
416
|
-
if (!hasExactKeys(context, ['repairOnly', 'reworkFindings']) || typeof context.repairOnly !== 'boolean'
|
|
417
|
-
|| !Array.isArray(context.reworkFindings) || !context.reworkFindings.every(nonEmpty)) {
|
|
418
|
-
throw new Error('durable mutable invocation context is invalid');
|
|
419
|
-
}
|
|
420
|
-
const launched = record.phase === 'launched' || record.phase === 'adopted';
|
|
421
|
-
if (launched !== (positive(record.pid) && positive(record.processGroupId) && nonEmpty(record.processStartIdentity) && timestamp(record.launchedAt)))
|
|
422
|
-
throw new Error('durable mutable invocation process identity is invalid');
|
|
423
|
-
if (!launched && (record.pid !== null || record.processGroupId !== null || record.processStartIdentity !== null || record.launchedAt !== null))
|
|
424
|
-
throw new Error('prepared mutable invocation has process identity');
|
|
425
|
-
const adopted = record.phase === 'adopted';
|
|
426
|
-
if (adopted !== (typeof record.reportSha256 === 'string'
|
|
427
|
-
&& /^[0-9a-f]{64}$/u.test(record.reportSha256) && record.resultSnapshot !== null))
|
|
428
|
-
throw new Error('durable mutable invocation adoption is invalid');
|
|
429
|
-
if (!adopted && (record.reportSha256 !== null || record.resultSnapshot !== null))
|
|
430
|
-
throw new Error('unfinished mutable invocation has adoption evidence');
|
|
431
|
-
requireReportSnapshot(record.baseline);
|
|
432
|
-
if (adopted)
|
|
433
|
-
requireReportSnapshot(record.resultSnapshot);
|
|
434
|
-
return structuredClone(value);
|
|
435
|
-
}
|
|
436
|
-
export function validateDurableReportInvocation(value) {
|
|
437
|
-
const keys = [
|
|
438
|
-
'version', 'operation', 'attemptId', 'generationHash', 'promptFactsSha256', 'reportPath', 'phase', 'host', 'bootId', 'preparedAt',
|
|
439
|
-
'launchedAt', 'pid', 'processStartIdentity', 'processGroupId', 'baseline',
|
|
440
|
-
];
|
|
441
|
-
if (!hasExactKeys(value, keys))
|
|
442
|
-
throw new Error('durable report invocation is invalid');
|
|
443
|
-
const record = value;
|
|
444
|
-
if (record.version !== 1 || !['triage', 'ambiguity-review', 'code-review', 'spec-author', 'spec-review', 'acceptance-proof'].includes(record.operation)
|
|
445
|
-
|| !nonEmpty(record.attemptId) || !/^[0-9a-f]{64}$/u.test(record.generationHash)
|
|
446
|
-
|| !nonEmpty(record.reportPath) || !nonEmpty(record.host) || !nonEmpty(record.bootId)
|
|
447
|
-
|| !/^[0-9a-f]{64}$/u.test(record.promptFactsSha256)
|
|
448
|
-
|| !timestamp(record.preparedAt) || !['prepared', 'launched'].includes(record.phase))
|
|
449
|
-
throw new Error('durable report invocation fields are invalid');
|
|
450
|
-
const launched = record.phase === 'launched';
|
|
451
|
-
if (launched !== (positive(record.pid) && positive(record.processGroupId) && nonEmpty(record.processStartIdentity)
|
|
452
|
-
&& timestamp(record.launchedAt)))
|
|
453
|
-
throw new Error('durable report invocation process identity is invalid');
|
|
454
|
-
if (!launched && (record.pid !== null || record.processGroupId !== null || record.processStartIdentity !== null || record.launchedAt !== null)) {
|
|
455
|
-
throw new Error('prepared report invocation has process identity');
|
|
82
|
+
return { status: 'blocked', kind: 'safety', code: 'report-operation-snapshot-failed' };
|
|
83
|
+
}
|
|
84
|
+
if (!sameSnapshot(before, after)) {
|
|
85
|
+
return { status: 'blocked', kind: 'safety', code: 'report-operation-worktree-mutated' };
|
|
86
|
+
}
|
|
87
|
+
if (launchResult.status !== 'completed')
|
|
88
|
+
return launchResult;
|
|
89
|
+
if (!input)
|
|
90
|
+
return { status: 'blocked', kind: 'external', code: 'report-operation-prepare-failed' };
|
|
91
|
+
return validateCompletedReport(input.operation, input.attemptId, launchResult.reportBytes, {
|
|
92
|
+
...input.reviewContext,
|
|
93
|
+
availableReviewers: [...(attempt?.reviewers ?? [])],
|
|
94
|
+
});
|
|
456
95
|
}
|
|
457
|
-
requireReportSnapshot(record.baseline);
|
|
458
|
-
return structuredClone(value);
|
|
459
96
|
}
|
|
460
|
-
function
|
|
97
|
+
function hasExactReadOnlyAuthority(attempt, input) {
|
|
461
98
|
const policy = attempt.policy;
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
99
|
+
return attempt.operation === input.operation
|
|
100
|
+
&& attempt.generationHash === input.workflowGeneration.generationHash
|
|
101
|
+
&& hasExactKeys(policy, POLICY_KEYS)
|
|
102
|
+
&& policy.sandboxMode === 'read-only'
|
|
103
|
+
&& policy.cwdClass === 'worktree'
|
|
104
|
+
&& policy.worktreeAccess === 'read-only'
|
|
105
|
+
&& Array.isArray(policy.writableRootClasses)
|
|
106
|
+
&& policy.writableRootClasses.length === 0
|
|
107
|
+
&& policy.runnerPostcondition === 'report-only'
|
|
108
|
+
&& policy.network === 'deny'
|
|
109
|
+
&& Array.isArray(policy.networkHosts)
|
|
110
|
+
&& policy.networkHosts.length === 0
|
|
111
|
+
&& Array.isArray(policy.mcpTools)
|
|
112
|
+
&& policy.mcpTools.length === 0
|
|
113
|
+
&& policy.approvalCeiling === 'never'
|
|
114
|
+
&& policy.externalWrite === false;
|
|
115
|
+
}
|
|
116
|
+
export function validateCompletedReport(operation, attemptId, reportBytes, reviewContext) {
|
|
117
|
+
const rawText = reportBytes.toString('utf8');
|
|
118
|
+
const repairable = Buffer.from(rawText, 'utf8').equals(reportBytes)
|
|
119
|
+
&& !containsCredentialEvidence(rawText);
|
|
120
|
+
try {
|
|
121
|
+
if (!Buffer.from(rawText, 'utf8').equals(reportBytes) || containsCredentialEvidence(rawText)) {
|
|
122
|
+
throw new Error('report payload contains forbidden credential material');
|
|
123
|
+
}
|
|
124
|
+
const decoded = decodeAgentReportForValidation(reportBytes);
|
|
125
|
+
const reviewReport = inputReview(operation, inputReviewContext(reviewContext), decoded);
|
|
126
|
+
const validatedPayload = reviewReport;
|
|
127
|
+
const artifactSha256 = hashCodeReviewReport(reviewReport);
|
|
128
|
+
return {
|
|
129
|
+
status: 'completed',
|
|
130
|
+
attemptId,
|
|
131
|
+
validatedPayload,
|
|
132
|
+
artifactSha256,
|
|
133
|
+
};
|
|
470
134
|
}
|
|
471
|
-
|
|
472
|
-
return
|
|
473
|
-
|
|
474
|
-
|
|
135
|
+
catch (error) {
|
|
136
|
+
return {
|
|
137
|
+
status: 'invalid',
|
|
138
|
+
attemptId,
|
|
139
|
+
findings: [safeFinding(error)],
|
|
140
|
+
...(repairable ? { repairInput: { originalReportSha256: sha256(reportBytes), originalReportBytes: Buffer.from(reportBytes) } } : {}),
|
|
141
|
+
};
|
|
475
142
|
}
|
|
476
|
-
return policy.sandboxMode === 'read-only' && policy.cwdClass === 'worktree'
|
|
477
|
-
&& policy.worktreeAccess === 'read-only' && Array.isArray(policy.writableRootClasses) && policy.writableRootClasses.length === 0
|
|
478
|
-
&& policy.runnerPostcondition === 'report-only';
|
|
479
143
|
}
|
|
480
|
-
function
|
|
481
|
-
return
|
|
482
|
-
&& policy.worktreeAccess === 'write' && canonicalJson(policy.writableRootClasses) === canonicalJson(['worktree'])
|
|
483
|
-
&& policy.runnerPostcondition === 'change-set' && policy.network === 'deny' && policy.networkHosts.length === 0
|
|
484
|
-
&& policy.mcpTools.length === 0 && policy.approvalCeiling === 'never' && policy.externalWrite === false;
|
|
144
|
+
function isImplementationReview(operation) {
|
|
145
|
+
return operation === 'code-review';
|
|
485
146
|
}
|
|
486
|
-
function
|
|
487
|
-
|
|
147
|
+
function inputReviewContext(context) {
|
|
148
|
+
if (!context)
|
|
149
|
+
throw new Error('implementation review validation context is missing');
|
|
150
|
+
return context;
|
|
488
151
|
}
|
|
489
|
-
function
|
|
490
|
-
|
|
152
|
+
function inputReview(operation, context, decoded) {
|
|
153
|
+
if (context.operation !== operation)
|
|
154
|
+
throw new Error('implementation review operation context mismatch');
|
|
155
|
+
return validateCodeReviewReport(decoded, context);
|
|
491
156
|
}
|
|
492
|
-
function
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
return structuredClone(value);
|
|
157
|
+
function safeFinding(error) {
|
|
158
|
+
return error instanceof Error && error.message.length > 0
|
|
159
|
+
? error.message.slice(0, MAX_STRING_LENGTH)
|
|
160
|
+
: 'report payload validation failed';
|
|
497
161
|
}
|
|
498
|
-
function
|
|
499
|
-
|
|
162
|
+
function sameSnapshot(left, right) {
|
|
163
|
+
try {
|
|
164
|
+
return canonicalJson(left) === canonicalJson(right);
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
500
169
|
}
|
|
501
170
|
function hasExactKeys(value, keys) {
|
|
502
171
|
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
@@ -505,10 +174,14 @@ function hasExactKeys(value, keys) {
|
|
|
505
174
|
const expected = [...keys].sort();
|
|
506
175
|
return actual.length === expected.length && actual.every((key, index) => key === expected[index]);
|
|
507
176
|
}
|
|
508
|
-
function
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
177
|
+
function requireReportSnapshot(value) {
|
|
178
|
+
const keys = ['headSha', 'indexTreeSha', 'trackedContentSha256', 'untrackedContentSha256', 'worktreeIdentity'];
|
|
179
|
+
if (!hasExactKeys(value, keys))
|
|
180
|
+
throw new Error('report-operation snapshot is invalid');
|
|
181
|
+
for (const key of keys) {
|
|
182
|
+
if (typeof value[key] !== 'string')
|
|
183
|
+
throw new Error('report-operation snapshot is invalid');
|
|
184
|
+
}
|
|
185
|
+
return value;
|
|
513
186
|
}
|
|
514
187
|
//# sourceMappingURL=contained-report-operation.js.map
|