@bridge_gpt/mcp-server 0.2.62 → 0.2.66
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/README.md +66 -15
- package/build/agent-launchers/claude-executor-adapter.js +145 -20
- package/build/agent-launchers/executor-adapter.js +81 -2
- package/build/agents.generated.js +10 -1
- package/build/bounded-wait.js +32 -1
- package/build/bridge-config.js +104 -0
- package/build/codex-bridge-profile.js +10 -1
- package/build/commands.generated.js +7 -6
- package/build/conductor/bridge-api-client.js +565 -32
- package/build/conductor/bring-up-facts.js +3 -1
- package/build/conductor/doctor.js +23 -14
- package/build/conductor/epic-child-continuation.js +351 -0
- package/build/conductor/epic-implementer-cli.js +154 -17
- package/build/conductor/git-hooks.js +529 -118
- package/build/conductor/guided-repair.js +323 -0
- package/build/conductor/hook-runtime-qualification.js +340 -0
- package/build/conductor/readiness-cli.js +30 -9
- package/build/conductor/readiness-sections.js +5 -4
- package/build/conductor/readiness.js +45 -12
- package/build/conductor/recovery-cli.js +859 -27
- package/build/conductor/recovery-operations.js +574 -5
- package/build/conductor/repair-contract.js +69 -3
- package/build/conductor-bundle-artifacts.js +1 -0
- package/build/credential-store.js +52 -13
- package/build/diagnostic-text.js +36 -0
- package/build/docs.generated.js +3 -2
- package/build/doctor.js +147 -21
- package/build/drive-epic-key.js +12 -0
- package/build/drive-epic.js +1537 -125
- package/build/epic-coherence-input-cli.js +137 -0
- package/build/epic-coherence-input.js +920 -0
- package/build/epic-coherence-input.test-fixtures.js +175 -0
- package/build/epic-implementer-bin.js +41 -33
- package/build/epic-implementer-bundle-cli.js +14 -1
- package/build/epic-launch-diagnostic.js +31 -0
- package/build/epic-launch-profile.js +58 -19
- package/build/epic-run-status.js +5 -0
- package/build/epic-shared/bridge-client.js +46 -4
- package/build/executor/cli.js +39 -0
- package/build/executor/completion-contract.js +423 -0
- package/build/executor/completion-supervision.js +291 -0
- package/build/executor/correction-dispatch.js +60 -0
- package/build/executor/credentials.js +3 -1
- package/build/executor/deps.js +65 -1
- package/build/executor/env.js +12 -0
- package/build/executor/heartbeat.js +13 -3
- package/build/executor/http-client.js +91 -4
- package/build/executor/integration-job.js +6 -1
- package/build/executor/job-errors.js +61 -4
- package/build/executor/job-log-registry.js +340 -73
- package/build/executor/job-runner.js +1550 -55
- package/build/executor/job-types.js +9 -6
- package/build/executor/preflight.js +189 -1
- package/build/executor/process-containment.js +756 -0
- package/build/executor/process.js +235 -5
- package/build/executor/prompt-spec.js +366 -1
- package/build/executor/provider-checks.js +69 -0
- package/build/executor/rebase-finalization.js +351 -0
- package/build/executor/reconcile-overlap-job.js +77 -84
- package/build/executor/required-verification.js +313 -0
- package/build/executor/required-work-ledger.js +1121 -0
- package/build/executor/results.js +142 -0
- package/build/executor/resume-pre-spawn.js +87 -4
- package/build/executor/runner.js +168 -0
- package/build/executor/stale-artifacts.js +5 -4
- package/build/executor/test-declaration.js +20 -0
- package/build/executor/types.js +13 -0
- package/build/executor/watch-cli.js +35 -8
- package/build/executor/worker-command.js +70 -34
- package/build/executor/worker-finalization.js +168 -26
- package/build/executor/worker-guard-hook.js +75 -14
- package/build/executor/worker-log.js +137 -70
- package/build/executor/worktree-gc.js +30 -6
- package/build/executor/worktree.js +13 -0
- package/build/executor-launch.js +658 -0
- package/build/index.js +594 -1152
- package/build/init.js +107 -4
- package/build/install-bridge-conductor.js +231 -41
- package/build/install-bridge.js +284 -20
- package/build/install-doctor.js +172 -30
- package/build/install-reexec.js +69 -7
- package/build/macos-terminal-permission-guidance.js +16 -0
- package/build/merge-pull-request.js +1 -1
- package/build/model-thresholds-tool.js +185 -0
- package/build/pipelines.generated.js +9 -8
- package/build/plan-discovery.js +137 -0
- package/build/plan-epic-implementer-eligibility.js +2 -2
- package/build/plane/cli.js +37 -3
- package/build/plane/member-logs.js +3 -0
- package/build/plane/member-roster.js +7 -1
- package/build/plane/shutdown.js +59 -1
- package/build/plane/status.js +4 -0
- package/build/plane/supervisor.js +146 -0
- package/build/plane/worker-exit-code.js +2 -0
- package/build/project-root.js +99 -0
- package/build/readme.generated.js +1 -1
- package/build/review-tickets.js +156 -13
- package/build/scripts/executor-protocol-contract-driver.js +13 -3
- package/build/setup-epic.js +208 -125
- package/build/sfcc/setup-status.js +2 -0
- package/build/sfcc/tool-wrapper.js +2 -0
- package/build/sfcc/tools-configured.js +96 -0
- package/build/start-tickets.js +163 -58
- package/build/upgrade-cli.js +109 -24
- package/build/version.generated.js +3 -3
- package/build/windows-npm-entry.js +103 -0
- package/build/worker-guard-hook-bin.js +5 -4
- package/build/worktree-core.js +6 -5
- package/docs/CONDUCTOR.md +116 -2
- package/docs/install/mcp-tool-integrations.md +1 -0
- package/docs/install/sfcc-integration.md +12 -7
- package/package.json +2 -1
- package/pipelines/implement-ticket.json +1 -1
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The required-work completion-evidence contract (BAPI-1301, D4a) — executor
|
|
3
|
+
* mirror of `src/python/llms/executor_completion_contract.py`.
|
|
4
|
+
*
|
|
5
|
+
* A covered child attempt (`implement`, `resume`, `remediate`, `ci_fix`) that
|
|
6
|
+
* was claimed under the `completion_evidence.v1` requirement reports its
|
|
7
|
+
* terminal outcome with one bounded, versioned envelope built from facts the
|
|
8
|
+
* EXECUTOR observed: the stream-json lifecycle it read to EOF, the processes it
|
|
9
|
+
* swept, and the finalization facts it verified. The server re-validates the
|
|
10
|
+
* envelope against the claimed row and refuses success unless the envelope
|
|
11
|
+
* proves the attempt finished its required work and cleaned up after itself.
|
|
12
|
+
*
|
|
13
|
+
* What the envelope proves, and what it does not:
|
|
14
|
+
* - A task that ended `succeeded` proves its LIFECYCLE ended. It does not prove
|
|
15
|
+
* the check it ran is correct or that an effect it attempted was authorized.
|
|
16
|
+
* Declared checks are run by the executor itself (D4c) and reported through
|
|
17
|
+
* `verification`.
|
|
18
|
+
* - `cleanup.verdict: "confirmed"` covers processes that kept the inherited
|
|
19
|
+
* environment. Every covered verdict names `residual_not_covered`: a daemon
|
|
20
|
+
* that detaches AND clears its environment carries no tag and is invisible to
|
|
21
|
+
* the sweep on macOS. The controlled workload excludes it.
|
|
22
|
+
*
|
|
23
|
+
* Nothing in the envelope is a prompt, a raw tool argument, a provider payload,
|
|
24
|
+
* a process argument, an environment value, a path, or a URL. Inputs and targets
|
|
25
|
+
* cross the wire only as SHA-256 digests.
|
|
26
|
+
*/
|
|
27
|
+
import { createHash } from "node:crypto";
|
|
28
|
+
/** The capability string declared on `POST /claim` and required by covered jobs. */
|
|
29
|
+
export const COMPLETION_EVIDENCE_CAPABILITY = "completion_evidence.v1";
|
|
30
|
+
export const COMPLETION_CONTRACT_VERSION = 1;
|
|
31
|
+
/**
|
|
32
|
+
* BAPI-1302 (D4c) — the envelope version that carries executor-run check facts
|
|
33
|
+
* in `verification_facts`. Sent for every job whose payload carries a
|
|
34
|
+
* required-verification declaration; version 1 otherwise.
|
|
35
|
+
*/
|
|
36
|
+
export const COMPLETION_CONTRACT_VERSION_V2 = 2;
|
|
37
|
+
/**
|
|
38
|
+
* BAPI-1302 (D4c) — declared on `POST /claim` when this executor runs declared
|
|
39
|
+
* checks itself. A job carrying a declaration requires it, so an executor
|
|
40
|
+
* without the runner never claims one.
|
|
41
|
+
*/
|
|
42
|
+
export const REQUIRED_VERIFICATION_CAPABILITY = "required_verification.v1";
|
|
43
|
+
export const COMPLETION_CONTRACT_MARKER_VALUES = ["completion_evidence.v1", "legacy"];
|
|
44
|
+
export const COVERED_JOB_TYPES = ["implement", "resume", "remediate", "ci_fix"];
|
|
45
|
+
/**
|
|
46
|
+
* The measured runtime identity (BAPI-1292 probe): Claude Code 2.1.282 on macOS
|
|
47
|
+
* arm64. A measured baseline, not a certification — any other CLI version or
|
|
48
|
+
* platform is unsupported until measured.
|
|
49
|
+
*/
|
|
50
|
+
export const SUPPORTED_COMPLETION_RUNTIME = {
|
|
51
|
+
adapter: "claude",
|
|
52
|
+
cli_version: "2.1.282",
|
|
53
|
+
platform: "darwin-arm64",
|
|
54
|
+
event_schema: "claude-stream-json.lifecycle.v1",
|
|
55
|
+
};
|
|
56
|
+
export const EVIDENCE_VALUES = ["sufficient", "insufficient"];
|
|
57
|
+
export const INSUFFICIENT_REASON_VALUES = [
|
|
58
|
+
"no_init_event",
|
|
59
|
+
"no_result_turn",
|
|
60
|
+
"active_turn_at_close",
|
|
61
|
+
"live_required_work",
|
|
62
|
+
"stale_task_snapshot",
|
|
63
|
+
"late_activity_after_close",
|
|
64
|
+
"stdout_not_read_to_eof",
|
|
65
|
+
"stream_parse_error",
|
|
66
|
+
"stream_lost",
|
|
67
|
+
"stream_oversize",
|
|
68
|
+
"stream_truncated",
|
|
69
|
+
"event_conflict",
|
|
70
|
+
"event_unknown",
|
|
71
|
+
"ledger_capacity_exhausted",
|
|
72
|
+
"denial_capacity_exhausted",
|
|
73
|
+
"forced_kill_no_cli_teardown",
|
|
74
|
+
"no_process_exit",
|
|
75
|
+
"transport_failed_before_work",
|
|
76
|
+
"transport_failed_after_progress",
|
|
77
|
+
"allowance_exhausted",
|
|
78
|
+
"unsupported_runtime",
|
|
79
|
+
"binding_mismatch",
|
|
80
|
+
// Failed before any worker was launched: nothing to be incomplete, nothing to
|
|
81
|
+
// clean up. Alone, it is not a completion cause (see isNotLaunchedEnvelope).
|
|
82
|
+
"worker_not_launched",
|
|
83
|
+
// BAPI-1302: the job's required-verification declaration was unusable, so no
|
|
84
|
+
// worker was launched against a partial set of checks.
|
|
85
|
+
"declaration_refused",
|
|
86
|
+
];
|
|
87
|
+
export const EXECUTION_VALUES = ["complete", "incomplete", "unknown"];
|
|
88
|
+
/**
|
|
89
|
+
* `not_declared` is "no declared local verification" — NOT a pass. `unavailable`
|
|
90
|
+
* means checks were declared but no runner is installed (D4c absent): a park.
|
|
91
|
+
*/
|
|
92
|
+
export const VERIFICATION_VALUES = [
|
|
93
|
+
"not_declared",
|
|
94
|
+
"passed",
|
|
95
|
+
"failed",
|
|
96
|
+
"unavailable",
|
|
97
|
+
"incomplete",
|
|
98
|
+
];
|
|
99
|
+
export const FINALIZATION_VALUES = ["ok", "unmet", "not_required", "unknown"];
|
|
100
|
+
export const CLEANUP_VERDICT_VALUES = ["confirmed", "failed", "unconfirmed"];
|
|
101
|
+
export const CLEANUP_RESIDUAL_ENV_SCRUBBING_DAEMON = "environment_scrubbing_daemon";
|
|
102
|
+
/**
|
|
103
|
+
* The second accepted residual (BAPI-1301). macOS hides a platform (Apple-signed)
|
|
104
|
+
* binary's environment from `ps -E`, so `/bin/bash`, `/bin/zsh` or `/bin/sleep`
|
|
105
|
+
* shows no tag. Tracked descendants cover the common case; a platform-binary
|
|
106
|
+
* process that detaches from the tracked tree between samples (reparented to
|
|
107
|
+
* launchd) is not attributable. Named on every verdict; the server still
|
|
108
|
+
* requires only the scrubbing-daemon token.
|
|
109
|
+
*/
|
|
110
|
+
export const CLEANUP_RESIDUAL_PLATFORM_BINARY_ENV_HIDDEN = "platform_binary_environment_hidden";
|
|
111
|
+
export const CLEANUP_REASON_VALUES = [
|
|
112
|
+
"survivors_detected",
|
|
113
|
+
"enumeration_failed",
|
|
114
|
+
"ownership_unverifiable",
|
|
115
|
+
"executor_interrupted",
|
|
116
|
+
"sweep_deadline_exhausted",
|
|
117
|
+
"not_run",
|
|
118
|
+
];
|
|
119
|
+
export const TASK_KIND_VALUES = [
|
|
120
|
+
"delegate",
|
|
121
|
+
"delegate_bg",
|
|
122
|
+
"bash_bg",
|
|
123
|
+
"monitor",
|
|
124
|
+
"bash_task",
|
|
125
|
+
"subagent_bash",
|
|
126
|
+
"task",
|
|
127
|
+
];
|
|
128
|
+
export const TASK_STATE_VALUES = [
|
|
129
|
+
"pending",
|
|
130
|
+
"succeeded",
|
|
131
|
+
"failed",
|
|
132
|
+
"stopped",
|
|
133
|
+
"withdrawn",
|
|
134
|
+
"replaced",
|
|
135
|
+
"unknown",
|
|
136
|
+
"conflict",
|
|
137
|
+
];
|
|
138
|
+
/** The only states that resolve a REQUIRED task. */
|
|
139
|
+
export const TASK_RESOLVING_STATES = new Set([
|
|
140
|
+
"succeeded",
|
|
141
|
+
"replaced",
|
|
142
|
+
]);
|
|
143
|
+
export const DENIAL_OPERATION_VALUES = [
|
|
144
|
+
"push",
|
|
145
|
+
"pr_create",
|
|
146
|
+
"external_mutation",
|
|
147
|
+
"delegate",
|
|
148
|
+
"other",
|
|
149
|
+
"unknown",
|
|
150
|
+
];
|
|
151
|
+
export const DENIAL_STATE_VALUES = ["blocking", "historical", "unlinked", "ambiguous"];
|
|
152
|
+
export const MISSING_CAPABILITY_VALUES = [
|
|
153
|
+
"declared_check_runner",
|
|
154
|
+
"permission_enforcement",
|
|
155
|
+
"focused_ci_repair",
|
|
156
|
+
"repaired_head_convergence",
|
|
157
|
+
"continuation",
|
|
158
|
+
];
|
|
159
|
+
/** BAPI-1302 — where a job's declaration came from. Neither `absent` nor `declared_empty` is a pass. */
|
|
160
|
+
export const DECLARATION_PROVENANCE_VALUES = ["declared", "declared_empty", "absent", "refused"];
|
|
161
|
+
/** BAPI-1302 — how one declared command ended, as the executor observed it. */
|
|
162
|
+
export const CHECK_OUTCOME_VALUES = [
|
|
163
|
+
"exited_zero",
|
|
164
|
+
"exited_nonzero",
|
|
165
|
+
"timed_out",
|
|
166
|
+
"launch_error",
|
|
167
|
+
"cancelled",
|
|
168
|
+
"not_run",
|
|
169
|
+
];
|
|
170
|
+
/** BAPI-1302 — bounded reason for a non-zero outcome. Never exit text or output. */
|
|
171
|
+
export const CHECK_REASON_VALUES = [
|
|
172
|
+
"declared_timeout",
|
|
173
|
+
"reservation_exhausted",
|
|
174
|
+
"stopped_after_interruption",
|
|
175
|
+
"ownership_lost",
|
|
176
|
+
"executor_cancelled",
|
|
177
|
+
"spawn_failed",
|
|
178
|
+
];
|
|
179
|
+
/**
|
|
180
|
+
* BAPI-1302 — what the executor saw of the final local artifact (full HEAD plus
|
|
181
|
+
* the TRACKED tree and index) around the checks. Only `stable` lets a result
|
|
182
|
+
* count. Untracked files are outside the guarantee.
|
|
183
|
+
*/
|
|
184
|
+
export const ARTIFACT_VALUES = [
|
|
185
|
+
"stable",
|
|
186
|
+
"not_observed",
|
|
187
|
+
"head_unreadable",
|
|
188
|
+
"dirty_before",
|
|
189
|
+
"head_moved",
|
|
190
|
+
"tracked_changed",
|
|
191
|
+
"unobservable_after",
|
|
192
|
+
];
|
|
193
|
+
export const MAX_CHECKS = 16;
|
|
194
|
+
export const MAX_CHECK_DURATION_MS = 2 * 60 * 60 * 1000;
|
|
195
|
+
export const COMPLETION_CAUSE_VALUES = [
|
|
196
|
+
"required_work_incomplete",
|
|
197
|
+
"required_check_failed",
|
|
198
|
+
"permission_stop",
|
|
199
|
+
"unsupported_completion_evidence",
|
|
200
|
+
"cleanup_unconfirmed",
|
|
201
|
+
"verification_runner_unavailable",
|
|
202
|
+
];
|
|
203
|
+
export const MAX_TASKS = 64;
|
|
204
|
+
export const MAX_DENIALS = 32;
|
|
205
|
+
export const MAX_IDENTIFIER_CHARS = 128;
|
|
206
|
+
export const MAX_SERIALIZED_BYTES = 32 * 1024;
|
|
207
|
+
/** One stream-json event larger than this is `stream_oversize`, never parsed. */
|
|
208
|
+
export const MAX_EVENT_BYTES = 1024 * 1024;
|
|
209
|
+
const IDENTIFIER_RE = /^[A-Za-z0-9][A-Za-z0-9_.:-]*$/;
|
|
210
|
+
export function sha256Hex(value) {
|
|
211
|
+
return createHash("sha256").update(value, "utf8").digest("hex");
|
|
212
|
+
}
|
|
213
|
+
/** A stable digest of a structured value (sorted keys). Never the value itself. */
|
|
214
|
+
export function digestStructured(value) {
|
|
215
|
+
return sha256Hex(stableStringify(value));
|
|
216
|
+
}
|
|
217
|
+
function stableStringify(value) {
|
|
218
|
+
if (value === null || typeof value !== "object")
|
|
219
|
+
return JSON.stringify(value ?? null);
|
|
220
|
+
if (Array.isArray(value))
|
|
221
|
+
return `[${value.map(stableStringify).join(",")}]`;
|
|
222
|
+
const entries = Object.entries(value)
|
|
223
|
+
.filter(([, v]) => v !== undefined)
|
|
224
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
225
|
+
return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`).join(",")}}`;
|
|
226
|
+
}
|
|
227
|
+
/** An opaque identifier, or null when the value cannot be carried safely. */
|
|
228
|
+
export function boundedIdentifier(value) {
|
|
229
|
+
if (typeof value !== "string")
|
|
230
|
+
return null;
|
|
231
|
+
if (value.length === 0 || value.length > MAX_IDENTIFIER_CHARS)
|
|
232
|
+
return null;
|
|
233
|
+
return IDENTIFIER_RE.test(value) ? value : null;
|
|
234
|
+
}
|
|
235
|
+
export function isCoveredJobType(jobType) {
|
|
236
|
+
return COVERED_JOB_TYPES.includes(jobType);
|
|
237
|
+
}
|
|
238
|
+
/** Whether a claimed job carries the covered completion contract. */
|
|
239
|
+
export function claimRequiresCompletionEvidence(requiredCapabilities) {
|
|
240
|
+
return Array.isArray(requiredCapabilities) && requiredCapabilities.includes(COMPLETION_EVIDENCE_CAPABILITY);
|
|
241
|
+
}
|
|
242
|
+
export function runtimeMatchesSupported(runtime) {
|
|
243
|
+
if (!runtime)
|
|
244
|
+
return false;
|
|
245
|
+
return (runtime.adapter === SUPPORTED_COMPLETION_RUNTIME.adapter &&
|
|
246
|
+
runtime.cli_version === SUPPORTED_COMPLETION_RUNTIME.cli_version &&
|
|
247
|
+
runtime.platform === SUPPORTED_COMPLETION_RUNTIME.platform &&
|
|
248
|
+
runtime.event_schema === SUPPORTED_COMPLETION_RUNTIME.event_schema);
|
|
249
|
+
}
|
|
250
|
+
export function unresolvedRequiredTasks(envelope) {
|
|
251
|
+
return envelope.tasks.filter((t) => t.required && !TASK_RESOLVING_STATES.has(t.state));
|
|
252
|
+
}
|
|
253
|
+
const FULL_SHA_RE = /^[0-9a-f]{40}$/;
|
|
254
|
+
/**
|
|
255
|
+
* The verification verdict version-2 facts support. Mirrors the server's
|
|
256
|
+
* `verification_verdict_from_facts` clause for clause; the envelope's
|
|
257
|
+
* `verification` token must equal it or the server refuses the envelope.
|
|
258
|
+
*
|
|
259
|
+
* `declaredCount` is the number of commands the job's declaration names; a
|
|
260
|
+
* declared command with no fact is not-run, so the verdict is `incomplete`.
|
|
261
|
+
*/
|
|
262
|
+
export function verificationVerdictFromFacts(facts, declaredCount) {
|
|
263
|
+
if (facts.provenance === "absent" || facts.provenance === "declared_empty")
|
|
264
|
+
return "not_declared";
|
|
265
|
+
if (facts.provenance !== "declared")
|
|
266
|
+
return "incomplete";
|
|
267
|
+
const expected = declaredCount ?? facts.commands.length;
|
|
268
|
+
if (expected === 0 || facts.commands.length !== expected)
|
|
269
|
+
return "incomplete";
|
|
270
|
+
if (facts.artifact !== "stable" || facts.tested_head_sha === null || !FULL_SHA_RE.test(facts.tested_head_sha)) {
|
|
271
|
+
return "incomplete";
|
|
272
|
+
}
|
|
273
|
+
if (facts.commands.some((c) => c.outcome !== "exited_zero" && c.outcome !== "exited_nonzero"))
|
|
274
|
+
return "incomplete";
|
|
275
|
+
if (facts.commands.some((c) => c.outcome === "exited_nonzero"))
|
|
276
|
+
return "failed";
|
|
277
|
+
return "passed";
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Closed reason the envelope cannot support a covered success, or null.
|
|
281
|
+
* Mirrors the server's `success_refusal_reason` clause for clause, so the
|
|
282
|
+
* executor never sends `/complete` for an envelope the server would refuse.
|
|
283
|
+
*/
|
|
284
|
+
export function successRefusalReason(envelope) {
|
|
285
|
+
if (envelope.evidence !== "sufficient")
|
|
286
|
+
return "evidence_insufficient";
|
|
287
|
+
if (envelope.execution !== "complete")
|
|
288
|
+
return "execution_not_complete";
|
|
289
|
+
if (unresolvedRequiredTasks(envelope).length > 0)
|
|
290
|
+
return "required_task_unresolved";
|
|
291
|
+
if (envelope.verification !== "passed" && envelope.verification !== "not_declared") {
|
|
292
|
+
return "verification_not_acceptable";
|
|
293
|
+
}
|
|
294
|
+
if (envelope.finalization !== "ok" && envelope.finalization !== "not_required") {
|
|
295
|
+
return "finalization_not_met";
|
|
296
|
+
}
|
|
297
|
+
if (envelope.cleanup.verdict !== "confirmed" || envelope.cleanup.survivors !== 0) {
|
|
298
|
+
return "cleanup_not_confirmed";
|
|
299
|
+
}
|
|
300
|
+
if (envelope.denials.some((d) => d.state === "blocking"))
|
|
301
|
+
return "blocking_denial";
|
|
302
|
+
if (envelope.cause !== null)
|
|
303
|
+
return "cause_present";
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* The executor-proposed cause for an envelope that cannot be a success. Same
|
|
308
|
+
* order as the server's `derive_park_cause`; the server re-derives it and never
|
|
309
|
+
* lets this value widen what the rest of the envelope proves.
|
|
310
|
+
*/
|
|
311
|
+
export function deriveCompletionCause(envelope) {
|
|
312
|
+
const reasons = new Set(envelope.insufficient_reasons);
|
|
313
|
+
if (reasons.has("unsupported_runtime"))
|
|
314
|
+
return "unsupported_completion_evidence";
|
|
315
|
+
if (isNotLaunchedEnvelope(envelope))
|
|
316
|
+
return null;
|
|
317
|
+
const blocking = envelope.denials.filter((d) => d.state === "blocking");
|
|
318
|
+
if (blocking.length > 0 && envelope.evidence === "sufficient" && blocking.some((d) => d.result_after_denial)) {
|
|
319
|
+
return "permission_stop";
|
|
320
|
+
}
|
|
321
|
+
if (envelope.cleanup.verdict !== "confirmed" || envelope.cleanup.survivors !== 0)
|
|
322
|
+
return "cleanup_unconfirmed";
|
|
323
|
+
const unresolved = envelope.tasks.some((t) => t.required && !TASK_RESOLVING_STATES.has(t.state));
|
|
324
|
+
if (blocking.length > 0 ||
|
|
325
|
+
envelope.evidence !== "sufficient" ||
|
|
326
|
+
envelope.execution !== "complete" ||
|
|
327
|
+
unresolved ||
|
|
328
|
+
envelope.verification === "incomplete") {
|
|
329
|
+
return "required_work_incomplete";
|
|
330
|
+
}
|
|
331
|
+
if (envelope.verification === "unavailable")
|
|
332
|
+
return "verification_runner_unavailable";
|
|
333
|
+
if (envelope.verification === "failed")
|
|
334
|
+
return "required_check_failed";
|
|
335
|
+
// Nothing completion-specific is wrong: an unmet finalization (or success)
|
|
336
|
+
// keeps its existing executor/server classification.
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Whether an envelope reports a failure before any worker was launched: exactly
|
|
341
|
+
* the `worker_not_launched` reason, no task/denial/result, confirmed cleanup with
|
|
342
|
+
* zero survivors. Mirrors the server's `is_not_launched_envelope`.
|
|
343
|
+
*/
|
|
344
|
+
export function isNotLaunchedEnvelope(envelope) {
|
|
345
|
+
return (envelope.insufficient_reasons.length === 1 &&
|
|
346
|
+
envelope.insufficient_reasons[0] === "worker_not_launched" &&
|
|
347
|
+
envelope.tasks.length === 0 &&
|
|
348
|
+
envelope.denials.length === 0 &&
|
|
349
|
+
envelope.results_seen === 0 &&
|
|
350
|
+
envelope.cleanup.verdict === "confirmed" &&
|
|
351
|
+
envelope.cleanup.survivors === 0);
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* The envelope for a covered attempt that failed before any worker existed.
|
|
355
|
+
*
|
|
356
|
+
* Every covered terminal report must carry an envelope; without one the server
|
|
357
|
+
* refuses the report and the row eventually dead-letters as cleanup
|
|
358
|
+
* unconfirmed — parking a transient pre-spawn problem for a human with a
|
|
359
|
+
* misleading cause. Nothing was spawned, so cleanup is truthfully confirmed with
|
|
360
|
+
* zero survivors, and the report's own error kind keeps deciding.
|
|
361
|
+
*/
|
|
362
|
+
export function buildNotLaunchedEnvelope(input) {
|
|
363
|
+
return {
|
|
364
|
+
evidence_version: input.verificationFacts ? COMPLETION_CONTRACT_VERSION_V2 : COMPLETION_CONTRACT_VERSION,
|
|
365
|
+
runtime: { ...input.runtime },
|
|
366
|
+
binding: { ...input.binding },
|
|
367
|
+
session_id: null,
|
|
368
|
+
evidence: "insufficient",
|
|
369
|
+
insufficient_reasons: ["worker_not_launched"],
|
|
370
|
+
execution: "unknown",
|
|
371
|
+
verification: input.verification,
|
|
372
|
+
finalization: "unknown",
|
|
373
|
+
cleanup: {
|
|
374
|
+
verdict: "confirmed",
|
|
375
|
+
method: "env_tag_sweep",
|
|
376
|
+
survivors: 0,
|
|
377
|
+
reasons: [],
|
|
378
|
+
residual_not_covered: [...input.residuals],
|
|
379
|
+
},
|
|
380
|
+
tasks: [],
|
|
381
|
+
denials: [],
|
|
382
|
+
missing_capabilities: [],
|
|
383
|
+
results_seen: 0,
|
|
384
|
+
cause: null,
|
|
385
|
+
...(input.verificationFacts ? { verification_facts: input.verificationFacts } : {}),
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Enforce the serialized bound. An envelope that would exceed it drops its
|
|
390
|
+
* lowest-value detail (resolved tasks, then historical/unlinked denials) and
|
|
391
|
+
* becomes `insufficient` — it never silently loses an unresolved obligation.
|
|
392
|
+
*/
|
|
393
|
+
export function boundEnvelope(envelope) {
|
|
394
|
+
const size = (e) => Buffer.byteLength(JSON.stringify(e), "utf8");
|
|
395
|
+
if (size(envelope) <= MAX_SERIALIZED_BYTES)
|
|
396
|
+
return envelope;
|
|
397
|
+
const markOversize = (e) => {
|
|
398
|
+
const reasons = new Set(e.insufficient_reasons);
|
|
399
|
+
reasons.add("ledger_capacity_exhausted");
|
|
400
|
+
const next = {
|
|
401
|
+
...e,
|
|
402
|
+
evidence: "insufficient",
|
|
403
|
+
insufficient_reasons: [...reasons],
|
|
404
|
+
execution: e.execution === "complete" ? "unknown" : e.execution,
|
|
405
|
+
cause: null,
|
|
406
|
+
};
|
|
407
|
+
return { ...next, cause: deriveCompletionCause(next) };
|
|
408
|
+
};
|
|
409
|
+
let next = markOversize({
|
|
410
|
+
...envelope,
|
|
411
|
+
tasks: envelope.tasks.filter((t) => t.required && !TASK_RESOLVING_STATES.has(t.state)),
|
|
412
|
+
});
|
|
413
|
+
if (size(next) <= MAX_SERIALIZED_BYTES)
|
|
414
|
+
return next;
|
|
415
|
+
next = markOversize({
|
|
416
|
+
...next,
|
|
417
|
+
denials: next.denials.filter((d) => d.state === "blocking" || d.state === "ambiguous"),
|
|
418
|
+
});
|
|
419
|
+
if (size(next) <= MAX_SERIALIZED_BYTES)
|
|
420
|
+
return next;
|
|
421
|
+
// Last resort: identities cannot fit. Keep the count-free verdict only.
|
|
422
|
+
return markOversize({ ...next, tasks: next.tasks.slice(0, 8), denials: next.denials.slice(0, 8) });
|
|
423
|
+
}
|