@opengeni/runtime 3.0.0-canary.0 → 3.0.1-canary.0

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.
@@ -217,12 +217,36 @@ export class ModalCommandControl {
217
217
  command: ModalProviderCommand,
218
218
  yieldTimeMs: number,
219
219
  signal?: AbortSignal,
220
+ ): Promise<ModalProviderOutputPage> {
221
+ return this.readOutput(command, yieldTimeMs, signal);
222
+ }
223
+
224
+ /** Fixed internal probes own their cancellation controller and must drain
225
+ * both reads before relinquishing the surrounding provider-operation gate.
226
+ * Retained-command readers keep their existing signal/receipt contract.
227
+ */
228
+ async readProbe(
229
+ command: ModalProviderCommand,
230
+ yieldTimeMs: number,
231
+ cancellation: AbortController,
232
+ ): Promise<ModalProviderOutputPage> {
233
+ return this.readOutput(command, yieldTimeMs, cancellation.signal, (error) =>
234
+ cancellation.abort(error),
235
+ );
236
+ }
237
+
238
+ private async readOutput(
239
+ command: ModalProviderCommand,
240
+ yieldTimeMs: number,
241
+ signal?: AbortSignal,
242
+ cancelSiblings?: (error: unknown) => void,
220
243
  ): Promise<ModalProviderOutputPage> {
221
244
  signal?.throwIfAborted();
222
245
  this.assertIdentity(command);
223
246
  const next = structuredClone(command);
224
- const pages = await Promise.all(
225
- (["stdout", "stderr"] as const).map(async (stream) => {
247
+ let failure: { error: unknown } | undefined;
248
+ const reads = (["stdout", "stderr"] as const)
249
+ .map(async (stream) => {
226
250
  const cursor = command.streams[stream];
227
251
  if (cursor.exitCode !== null) return null;
228
252
  const descriptor = stream === "stdout" ? 1 : 2;
@@ -261,8 +285,29 @@ export class ModalCommandControl {
261
285
  };
262
286
  }
263
287
  return null;
264
- }),
265
- );
288
+ })
289
+ .map((read) =>
290
+ !cancelSiblings
291
+ ? read
292
+ : read.catch((error: unknown) => {
293
+ // A logical read owns both output streams. Promise.all alone releases
294
+ // its caller on the first rejection while a sibling RPC may still be
295
+ // running. Cancel that sibling, retain the original error, and drain
296
+ // both reads before returning control to mutation/capture ownership.
297
+ failure ??= { error };
298
+ cancelSiblings(error);
299
+ throw error;
300
+ }),
301
+ );
302
+ const pages = cancelSiblings
303
+ ? await Promise.allSettled(reads).then((results) => {
304
+ if (failure) throw failure.error;
305
+ return results.map((result) => {
306
+ if (result.status === "rejected") throw result.reason;
307
+ return result.value;
308
+ });
309
+ })
310
+ : await Promise.all(reads);
266
311
  const stdoutExit = next.streams.stdout.exitCode;
267
312
  const stderrExit = next.streams.stderr.exitCode;
268
313
  if (stdoutExit !== null && stderrExit !== null && stdoutExit !== stderrExit)
@@ -11,6 +11,7 @@ import {
11
11
  admittedProviderCommandHandle,
12
12
  } from "../provider-command-session";
13
13
  import type { ModalCommandControl, ModalProviderCommand } from "./modal-command-control";
14
+ import { verifyModalMaterializedPath } from "./modal-materialization-verification";
14
15
 
15
16
  type Entry = {
16
17
  command: ModalProviderCommand;
@@ -33,6 +34,7 @@ export function installModalCommandSession(
33
34
  session: ProviderCommandSession & {
34
35
  execCommand?: ChannelASession["execCommand"];
35
36
  writeStdin?: ChannelASession["writeStdin"];
37
+ verifyMaterializedPath?: (path: string, workdir: string) => Promise<void>;
36
38
  },
37
39
  control: ModalCommandControl,
38
40
  ): void {
@@ -41,6 +43,8 @@ export function installModalCommandSession(
41
43
  const originalWrite = session.writeStdin?.bind(session);
42
44
  const cancelLegacyStart = session.cancelPendingExecCommand?.bind(session);
43
45
  const pendingStarts = new Set<AbortController>();
46
+ session.verifyMaterializedPath = (path, workdir) =>
47
+ verifyModalMaterializedPath(control, path, workdir, pendingStarts);
44
48
  const entries = new Map<number, Entry>();
45
49
  // Current SDK setup commands are not retained/admitted commands. Give their
46
50
  // live observer handles a disjoint, adapter-local range; never use a missing
@@ -0,0 +1,122 @@
1
+ import {
2
+ SandboxMaterializationVerificationError,
3
+ retainMaterializationVerificationDiagnostic,
4
+ type MaterializationVerificationDiagnostic,
5
+ } from "../materialization-verification-error";
6
+ import type { ModalCommandControl, ModalProviderCommand } from "./modal-command-control";
7
+
8
+ const MARKER = "__OPENGENI_MATERIALIZED_PATH_VISIBLE__";
9
+ const OUTPUT_LIMIT = 16 * 1024;
10
+
11
+ /** A fixed read-only probe, not an admitted user command. Advance its private
12
+ * provider cursor locally; never borrow the surrounding mutation's retained
13
+ * handle or relax retained-command persistence requirements. The caller keeps
14
+ * the original backend and provider-operation gate for the entire observation.
15
+ */
16
+ export async function verifyModalMaterializedPath(
17
+ control: Pick<ModalCommandControl, "start" | "readProbe">,
18
+ path: string,
19
+ workdir: string,
20
+ pending: Set<AbortController>,
21
+ observationTimeoutMs = 30_000,
22
+ ): Promise<void> {
23
+ const quote = (value: string) => `'${value.replaceAll("'", `'"'"'`)}'`;
24
+ const command = `test -e ${quote(path)} && printf %s ${quote(MARKER)}`;
25
+ const cancellation = new AbortController();
26
+ const deadlineReason = new Error("Materialization visibility observation deadline exceeded");
27
+ let providerCommand: ModalProviderCommand | undefined;
28
+ let stdout = "";
29
+ let stderr = "";
30
+ const diagnostic = (
31
+ reason: MaterializationVerificationDiagnostic["reason"],
32
+ exitCode: number | null = null,
33
+ ): MaterializationVerificationDiagnostic => ({
34
+ reason,
35
+ path,
36
+ workdir,
37
+ command,
38
+ output: (stdout + stderr).slice(0, OUTPUT_LIMIT),
39
+ exitCode,
40
+ providerSessionId: null,
41
+ ...(providerCommand
42
+ ? {
43
+ providerExecution: {
44
+ sandboxId: providerCommand.sandboxId,
45
+ taskId: providerCommand.taskId,
46
+ execId: providerCommand.execId,
47
+ },
48
+ }
49
+ : {}),
50
+ });
51
+ const timer = setTimeout(() => {
52
+ cancellation.abort(deadlineReason);
53
+ }, observationTimeoutMs);
54
+ timer.unref();
55
+ pending.add(cancellation);
56
+ try {
57
+ providerCommand = await control.start(
58
+ { cmd: command, workdir, shell: "sh", login: false, tty: false },
59
+ cancellation.signal,
60
+ );
61
+ for (;;) {
62
+ cancellation.signal.throwIfAborted();
63
+ const page = await control.readProbe(providerCommand, 1_000, cancellation);
64
+ // Cancellation ends observation, not the process. Never accept a late
65
+ // success after the attempt requested cancellation.
66
+ cancellation.signal.throwIfAborted();
67
+ if (
68
+ page.command.sandboxId !== providerCommand.sandboxId ||
69
+ page.command.taskId !== providerCommand.taskId ||
70
+ page.command.execId !== providerCommand.execId
71
+ ) {
72
+ throw new SandboxMaterializationVerificationError(diagnostic("invalid_response"));
73
+ }
74
+ providerCommand = page.command;
75
+ for (const chunk of page.chunks) {
76
+ if (chunk.stream === "stdout") stdout += chunk.text;
77
+ else stderr += chunk.text;
78
+ }
79
+ if (stdout.length + stderr.length > OUTPUT_LIMIT) {
80
+ throw new SandboxMaterializationVerificationError(diagnostic("invalid_response"));
81
+ }
82
+ if (page.exitCode === null) {
83
+ // An empty provider page may return immediately. Yield to cancellation
84
+ // and the deadline instead of starving timers with resolved promises.
85
+ await new Promise<void>((resolve) => setImmediate(resolve));
86
+ continue;
87
+ }
88
+ if (page.exitCode === 0 && stdout === MARKER) return;
89
+ throw new SandboxMaterializationVerificationError(
90
+ diagnostic(
91
+ page.exitCode === 1
92
+ ? "path_not_visible"
93
+ : page.exitCode === 0
94
+ ? "invalid_response"
95
+ : "command_failed",
96
+ page.exitCode,
97
+ ),
98
+ );
99
+ }
100
+ } catch (error) {
101
+ if (cancellation.signal.reason === deadlineReason) {
102
+ throw new SandboxMaterializationVerificationError(
103
+ {
104
+ ...diagnostic("command_pending"),
105
+ causeMessage: "Observation deadline exceeded; provider process completion is unconfirmed",
106
+ },
107
+ { cause: error },
108
+ );
109
+ }
110
+ if (!(error instanceof SandboxMaterializationVerificationError)) {
111
+ retainMaterializationVerificationDiagnostic(error, {
112
+ ...diagnostic("command_error"),
113
+ causeMessage: error instanceof Error ? error.message : String(error),
114
+ });
115
+ }
116
+ throw error;
117
+ } finally {
118
+ cancellation.abort(new Error("Materialization visibility observation finished"));
119
+ clearTimeout(timer);
120
+ pending.delete(cancellation);
121
+ }
122
+ }
@@ -34,7 +34,13 @@ import {
34
34
  type ProviderCommandSession,
35
35
  } from "../provider-command-session";
36
36
  import type { SandboxProviderCommand } from "@opengeni/contracts";
37
- import { hasTypedExecHandleLoss } from "../exec-banner";
37
+ import { hasTypedExecHandleLoss, parseExecResponseBanner } from "../exec-banner";
38
+ import {
39
+ SandboxMaterializationVerificationError,
40
+ materializationVerificationDiagnostic,
41
+ retainMaterializationVerificationDiagnostic,
42
+ type MaterializationFailureReason,
43
+ } from "../materialization-verification-error";
38
44
  import { CAPABILITY_DESCRIPTORS, type SandboxBackend } from "@opengeni/contracts";
39
45
  import { SelfhostedControlError } from "../selfhosted/control-rpc";
40
46
  import {
@@ -102,6 +108,8 @@ export interface RoutableBackendSession extends ProviderCommandSession {
102
108
  pathExists?(path: string, runAs?: string): Promise<boolean>;
103
109
  viewImage?(args: unknown): Promise<unknown>;
104
110
  materializeEntry?(args: unknown): Promise<void>;
111
+ /** Provider-owned fixed read-only probe; never an agent command surface. */
112
+ verifyMaterializedPath?(path: string, workdir: string): Promise<void>;
105
113
  supportsPty?(): boolean;
106
114
  resolveExposedPort?(port: number): Promise<ExposedPortEndpoint>;
107
115
  serializeSessionState?(): Promise<unknown>;
@@ -299,6 +307,7 @@ export type RoutingSandboxOperationObservation = {
299
307
  op: string;
300
308
  outcome: "ok" | "not_found" | "failed";
301
309
  durationMs: number;
310
+ materializationFailureReason?: MaterializationFailureReason;
302
311
  };
303
312
 
304
313
  export type RoutingSandboxOperationObserver = (
@@ -1689,11 +1698,13 @@ export class RoutingSandboxSession implements RoutableBackendSession {
1689
1698
  ): Promise<T> {
1690
1699
  const startedAt = performance.now();
1691
1700
  let outcome: RoutingSandboxOperationObservation["outcome"] = "failed";
1701
+ let materializationFailureReason: MaterializationFailureReason | undefined;
1692
1702
  try {
1693
1703
  const result = await withSandboxProviderOperation(backend.session, fn, onCaptureWait);
1694
1704
  outcome = "ok";
1695
1705
  return result;
1696
1706
  } catch (error) {
1707
+ materializationFailureReason = materializationVerificationDiagnostic(error)?.reason;
1697
1708
  if (
1698
1709
  (op === "readFile" || op === "listDir" || op === "pathExists" || op === "viewImage") &&
1699
1710
  isDefinitePathNotFoundError(error)
@@ -1708,6 +1719,7 @@ export class RoutingSandboxSession implements RoutableBackendSession {
1708
1719
  op,
1709
1720
  outcome,
1710
1721
  durationMs: Math.max(0, performance.now() - startedAt),
1722
+ ...(materializationFailureReason ? { materializationFailureReason } : {}),
1711
1723
  });
1712
1724
  } catch {
1713
1725
  // Telemetry is never part of provider or durable-settlement authority.
@@ -2289,6 +2301,9 @@ async function assertProviderCanSeeMaterializedPath(
2289
2301
  session: RoutableBackendSession,
2290
2302
  path: string,
2291
2303
  ): Promise<void> {
2304
+ if (session.verifyMaterializedPath) {
2305
+ return session.verifyMaterializedPath(path, providerManifestRoot(session) ?? "/workspace");
2306
+ }
2292
2307
  const command = `test -e ${shellSingleQuote(path)} && printf %s ${shellSingleQuote(
2293
2308
  MATERIALIZED_PATH_MARKER,
2294
2309
  )}`;
@@ -2300,18 +2315,68 @@ async function assertProviderCanSeeMaterializedPath(
2300
2315
  tty: false,
2301
2316
  };
2302
2317
  let output: string;
2303
- if (session.execCommand) {
2304
- output = await session.execCommand(args);
2305
- } else if (session.exec) {
2306
- output = formatExecResult(await session.exec(args));
2307
- } else {
2318
+ let result: unknown;
2319
+ if (!session.execCommand && !session.exec) {
2308
2320
  throw new RoutingUnsupportedError("materializeEntry.verify", "unknown");
2309
2321
  }
2310
- if (!output.includes(MATERIALIZED_PATH_MARKER)) {
2311
- throw new Error(
2312
- `Sandbox materialization completed but the provider cannot read the destination path: ${path}`,
2313
- );
2322
+ try {
2323
+ result = session.execCommand ? await session.execCommand(args) : await session.exec!(args);
2324
+ } catch (cause) {
2325
+ retainMaterializationVerificationDiagnostic(cause, {
2326
+ reason: "command_error",
2327
+ path,
2328
+ workdir: args.workdir,
2329
+ command,
2330
+ output: null,
2331
+ exitCode: null,
2332
+ providerSessionId: null,
2333
+ causeMessage: cause instanceof Error ? cause.message : String(cause),
2334
+ });
2335
+ throw cause;
2336
+ }
2337
+ try {
2338
+ output = formatExecResult(result);
2339
+ } catch (cause) {
2340
+ const record = result && typeof result === "object" ? (result as Record<string, unknown>) : {};
2341
+ const returnedOutput = [record.output, record.stderr, record.stdout]
2342
+ .filter((value): value is string => typeof value === "string")
2343
+ .join("\n");
2344
+ throw new SandboxMaterializationVerificationError({
2345
+ reason: "invalid_response",
2346
+ path,
2347
+ workdir: args.workdir,
2348
+ command,
2349
+ output: returnedOutput || null,
2350
+ exitCode: null,
2351
+ providerSessionId: null,
2352
+ causeMessage: cause instanceof Error ? cause.message : String(cause),
2353
+ });
2354
+ }
2355
+ const status = parseExecResponseBanner(output);
2356
+ const markerPresent = stripExecBanner(output).includes(MATERIALIZED_PATH_MARKER);
2357
+ if (
2358
+ markerPresent &&
2359
+ (status.kind === "absent" || (status.kind === "exited" && status.exitCode === 0))
2360
+ ) {
2361
+ return;
2314
2362
  }
2363
+ const reason: MaterializationFailureReason =
2364
+ status.kind === "running"
2365
+ ? "command_pending"
2366
+ : status.kind === "exited" && status.exitCode === 1
2367
+ ? "path_not_visible"
2368
+ : status.kind === "exited" && status.exitCode !== 0
2369
+ ? "command_failed"
2370
+ : "invalid_response";
2371
+ throw new SandboxMaterializationVerificationError({
2372
+ reason,
2373
+ path,
2374
+ workdir: args.workdir,
2375
+ command,
2376
+ output,
2377
+ exitCode: status.kind === "exited" ? status.exitCode : null,
2378
+ providerSessionId: status.kind === "running" ? status.sessionId : null,
2379
+ });
2315
2380
  }
2316
2381
 
2317
2382
  function providerManifestRoot(session: RoutableBackendSession): string | null {