Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions dag/extdeps/shell/exec.dag
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,22 @@ data shell_exec_transport_ceiling_authority: ByteSize = host_exec_arg_max_strlen
// ShellSpawnRefused the streams are absent. A sibling row rather than a field added to RunArgv,
// because adding it there would hand every existing RunArgv caller empty streams on a spawn
// failure where today its evaluation stops.

// RunArgvStdin completes the family Run / RunArgv / RunArgvOutcome along the one axis they
// vary on: whether the child receives stdin. Run is argv-fixed [bash, -s] with the payload on
// stdin (the heredoc path); RunArgv takes a caller-named program and argument vector but no
// stdin; this row is the missing cell -- the SAME direct-exec shape with a stdin payload. The
// survey that located the gap (2026-10-01, before minting): the argv-plus-stdin shape already
// exists tree-wide only as TOOL-FIXED rows -- jq.Process.RunWithStdin (program fixed "jq"),
// ssh.Session.ExecPortableWordsWithStdin (program fixed "ssh"), git.Plumbing.HashObjectWriteStdin
// (program fixed "git") -- each a different service owning its own tool. None admits a
// caller-named program, so a runtime whose executable arrives as projected data (the bundled
// Codex native binary) had no typed route and fell back to the retained heredoc; that fallback
// is what this row retires. Same output triple and no-success-field discipline as RunArgv (a
// nonzero exit is DATA, decoded by the caller's policy), same transport grammar the seed's
// dispatch_shell already realizes generically (it reads declared argv children and pipes a
// declared stdin payload; no seed-side change).

service shell.Exec {
operation Run {
input { script: TransportScript }
Expand Down Expand Up @@ -153,6 +169,25 @@ service shell.Exec {
nonzero => Unit
}
}
operation RunArgvStdin {
input { program: NonEmptyStr, arguments: ProcessArgvExpansion, stdin_payload: String }

output {
exit_code: Int from "exit_code"
stdout: String from "stdout"
stderr: String from "stderr"
}

transport shell {
argv: [program, arguments]
stdin: stdin_payload
}

exit {
0 => Unit
nonzero => Unit
}
}

operation RunArgvOutcome {
input { program: NonEmptyStr, arguments: ProcessArgvExpansion }
Expand Down
224 changes: 64 additions & 160 deletions dag/gunbc/codex_app_server_press.dag
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
module gunbc.codex_app_server_press

import std.dissolution { DissolutionCondition, dissolution_description, unbound_dissolution }

import std.types { NonEmptyStr, String, FilePath, List, Bool, Int, EpochSecs }
import std.content_hash { ContentHash, content_hash_of_value }
import gunbc.provider_interface_binding {
Expand Down Expand Up @@ -72,9 +70,15 @@ import extdeps.llm.codex_app_server {
codex_structured_error_code_quota_exceeded,
codex_structured_error_code_authentication_required,
}
import gunbc.retained_shell_script { retained_foreign }
import extdeps.shell
import extdeps.shell.exec
import extdeps.tools.env { env_path_resolved_program, env_prefixed_args }
import v2.std.orchestration { EnvSet }
import v2.std.compilers.cli_surface {
ProcessArgvExpansion,
process_argv_expansion,
cli_surface_of_literal_words,
}
import extdeps.languages.json.emit {
JsonValue, JsonObject, JsonString, JsonNumber, JsonBool, JsonArray, JsonNull,
JsonKeyValue,
Expand Down Expand Up @@ -415,11 +419,12 @@ type CodexAccountStandingSummary {
// std.process.ProcessExit (ExitSuccess | ExitFailure with code and reason) models CLI/program exit
// semantics with a typed reason string. ProcessExitObservation in gunbc.provider_wire_evidence
// models shell.Exec transport (success bool + optional exit_code from the host).
// ProviderProcessExited carries only the parsed sentinel exit code while
// ProviderProcessObservationFailed means the mux marker or exit observation was absent — a
// press-parse outcome, not a host refusal reason. Converge when shell→intent typed stdio transport
// lands (docs/plans/shell-intent-emit-realization-design.md) and replaces sentinel reparsing — this
// coproduct deletes with that scaffold.
// ProviderProcessExited carries the transport's declared exit_code; ObservationFailed means the
// press never reached a process observation at all (interface binding unavailable, bundled
// executable absent) — a press-arm outcome, not a host refusal reason. The sentinel-parse
// alternative this coproduct once named is gone: the account trip runs as a typed argv through
// shell.Exec.RunArgvStdin, whose exit_code is a declared output, never parsed back out of a
// stream.

type ProviderProcessExit
= ProviderProcessExited { code: Int }
Expand Down Expand Up @@ -515,49 +520,39 @@ fn binding_from_initialize_session(
}
}

data codex_press_stdio_sentinel_mux_dissolve_note: DissolutionCondition = unbound_dissolution(description: "Interim stdout/stderr/exit capture muxes shell.Exec.Run output through literal ---GUNBC_EXIT---/---GUNBC_STDOUT---/---GUNBC_STDERR--- sentinels (codex_press_account_trip_script → split_gunbc_stdio_capture). Wrapped in retained_foreign with dissolve-on v2.workflow.bash_emit. The sentinel concat itself dissolves when shell→intent typed stdio transport replaces foreign script muxing (docs/plans/shell-intent-emit-realization-design.md Phase 2 host-effect route for runtime-present press sites).")

fn codex_press_account_trip_script(
// THE ACCOUNT TRIP IS A TYPED PROCESS, NOT A SHELL SCRIPT. The deleted shape concat-built one
// bash script (codex_press_account_trip_script) that exported CODEX_HOME, wrote the four request
// lines to temp files with printf, ran `codex app-server --stdio` with its streams redirected to
// files, and muxed exit+stdout+stderr back through literal ---GUNBC_ sentinels that
// split_gunbc_stdio_capture re-parsed out of the combined stream — wrapped in retained_foreign
// with a dissolve-on. The whole carrier is deleted with its site, per its own
// codex_press_stdio_sentinel_mux_dissolve_note, which named exactly this replacement and is
// deleted beside the concat it described. The child now spawns through shell.Exec.RunArgvStdin:
// env(1) carries the CODEX_HOME binding as an argv element (extdeps.tools.env, the one env(1)
// authority), the four request lines ride stdin as data, and stdout, stderr and the exit code
// arrive as separate declared outputs — no script body, no temp file, no sentinel, and no
// quoting for a path to escape.
fn codex_press_account_trip_request_lines() -> String {
join(
[
codex_app_server_initialize_request_line() as String,
codex_app_server_initialized_notification_line() as String,
codex_app_server_account_read_request_line() as String,
codex_app_server_rate_limits_read_request_line() as String,
],
"\n",
) + "\n"
}

fn codex_press_account_trip_invocation(
executable: FilePath,
codex_home: FilePath,
) -> String {
concat(
"export CODEX_HOME='",
concat(
codex_home as String,
concat(
"'; REQ=$(mktemp); OUT=$(mktemp); ERR=$(mktemp); ",
concat(
"printf '%s\\n' '",
concat(
codex_app_server_initialize_request_line() as String,
concat(
"' >\"$REQ\"; printf '%s\\n' '",
concat(
codex_app_server_initialized_notification_line() as String,
concat(
"' >>\"$REQ\"; printf '%s\\n' '",
concat(
codex_app_server_account_read_request_line() as String,
concat(
"' >>\"$REQ\"; printf '%s\\n' '",
concat(
codex_app_server_rate_limits_read_request_line() as String,
concat(
"' >>\"$REQ\"; cat \"$REQ\" | '",
concat(
executable as String,
"' app-server --stdio >\"$OUT\" 2>\"$ERR\"; EC=$?; echo \"---GUNBC_EXIT---$EC---\"; echo '---GUNBC_STDOUT---'; cat \"$OUT\"; echo '---GUNBC_STDERR---'; cat \"$ERR\"; rm -f \"$REQ\" \"$OUT\" \"$ERR\"; exit \"$EC\"",
),
),
),
),
),
),
),
),
),
),
) -> ProcessArgvExpansion {
process_argv_expansion(
surface: cli_surface_of_literal_words(
words: env_prefixed_args(
bindings: [EnvSet { name: "CODEX_HOME", value: codex_home as String }],
command_argv: [executable as String, "app-server", "--stdio"],
),
),
)
Expand Down Expand Up @@ -1511,61 +1506,6 @@ fn parse_account_trip_session(
}
}

type GunbcStdioCapture {
stdout: String
stderr: String
exit_code: Int?
}

fn parse_gunbc_exit_marker(combined: String) -> Int? {
let parts = split(s: combined, delimiter: "---GUNBC_EXIT---")
match parts.skip(n: 1).first() {
Absent => none
Present { value: after } => {
let code_parts = split(s: after, delimiter: "---")
match code_parts.first() {
Absent => none
Present { value: code_s } => parse_int(s: code_s.trim())
}
}
}
}

fn split_gunbc_stdio_capture(combined: String) -> GunbcStdioCapture {
let exit_code = parse_gunbc_exit_marker(combined: combined)
let parts = split(s: combined, delimiter: "---GUNBC_STDOUT---")
match parts.skip(n: 1).first() {
Absent =>
GunbcStdioCapture { stdout: combined, stderr: "", exit_code: exit_code }
Present { value: after_marker } => {
let err_parts = split(s: after_marker, delimiter: "---GUNBC_STDERR---")
match err_parts.first() {
Absent =>
GunbcStdioCapture {
stdout: after_marker.trim(),
stderr: "",
exit_code: exit_code,
}
Present { value: out } =>
match err_parts.skip(n: 1).first() {
Absent =>
GunbcStdioCapture {
stdout: out.trim(),
stderr: "",
exit_code: exit_code,
}
Present { value: err } =>
GunbcStdioCapture {
stdout: out.trim(),
stderr: err.trim(),
exit_code: exit_code,
}
}
}
}
}
}

fn preflight_subject_from_bundle(
bundle: CodexRuntimeBundle,
binding: ProviderInterfaceBinding,
Expand Down Expand Up @@ -1633,32 +1573,6 @@ fn parse_account_trip_stdout_for_tests(stdout: String) -> CodexAccountStandingEv
)
}

fn process_receipt_from_capture(
capture: GunbcStdioCapture,
wire: ProviderWireEvidenceReceipt?,
run_success: Bool,
) -> ProviderProcessReceipt {
match capture.exit_code {
Present { value: code } =>
ProviderProcessReceipt {
exit: ProviderProcessExited { code: code },
wire: wire,
}
Absent =>
if run_success {
ProviderProcessReceipt {
exit: ProviderProcessObservationFailed,
wire: wire,
}
} else {
ProviderProcessReceipt {
exit: ProviderProcessObservationFailed,
wire: wire,
}
}
}
}

fn observe_codex_account_preflight(
bundle: CodexRuntimeBundle,
probe_key: ProviderProbeKey,
Expand All @@ -1685,30 +1599,28 @@ fn observe_codex_account_preflight(
},
}
} else {
let script = retained_foreign(
body: codex_press_account_trip_script(
let run = shell.Exec.RunArgvStdin(
program: env_path_resolved_program().invocation,
arguments: codex_press_account_trip_invocation(
executable: projected.executable.path,
codex_home: codex_home,
),
reason: "Codex app-server held-open account session; exit+stdout/stderr captured for typed NDJSON id correlation" as NonEmptyStr,
stdin_payload: codex_press_account_trip_request_lines(),
)
let run = shell.Exec.Run(script: script)
let capture = split_gunbc_stdio_capture(combined: run.stdout)
let wire_capture = retain_provider_wire_capture(
evidence_root: evidence_root,
stdout: capture.stdout,
stderr: capture.stderr,
success: run.success,
exit_code: capture.exit_code,
stdout: run.stdout,
stderr: run.stderr,
success: run.exit_code == 0,
exit_code: Present { value: run.exit_code },
)
let process = process_receipt_from_capture(
capture: capture,
let process = ProviderProcessReceipt {
exit: ProviderProcessExited { code: run.exit_code },
wire: match wire_capture {
ProviderWireCaptureOk { receipt: wire } => Present { value: wire }
ProviderWireCaptureRefused { cause: _ } => none
},
run_success: run.success,
)
}
match wire_capture {
ProviderWireCaptureRefused { cause: reason } =>
CodexPressUnavailable {
Expand All @@ -1717,8 +1629,8 @@ fn observe_codex_account_preflight(
}
ProviderWireCaptureOk { receipt: wire } => {
let evidence = parse_account_trip_session(
stdout: capture.stdout,
stderr: capture.stderr,
stdout: run.stdout,
stderr: run.stderr,
evidence_root: evidence_root,
mode: AccountTripDigestRetainPrivateSha512,
)
Expand All @@ -1741,21 +1653,13 @@ fn observe_codex_account_preflight(
wire: Present { value: wire },
},
}
match capture.exit_code {
Present { value: code } =>
if code != 0 {
CodexPressUnavailable {
reason: "codex app-server process exited nonzero" as NonEmptyStr,
process: receipt.process,
}
} else {
outcome_from_standing_receipt(receipt: receipt)
}
Absent =>
CodexPressUnavailable {
reason: "codex app-server process exit observation refused" as NonEmptyStr,
process: receipt.process,
}
if run.exit_code != 0 {
CodexPressUnavailable {
reason: "codex app-server process exited nonzero" as NonEmptyStr,
process: receipt.process,
}
} else {
outcome_from_standing_receipt(receipt: receipt)
}
}
}
Expand Down
Loading