Step 11c: per-exec process boundary and remaining withdrawal gaps #28

Open
opened 2026-09-24 08:10:08 +00:00 by kayg · 21 comments
Owner

Carried over from step 11b (#22) after its one fix round (ce77555). 11b fixed dispatch fencing, remove-and-restore, CLOSE ordering and reaping of TERM-ignoring children in the process group. These remain, and each existed before 11b:

  1. Detached descendants escape. Double-forked or setsid children leave the exec's process group, so a group signal misses them. This needs a per-exec process boundary (a cgroup per exec, or an engine-side kill of the whole exec tree). Research the options for Docker and rootless Podman and cite them.
  2. A stalled nonpersistent PTY write can block cleanup in crates/agent's control path.
  3. A persistent session created after a failed database commit can escape compensation.
  4. Inbound DATA fencing starts when the watcher observes the epoch change, not at commit. Specify the exact guarantee in docs/contracts.md §2.3 and close the window.

Security surface: Sol for the build and the review. Reproducers are in /mnt/hdd/infraternal/artifacts/review-grant-shrink/.

Carried over from step 11b (#22) after its one fix round (`ce77555`). 11b fixed dispatch fencing, remove-and-restore, CLOSE ordering and reaping of TERM-ignoring children in the process group. These remain, and each existed before 11b: 1. **Detached descendants escape.** Double-forked or `setsid` children leave the exec's process group, so a group signal misses them. This needs a per-exec process boundary (a cgroup per exec, or an engine-side kill of the whole exec tree). Research the options for Docker and rootless Podman and cite them. 2. **A stalled nonpersistent PTY write** can block cleanup in `crates/agent`'s control path. 3. **A persistent session created after a failed database commit** can escape compensation. 4. **Inbound DATA fencing** starts when the watcher observes the epoch change, not at commit. Specify the exact guarantee in `docs/contracts.md` §2.3 and close the window. Security surface: Sol for the build and the review. Reproducers are in `/mnt/hdd/infraternal/artifacts/review-grant-shrink/`.
Author
Owner

Starting step 11c on branch process-boundary at base 1337efde31f52d0c717029c1760ff7d3c7eb2940. Reading issue #28 and its reproducers, then implementing and testing the four withdrawal gaps. No push.

Starting step 11c on branch `process-boundary` at base `1337efde31f52d0c717029c1760ff7d3c7eb2940`. Reading issue #28 and its reproducers, then implementing and testing the four withdrawal gaps. No push.
Author
Owner

Step 11c findings: Docker's exec create/start interface has no per-exec cgroup placement field (https://docs.docker.com/reference/api/engine/). Linux cgroup migration requires write access to both the destination and the common ancestor's cgroup.procs; cgroup.kill is the kernel-enforced option (https://cdn.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html). On this VM the rootless Podman scope is owned by kayg, while the rootful Docker scope is owned by root, so a host-side cgroup move is not portable to the unprivileged infraternal agent. The current branch tags each exec environment and scans /proc from a helper inside its container. A new real-engine test reaped both setsid and double-fork descendants on rootful Docker and rootless Podman (1 passed, 0 failed, 14.22s). This is cooperative: a child can remove the marker or deny /proc inspection. The DATA-after-commit guarantee still needs a commit-synchronized write fence; watcher polling cannot provide it.

Step 11c findings: Docker's exec create/start interface has no per-exec cgroup placement field (https://docs.docker.com/reference/api/engine/). Linux cgroup migration requires write access to both the destination and the common ancestor's `cgroup.procs`; `cgroup.kill` is the kernel-enforced option (https://cdn.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html). On this VM the rootless Podman scope is owned by `kayg`, while the rootful Docker scope is owned by root, so a host-side cgroup move is not portable to the unprivileged infraternal agent. The current branch tags each exec environment and scans `/proc` from a helper inside its container. A new real-engine test reaped both `setsid` and double-fork descendants on rootful Docker and rootless Podman (1 passed, 0 failed, 14.22s). This is cooperative: a child can remove the marker or deny `/proc` inspection. The DATA-after-commit guarantee still needs a commit-synchronized write fence; watcher polling cannot provide it.
Author
Owner

The /proc boundary's limit is confirmed by the Linux proc_pid_environ(5) manual: it exposes the environment supplied at execve and access is subject to PTRACE_MODE_READ_FSCREDS (https://man7.org/linux/man-pages/man5/proc_pid_environ.5.html). Therefore a descendant that starts a new program with a scrubbed environment, or cannot be inspected under the container's proc policy, is outside this cooperative tracker. The real Docker/rootless Podman fixture proves ordinary inherited-environment setsid and double-fork descendants are reaped; it is not proof against hostile scrubbing.

The `/proc` boundary's limit is confirmed by the Linux `proc_pid_environ(5)` manual: it exposes the environment supplied at `execve` and access is subject to `PTRACE_MODE_READ_FSCREDS` (https://man7.org/linux/man-pages/man5/proc_pid_environ.5.html). Therefore a descendant that starts a new program with a scrubbed environment, or cannot be inspected under the container's proc policy, is outside this cooperative tracker. The real Docker/rootless Podman fixture proves ordinary inherited-environment `setsid` and double-fork descendants are reaped; it is not proof against hostile scrubbing.
Author
Owner

DATA fence implementation: the server reserves bounded relay capacity, then SqliteStorage::admit_stream_data uses BEGIN IMMEDIATE to compare both attachment epochs and enqueue the frame synchronously before COMMIT. Grant mutations use SQLite's same writer lock. Therefore a DATA frame sent after a withdrawal transaction commits cannot pass the old-epoch check, including when the watcher has not polled yet. A frame enqueued before commit may still drain to the process afterward; the updated §2.3 states this exact limit. Added a concurrent commit/admission test, a Docker/rootless Podman continuous-writer process test, and a Criterion benchmark for the input fence. These new tests are pending verification while the integration build is running.

DATA fence implementation: the server reserves bounded relay capacity, then `SqliteStorage::admit_stream_data` uses `BEGIN IMMEDIATE` to compare both attachment epochs and enqueue the frame synchronously before `COMMIT`. Grant mutations use SQLite's same writer lock. Therefore a DATA frame sent after a withdrawal transaction commits cannot pass the old-epoch check, including when the watcher has not polled yet. A frame enqueued before commit may still drain to the process afterward; the updated §2.3 states this exact limit. Added a concurrent commit/admission test, a Docker/rootless Podman continuous-writer process test, and a Criterion benchmark for the input fence. These new tests are pending verification while the integration build is running.
Author
Owner

Integration finding on f852f36: stalled_pty_stdin_cannot_hold_exec_termination passed on Docker (397 ms) but rootless Podman returned exec_signal_timeout from the cleanup helper after 244 s of engine tests. The added per-exec /proc helper and original-group helper are currently serialized, and helper create/start has a 750 ms deadline. I am making KILL helpers concurrent and allowing more startup time, then will rerun the targeted regression and both required gates.

Integration finding on f852f36: `stalled_pty_stdin_cannot_hold_exec_termination` passed on Docker (397 ms) but rootless Podman returned `exec_signal_timeout` from the cleanup helper after 244 s of engine tests. The added per-exec `/proc` helper and original-group helper are currently serialized, and helper create/start has a 750 ms deadline. I am making KILL helpers concurrent and allowing more startup time, then will rerun the targeted regression and both required gates.
Author
Owner

Cleanup helper correction committed as c8522e0. The Podman failure was two races: a 250 ms helper-inspection timeout under rootless Podman load, and the marker helper reaping a process group before the concurrent group helper signaled it. Helpers now have a bounded retry window for inspection; TERM/KILL group and marker helpers run without making one wait for the other; signaling an already vanished group succeeds. Targeted real-engine stalled_pty_stdin_cannot_hold_exec_termination passed: Docker 470 ms, rootless Podman 1.354 s; 1 passed, 0 failed. Full final-head gates are running.

Cleanup helper correction committed as c8522e0. The Podman failure was two races: a 250 ms helper-inspection timeout under rootless Podman load, and the marker helper reaping a process group before the concurrent group helper signaled it. Helpers now have a bounded retry window for inspection; TERM/KILL group and marker helpers run without making one wait for the other; signaling an already vanished group succeeds. Targeted real-engine `stalled_pty_stdin_cannot_hold_exec_termination` passed: Docker 470 ms, rootless Podman 1.354 s; 1 passed, 0 failed. Full final-head gates are running.
Author
Owner

Final-head integration run on c8522e0 passed the real engine suite, log snapshots, TypeScript stream client, and the new client_data_written_after_grant_commit_never_reaches_exec plus failed_dispatch_commit_never_leaves_a_persistent_exec tests. It then failed the existing every_grant_shrink_closes_logs_and_persistent_exec_on_both_engines test because the Podman tag_change case did not see its shell command start within the 10 s test deadline. A direct rerun of that full regression passed all 16 Docker/Podman causes in 47.63 s, including Podman tag_change (CLOSE 19 ms, process reaped 725 ms). No source change was made; the full integration gate is being rerun to verify the combined suite.

Final-head integration run on c8522e0 passed the real engine suite, log snapshots, TypeScript stream client, and the new `client_data_written_after_grant_commit_never_reaches_exec` plus `failed_dispatch_commit_never_leaves_a_persistent_exec` tests. It then failed the existing `every_grant_shrink_closes_logs_and_persistent_exec_on_both_engines` test because the Podman `tag_change` case did not see its shell command start within the 10 s test deadline. A direct rerun of that full regression passed all 16 Docker/Podman causes in 47.63 s, including Podman `tag_change` (CLOSE 19 ms, process reaped 725 ms). No source change was made; the full integration gate is being rerun to verify the combined suite.
Author
Owner

Second final-head integration run passed the real engine and stream regression sections, including the new 11c cases, then timed out in the existing compiled CLI/MCP SSH replay PTY harness after the remote command printed its expected BEFORE/AFTER output. A direct rerun of the complete cli_and_mcp_use_the_public_http_contract test passed (122.23 s, 1 passed, 0 failed). No source change was made. A third full integration gate began with load5 3.03; the first full run's older Podman grant-shrink test also passed in this second run.

Second final-head integration run passed the real engine and stream regression sections, including the new 11c cases, then timed out in the existing compiled CLI/MCP SSH replay PTY harness after the remote command printed its expected BEFORE/AFTER output. A direct rerun of the complete `cli_and_mcp_use_the_public_http_contract` test passed (122.23 s, 1 passed, 0 failed). No source change was made. A third full integration gate began with load5 3.03; the first full run's older Podman grant-shrink test also passed in this second run.
Author
Owner

Third full integration run again passed the 11c engine and stream sections, then timed out in the existing compiled CLI/MCP SSH PTY harness. This time the initial SSH command printed 41 111 and __CLI_SSH_COMMAND_OK__, then echoed exit 37, but the CLI process did not exit within the harness's 15 s wait. The direct complete CLI/MCP test passed earlier on this same head. The failure is intermittent and occurs after remote input is delivered; I am checking the SSH close path before deciding whether another gate run is warranted. No code change has been made.

Third full integration run again passed the 11c engine and stream sections, then timed out in the existing compiled CLI/MCP SSH PTY harness. This time the initial SSH command printed `41 111` and `__CLI_SSH_COMMAND_OK__`, then echoed `exit 37`, but the CLI process did not exit within the harness's 15 s wait. The direct complete CLI/MCP test passed earlier on this same head. The failure is intermittent and occurs after remote input is delivered; I am checking the SSH close path before deciding whether another gate run is warranted. No code change has been made.
Author
Owner

While investigating the intermittent full-gate timeouts, I found a cancellation safety issue in the new inbound DATA fence: it used raw BEGIN IMMEDIATE on a pooled SQLx connection. A cancelled async admission could leave transaction cleanup dependent on pool behavior. I changed it to SQLx Connection::begin_with("BEGIN IMMEDIATE"), whose transaction object starts rollback on drop (SQLx Connection, SQLx Transaction). The injected interruption regression passes and verifies a grant write can proceed afterward. New head 2a79eeb; final gates are being rerun. This safeguard may or may not explain the earlier timing failures; no causal claim yet.

While investigating the intermittent full-gate timeouts, I found a cancellation safety issue in the new inbound DATA fence: it used raw `BEGIN IMMEDIATE` on a pooled SQLx connection. A cancelled async admission could leave transaction cleanup dependent on pool behavior. I changed it to SQLx `Connection::begin_with("BEGIN IMMEDIATE")`, whose transaction object starts rollback on drop ([SQLx Connection](https://docs.rs/sqlx/0.8.6/sqlx/trait.Connection.html#method.begin_with), [SQLx Transaction](https://docs.rs/sqlx/0.8.6/sqlx/struct.Transaction.html)). The injected interruption regression passes and verifies a grant write can proceed afterward. New head 2a79eeb; final gates are being rerun. This safeguard may or may not explain the earlier timing failures; no causal claim yet.
Author
Owner

Step 11c finished on branch process-boundary at head 2a79eeb65ec8982609be9da7859a2283d45e3783 (base 1337efde31f52d0c717029c1760ff7d3c7eb2940). No push; working tree clean.

Boundary choice: each exec receives a unique inherited environment marker. Cleanup scans /proc/*/environ inside the container PID namespace and signals matching PIDs, including setsid and double-fork descendants; it also signals the original process group. This works without host cgroup delegation under rootless Podman and was exercised on real rootful Docker and rootless Podman. Docker's exec API does not offer a per-exec cgroup, and a host cgroup move for rootful Docker requires delegation the unprivileged server does not have. Limits: a process can scrub its marker, deny /proc inspection, or fork during the final scan; the image needs /bin/sh, tr, and grep. This is cooperative cleanup, not kernel containment. Sources: Docker Exec API, Linux proc_pid_environ(5), Linux cgroup v2.

Other fixes: nonpersistent PTY writes have a 250 ms bound so cleanup cannot wait indefinitely; failed dispatch commits fence late persistent exec/SSH creation and terminate any already reserved session; inbound DATA enqueue is serialized with grant commits under an immediate SQLite transaction. SQLx owns rollback on interrupted admission. Exact ingress guarantee: DATA sent after the withdrawal commit is not forwarded; data admitted before the commit may already be buffered and can be consumed afterward. docs/contracts.md §2.3 states this distinction. The 11b forbidden CLOSE bound remains covered by real-engine tests. Added a Criterion benchmark for the admission path; performance numbers are reserved for the perf-test VM under the repo contract.

Targeted evidence: real Docker/rootless Podman descendant test passed; stalled PTY cleanup test passed (Docker 470 ms, Podman 1.354 s); failed commit and continuous post-commit DATA tests passed on both engines; injected interrupted DATA admission released the grant write lock. Earlier full integration runs exposed intermittent pre-existing Podman process-start and CLI SSH PTY timing failures. Direct reruns of those suites passed, and the final full integration run below passed without a source or test-timeout change to those paths. Their failed outputs and analysis are in prior issue comments.

scripts/verify fast output (verbatim):

PASS fast: migrations are append-only
PASS fast: JavaScript dependencies
PASS fast: build web app
PASS fast: cargo fmt --check
PASS fast: cargo clippy
PASS fast: cargo test
PASS fast: svelte-check
PASS fast: UI package svelte-check
PASS fast: stream package typecheck
PASS fast: Vitest
PASS fast: UI package Vitest
PASS fast: stream package Vitest
PASS fast: OpenAPI and TypeScript client drift
PASS fast: terminal TypeScript
PASS fast: terminal Vitest

scripts/verify integration output (verbatim):

PASS integration: JavaScript dependencies
PASS integration: build web app
PASS integration: build release binary
PASS integration: real HTTP behavior
PASS integration: Docker socket _ping
PASS integration: rootless Podman socket _ping
PASS integration: engine adapters against Docker and Podman
PASS integration: bounded follow:false log snapshots against Docker and rootless Podman
PASS integration: TypeScript stream client against Docker and Podman
PASS integration: stream authorization and CLOSE regressions against Docker
PASS integration: persistent PTY termination preempts blocked send
PASS integration: resource HTTP API against Docker and rootless Podman
PASS integration: compiled CLI and MCP against real server and engines
PASS integration: SSH contract against isolated real sshd
PASS integration: SSH product API and stream against isolated real sshd
Step 11c finished on branch `process-boundary` at head `2a79eeb65ec8982609be9da7859a2283d45e3783` (base `1337efde31f52d0c717029c1760ff7d3c7eb2940`). No push; working tree clean. Boundary choice: each exec receives a unique inherited environment marker. Cleanup scans `/proc/*/environ` inside the container PID namespace and signals matching PIDs, including `setsid` and double-fork descendants; it also signals the original process group. This works without host cgroup delegation under rootless Podman and was exercised on real rootful Docker and rootless Podman. Docker's exec API does not offer a per-exec cgroup, and a host cgroup move for rootful Docker requires delegation the unprivileged server does not have. Limits: a process can scrub its marker, deny `/proc` inspection, or fork during the final scan; the image needs `/bin/sh`, `tr`, and `grep`. This is cooperative cleanup, not kernel containment. Sources: [Docker Exec API](https://docs.docker.com/reference/api/engine/version/v1.43/#tag/Exec), [Linux proc_pid_environ(5)](https://man7.org/linux/man-pages/man5/proc_pid_environ.5.html), [Linux cgroup v2](https://cdn.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html). Other fixes: nonpersistent PTY writes have a 250 ms bound so cleanup cannot wait indefinitely; failed dispatch commits fence late persistent exec/SSH creation and terminate any already reserved session; inbound DATA enqueue is serialized with grant commits under an immediate SQLite transaction. SQLx owns rollback on interrupted admission. Exact ingress guarantee: DATA sent after the withdrawal commit is not forwarded; data admitted before the commit may already be buffered and can be consumed afterward. `docs/contracts.md` §2.3 states this distinction. The 11b forbidden CLOSE bound remains covered by real-engine tests. Added a Criterion benchmark for the admission path; performance numbers are reserved for the perf-test VM under the repo contract. Targeted evidence: real Docker/rootless Podman descendant test passed; stalled PTY cleanup test passed (Docker 470 ms, Podman 1.354 s); failed commit and continuous post-commit DATA tests passed on both engines; injected interrupted DATA admission released the grant write lock. Earlier full integration runs exposed intermittent pre-existing Podman process-start and CLI SSH PTY timing failures. Direct reruns of those suites passed, and the final full integration run below passed without a source or test-timeout change to those paths. Their failed outputs and analysis are in prior issue comments. `scripts/verify fast` output (verbatim): ``` PASS fast: migrations are append-only PASS fast: JavaScript dependencies PASS fast: build web app PASS fast: cargo fmt --check PASS fast: cargo clippy PASS fast: cargo test PASS fast: svelte-check PASS fast: UI package svelte-check PASS fast: stream package typecheck PASS fast: Vitest PASS fast: UI package Vitest PASS fast: stream package Vitest PASS fast: OpenAPI and TypeScript client drift PASS fast: terminal TypeScript PASS fast: terminal Vitest ``` `scripts/verify integration` output (verbatim): ``` PASS integration: JavaScript dependencies PASS integration: build web app PASS integration: build release binary PASS integration: real HTTP behavior PASS integration: Docker socket _ping PASS integration: rootless Podman socket _ping PASS integration: engine adapters against Docker and Podman PASS integration: bounded follow:false log snapshots against Docker and rootless Podman PASS integration: TypeScript stream client against Docker and Podman PASS integration: stream authorization and CLOSE regressions against Docker PASS integration: persistent PTY termination preempts blocked send PASS integration: resource HTTP API against Docker and rootless Podman PASS integration: compiled CLI and MCP against real server and engines PASS integration: SSH contract against isolated real sshd PASS integration: SSH product API and stream against isolated real sshd ```
Author
Owner

Review-11c started on branch process-boundary, base 1337efd, head 2a79eeb65ec8982609be9da7859a2283d45e3783. Independent read-only defensive audit; targeted tests pending.

Review-11c started on branch `process-boundary`, base `1337efd`, head `2a79eeb65ec8982609be9da7859a2283d45e3783`. Independent read-only defensive audit; targeted tests pending.
Author
Owner

Independent review-11c complete. Branch process-boundary, base 1337efd, head 2a79eeb65ec8982609be9da7859a2283d45e3783. Verdict: request changes. Full notes: /mnt/hdd/infraternal/jobs/review-11c/findings.md.

Findings:

  1. High (crates/engine/src/lib.rs:943-972,1015-1031): if the marker helper cannot scan /proc or the image lacks tr/grep, detached setsid descendants remain outside the original group. The code reports an error, but the requested descendant-termination property is not met for that configuration. Gate exec creation on a usable scanner or provide another boundary; qualify any guarantee accordingly.
  2. Medium (crates/engine/src/lib.rs:1136-1189): a helper can be created or started before its request times out, then continue after the cleanup future releases its 256-slot permit. Repeated timeout cycles can accumulate detached helper processes. Retain and supervise helper IDs until confirmed exited.
  3. Medium (crates/engine/src/lib.rs:946-968,1041-1106): scan failure does not always escalate to the original group. Group capture and the host /proc liveness check are prerequisites; unreadable host proc or a non-group-leading exec skips it. Narrow the claim and test this failure path.

The cooperative marker limitation is documented substantially honestly. /proc/<pid>/environ shows the initial environment for the current exec image and has ptrace visibility checks (https://man7.org/linux/man-pages/man5/proc_pid_environ.5.html). Docker and Podman process listings alone do not establish exec ancestry (https://docs.docker.com/reference/cli/docker/container/top/ ; https://docs.podman.io/en/latest/markdown/podman-top.1.html). A per-exec cgroup is stronger but not a cheap universal substitute because cgroup migration requires delegated write access (https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html).

Checks run, real output:

  • cargo test -p infraternal-engine --test integration pty_close_reaps_setsid_and_double_fork_descendants -- --ignored: test pty_close_reaps_setsid_and_double_fork_descendants ... ok / test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 9 filtered out; finished in 16.19s
  • cargo test -p infraternal-agent stalled_nonpersistent_pty_input_does_not_hold_cleanup --lib -- --ignored: test tests::stalled_nonpersistent_pty_input_does_not_hold_cleanup ... ok / test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 9 filtered out; finished in 5.64s
  • cargo test -p infraternal-server --test storage continuous_stream_data_is_fenced_at_revocation_commit -- --exact: test continuous_stream_data_is_fenced_at_revocation_commit ... ok / test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 20 filtered out; finished in 0.21s
  • cargo test -p infraternal-server --test storage interrupted_stream_data_admission_releases_grant_write_lock: test interrupted_stream_data_admission_releases_grant_write_lock ... ok / test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 20 filtered out; finished in 0.05s
  • cargo test -p infraternal-server --test stream_regressions failed_dispatch_commit_never_leaves_a_persistent_exec -- --ignored: test failed_dispatch_commit_never_leaves_a_persistent_exec ... ok / test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 18 filtered out; finished in 15.86s

Throwaway mutation checks: disabling PersistentExecSessions::reserve cancellation made its new unit test fail; disabling the admit_stream_data epoch check made the continuous revocation test fail with 500 post-commit admissions. A focused throwaway test deleting containers:exec role grant passed: existing migration 0010_authorization_epochs.sql bumps user and token epochs on DELETE, so DATA admission correctly rejected it. The throwaway worktree was removed. The review worktree is clean. No commit or push was made; issue remains open.

Independent review-11c complete. Branch `process-boundary`, base `1337efd`, head `2a79eeb65ec8982609be9da7859a2283d45e3783`. Verdict: **request changes**. Full notes: `/mnt/hdd/infraternal/jobs/review-11c/findings.md`. Findings: 1. High (`crates/engine/src/lib.rs:943-972,1015-1031`): if the marker helper cannot scan `/proc` or the image lacks `tr`/`grep`, detached `setsid` descendants remain outside the original group. The code reports an error, but the requested descendant-termination property is not met for that configuration. Gate exec creation on a usable scanner or provide another boundary; qualify any guarantee accordingly. 2. Medium (`crates/engine/src/lib.rs:1136-1189`): a helper can be created or started before its request times out, then continue after the cleanup future releases its 256-slot permit. Repeated timeout cycles can accumulate detached helper processes. Retain and supervise helper IDs until confirmed exited. 3. Medium (`crates/engine/src/lib.rs:946-968,1041-1106`): scan failure does not **always** escalate to the original group. Group capture and the host `/proc` liveness check are prerequisites; unreadable host proc or a non-group-leading exec skips it. Narrow the claim and test this failure path. The cooperative marker limitation is documented substantially honestly. `/proc/<pid>/environ` shows the initial environment for the current exec image and has ptrace visibility checks (https://man7.org/linux/man-pages/man5/proc_pid_environ.5.html). Docker and Podman process listings alone do not establish exec ancestry (https://docs.docker.com/reference/cli/docker/container/top/ ; https://docs.podman.io/en/latest/markdown/podman-top.1.html). A per-exec cgroup is stronger but not a cheap universal substitute because cgroup migration requires delegated write access (https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html). Checks run, real output: - `cargo test -p infraternal-engine --test integration pty_close_reaps_setsid_and_double_fork_descendants -- --ignored`: `test pty_close_reaps_setsid_and_double_fork_descendants ... ok` / `test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 9 filtered out; finished in 16.19s` - `cargo test -p infraternal-agent stalled_nonpersistent_pty_input_does_not_hold_cleanup --lib -- --ignored`: `test tests::stalled_nonpersistent_pty_input_does_not_hold_cleanup ... ok` / `test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 9 filtered out; finished in 5.64s` - `cargo test -p infraternal-server --test storage continuous_stream_data_is_fenced_at_revocation_commit -- --exact`: `test continuous_stream_data_is_fenced_at_revocation_commit ... ok` / `test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 20 filtered out; finished in 0.21s` - `cargo test -p infraternal-server --test storage interrupted_stream_data_admission_releases_grant_write_lock`: `test interrupted_stream_data_admission_releases_grant_write_lock ... ok` / `test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 20 filtered out; finished in 0.05s` - `cargo test -p infraternal-server --test stream_regressions failed_dispatch_commit_never_leaves_a_persistent_exec -- --ignored`: `test failed_dispatch_commit_never_leaves_a_persistent_exec ... ok` / `test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 18 filtered out; finished in 15.86s` Throwaway mutation checks: disabling `PersistentExecSessions::reserve` cancellation made its new unit test fail; disabling the `admit_stream_data` epoch check made the continuous revocation test fail with 500 post-commit admissions. A focused throwaway test deleting `containers:exec` role grant passed: existing migration `0010_authorization_epochs.sql` bumps user and token epochs on DELETE, so DATA admission correctly rejected it. The throwaway worktree was removed. The review worktree is clean. No commit or push was made; issue remains open.
Author
Owner

Fix round started on branch process-boundary at base 2a79eeb65ec8982609be9da7859a2283d45e3783. I am addressing audit Findings 1–3: explicit unconfirmed termination outcomes, owned helper lifecycle, and host /proc dependency. DATA fencing is unchanged. I will post regression sensitivity and final gate output here.

Fix round started on branch `process-boundary` at base `2a79eeb65ec8982609be9da7859a2283d45e3783`. I am addressing audit Findings 1–3: explicit unconfirmed termination outcomes, owned helper lifecycle, and host `/proc` dependency. DATA fencing is unchanged. I will post regression sensitivity and final gate output here.
Author
Owner

Fix round evidence on process-boundary (current head 2c6e335):

  • Finding 1: after KILL, cleanup verifies the container marker scan. When it cannot confirm the boundary it attempts a host /proc scan scoped by the captured PID namespace and uses pidfds to signal matching processes. An incomplete scan returns outcome_unknown, and persistent-session completion audits now say unconfirmed. In a real rootless Podman container with tr and grep unavailable and restricted host /proc, the new regression observes an unconfirmed result instead of success. The existing real Docker/Podman setsid and double-fork regression passes.
  • Finding 2: helper IDs are retained immediately after create. Start and inspect remain supervised; a timed-out running helper gets a host pidfd KILL attempt, and its cleanup slot stays occupied until inspection observes exit. The timeout unit test confirms the caller receives unknown while capacity remains held.
  • Finding 3: the contract now limits original-group escalation to a captured, verifiably live group. A missing host /proc injection returns Unknown rather than Gone; the unit regression passes.

The fast gate passed all 15 checks on this head. Regression sensitivity checks and final integration gate remain.

Fix round evidence on `process-boundary` (current head `2c6e335`): - Finding 1: after KILL, cleanup verifies the container marker scan. When it cannot confirm the boundary it attempts a host `/proc` scan scoped by the captured PID namespace and uses pidfds to signal matching processes. An incomplete scan returns `outcome_unknown`, and persistent-session completion audits now say `unconfirmed`. In a real rootless Podman container with `tr` and `grep` unavailable and restricted host `/proc`, the new regression observes an unconfirmed result instead of success. The existing real Docker/Podman `setsid` and double-fork regression passes. - Finding 2: helper IDs are retained immediately after create. Start and inspect remain supervised; a timed-out running helper gets a host pidfd KILL attempt, and its cleanup slot stays occupied until inspection observes exit. The timeout unit test confirms the caller receives unknown while capacity remains held. - Finding 3: the contract now limits original-group escalation to a captured, verifiably live group. A missing host `/proc` injection returns `Unknown` rather than `Gone`; the unit regression passes. The fast gate passed all 15 checks on this head. Regression sensitivity checks and final integration gate remain.
Author
Owner

Gate note: the first integration run (started before the later helper-identity commits) passed the release build, real HTTP, Docker/Podman sockets, engine adapters, log snapshots, TypeScript stream, stream authorization, persistent PTY blocked-send, and resource HTTP checks. It failed the compiled CLI/MCP SSH PTY harness: the CLI printed __CLI_SSH_COMMAND_OK__ and exit but did not exit within the harness deadline. The direct one-time rerun of cli_and_mcp_use_the_public_http_contract passed (1 passed, 126.15s). Full failing and passing outputs are retained in /tmp/fix-11c-integration.log and /tmp/fix-11c-cli-mcp-rerun.log; final-head fast and integration are being run separately.

Gate note: the first integration run (started before the later helper-identity commits) passed the release build, real HTTP, Docker/Podman sockets, engine adapters, log snapshots, TypeScript stream, stream authorization, persistent PTY blocked-send, and resource HTTP checks. It failed the compiled CLI/MCP SSH PTY harness: the CLI printed `__CLI_SSH_COMMAND_OK__` and `exit` but did not exit within the harness deadline. The direct one-time rerun of `cli_and_mcp_use_the_public_http_contract` passed (`1 passed`, 126.15s). Full failing and passing outputs are retained in `/tmp/fix-11c-integration.log` and `/tmp/fix-11c-cli-mcp-rerun.log`; final-head fast and integration are being run separately.
Author
Owner

Regression sensitivity confirmed on the fix branch. I reversed one fix at a time locally, ran only its regression, then restored the committed code and reran the unit suite:

  1. Replacing the explicit unknown_outcome constructor with an ordinary engine error made the real rootless Podman missing-tools test fail: incomplete scan must be explicit ... outcome_unknown: false (log /tmp/fix-11c-regression-1.log).
  2. Releasing the supervisor's permit before helper completion made the slot test fail: left: 1 right: 0 available permits (log /tmp/fix-11c-regression-2.log).
  3. Treating missing host /proc as Gone made the injected-proc test fail: left: Gone right: Unknown (log /tmp/fix-11c-regression-3.log).

All three reversions were restored. Final-head scripts/verify fast passed all 15 checks at 15cd8694140d5347670bfab70c4da6d12a7b5775 (full gate output will be quoted in the final comment). The final integration run started with load5=4.57.

Regression sensitivity confirmed on the fix branch. I reversed one fix at a time locally, ran only its regression, then restored the committed code and reran the unit suite: 1. Replacing the explicit `unknown_outcome` constructor with an ordinary engine error made the real rootless Podman missing-tools test fail: `incomplete scan must be explicit ... outcome_unknown: false` (log `/tmp/fix-11c-regression-1.log`). 2. Releasing the supervisor's permit before helper completion made the slot test fail: `left: 1 right: 0` available permits (log `/tmp/fix-11c-regression-2.log`). 3. Treating missing host `/proc` as `Gone` made the injected-proc test fail: `left: Gone right: Unknown` (log `/tmp/fix-11c-regression-3.log`). All three reversions were restored. Final-head `scripts/verify fast` passed all 15 checks at `15cd8694140d5347670bfab70c4da6d12a7b5775` (full gate output will be quoted in the final comment). The final integration run started with load5=4.57.
Author
Owner

Fix round complete on process-boundary at head 15cd8694140d5347670bfab70c4da6d12a7b5775. No push; working tree clean. DATA fencing was left unchanged.

Finding 1 — fixed within the documented cooperative boundary. Cleanup now runs a final post-KILL verification scan. A failed/incomplete container scan uses a host /proc PID-namespace marker fallback when visibility allows; signals use pidfds. The real rootless Podman missing-tools regression confirms an explicit outcome_unknown when host /proc is restricted. Persistent-session completion audits record unconfirmed, and the caller receives an error. The existing real Docker/Podman setsid/double-fork test passes. A marker removed by a hostile process or a fork after verification remains outside the guarantee, as documented.

Finding 2 — fixed. A cleanup helper ID is retained immediately after creation, the helper is supervised through start and inspect, and a timeout attempts host pidfd KILL only after checking its unique marker. The exec cleanup slot remains held until an exit code is observed, even after the caller receives an unconfirmed result. The timeout/slot regression passes. Docker's exec API does not expose an exec-delete endpoint, so a helper ID cannot be explicitly removed; the implementation confirms the process exited before releasing capacity.

Finding 3 — fixed/documented. Original-group escalation is limited to groups captured from readable host /proc and verifiably live. An injected missing host /proc test returns Unknown, not Gone, and prevents a false success. The contract states this conditional guarantee.

Regression sensitivity was proved by reversing each fix locally, one at a time: missing-tools test failed with outcome_unknown: false; supervisor test failed with available permits left: 1 right: 0; missing-proc test failed with left: Gone right: Unknown. Restoring the code made all pass. The pidfd test was also synchronized with its owned child environment after an intermittent fast-gate failure; final fast passed.

Final-head scripts/verify fast output (verbatim):

PASS fast: migrations are append-only
PASS fast: JavaScript dependencies
PASS fast: build web app
PASS fast: cargo fmt --check
PASS fast: cargo clippy
PASS fast: cargo test
PASS fast: svelte-check
PASS fast: UI package svelte-check
PASS fast: stream package typecheck
PASS fast: Vitest
PASS fast: UI package Vitest
PASS fast: stream package Vitest
PASS fast: OpenAPI and TypeScript client drift
PASS fast: terminal TypeScript
PASS fast: terminal Vitest

Final-head scripts/verify integration output (verbatim):

PASS integration: JavaScript dependencies
PASS integration: build web app
PASS integration: build release binary
PASS integration: real HTTP behavior
PASS integration: Docker socket _ping
PASS integration: rootless Podman socket _ping
PASS integration: engine adapters against Docker and Podman
PASS integration: bounded follow:false log snapshots against Docker and rootless Podman
PASS integration: TypeScript stream client against Docker and Podman
PASS integration: stream authorization and CLOSE regressions against Docker
PASS integration: persistent PTY termination preempts blocked send
PASS integration: resource HTTP API against Docker and rootless Podman
FAIL integration: compiled CLI and MCP against real server and engines
   Compiling infraternal-server v0.0.1 (/home/kayg/wt/process-boundary/crates/server)
   Compiling infraternal v0.0.1 (/home/kayg/wt/process-boundary/crates/infraternal)
    Finished `test` profile [unoptimized] target(s) in 25.29s
     Running tests/cli_mcp.rs (target/debug/deps/cli_mcp-94557349a527577f)

running 1 test
test cli_and_mcp_use_the_public_http_contract ... CLI_MCP_SLOW_COMMAND command=logs elapsed_ms=10023
STREAM_EXEC_RTT_MS runtime=docker elapsed_ms=358
CLI_MCP_SLOW_COMMAND command=logs elapsed_ms=10015
STREAM_EXEC_RTT_MS runtime=podman elapsed_ms=502
STREAM_SLOW_READER bytes=8388613 rss_delta_kib=80

thread 'cli_and_mcp_use_the_public_http_contract' (3200006) panicked at crates/infraternal/tests/cli_mcp.rs:2332:5:
remote stty size should receive the SIGWINCH resize: [87, 97, 114, 110, 105, 110, 103, 58, 32, 116, 104, 101, 32, 79, 83, 32, 107, 101, 121, 99, 104, 97, 105, 110, 32, 105, 115, 32, 117, 110, 97, 118, 97, 105, 108, 97, 98, 108, 101, 59, 32, 117, 115, 105, 110, 103, 32, 97, 32, 112, 101, 114, 109, 105, 115, 115, 105, 111, 110, 45, 114, 101, 115, 116, 114, 105, 99, 116, 101, 100, 32, 99, 114, 101, 100, 101, 110, 116, 105, 97, 108, 32, 102, 105, 108, 101, 46, 13, 10, 50, 52, 32, 56, 48, 13, 10, 50, 52, 32, 56, 48, 13, 10]
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
FAILED

failures:

failures:
    cli_and_mcp_use_the_public_http_contract

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 106.66s

error: test failed, to rerun pass `-p infraternal --test cli_mcp`

The final integration run began at load5=4.57; load5 rose above 12 during the CLI/MCP SSH timing check. That check was rerun directly once after load dropped. Direct rerun output (verbatim):

    Finished `test` profile [unoptimized] target(s) in 0.41s
     Running tests/cli_mcp.rs (target/debug/deps/cli_mcp-94557349a527577f)

running 1 test
CLI_MCP_SLOW_COMMAND command=logs elapsed_ms=10008
STREAM_EXEC_RTT_MS runtime=docker elapsed_ms=180
test cli_and_mcp_use_the_public_http_contract has been running for over 60 seconds
CLI_MCP_SLOW_COMMAND command=logs elapsed_ms=10008
STREAM_EXEC_RTT_MS runtime=podman elapsed_ms=248
STREAM_SLOW_READER bytes=8388613 rss_delta_kib=80
STREAM_EXEC_STDIN_EOF=blocked_by_current_pty
Warning: the OS keychain is unavailable; using a permission-restricted credential file.

thread 'cli_and_mcp_use_the_public_http_contract' (3236233) panicked at crates/infraternal/tests/cli_mcp.rs:1696:9:
schema type "object" rejected null
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Warning: the OS keychain is unavailable; using a permission-restricted credential file.
test cli_and_mcp_use_the_public_http_contract ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 126.69s

The earlier integration run on the pre-safety-commit head also failed the CLI/MCP SSH exit-wait harness after 12 PASS checks; its one direct rerun passed. Those exact logs remain in /tmp/fix-11c-integration.log and /tmp/fix-11c-cli-mcp-rerun.log, and that flake was noted above.

Fix round complete on `process-boundary` at head `15cd8694140d5347670bfab70c4da6d12a7b5775`. No push; working tree clean. DATA fencing was left unchanged. Finding 1 — fixed within the documented cooperative boundary. Cleanup now runs a final post-KILL verification scan. A failed/incomplete container scan uses a host `/proc` PID-namespace marker fallback when visibility allows; signals use pidfds. The real rootless Podman missing-tools regression confirms an explicit `outcome_unknown` when host `/proc` is restricted. Persistent-session completion audits record `unconfirmed`, and the caller receives an error. The existing real Docker/Podman setsid/double-fork test passes. A marker removed by a hostile process or a fork after verification remains outside the guarantee, as documented. Finding 2 — fixed. A cleanup helper ID is retained immediately after creation, the helper is supervised through start and inspect, and a timeout attempts host pidfd KILL only after checking its unique marker. The exec cleanup slot remains held until an exit code is observed, even after the caller receives an unconfirmed result. The timeout/slot regression passes. Docker's exec API does not expose an exec-delete endpoint, so a helper ID cannot be explicitly removed; the implementation confirms the process exited before releasing capacity. Finding 3 — fixed/documented. Original-group escalation is limited to groups captured from readable host `/proc` and verifiably live. An injected missing host `/proc` test returns `Unknown`, not `Gone`, and prevents a false success. The contract states this conditional guarantee. Regression sensitivity was proved by reversing each fix locally, one at a time: missing-tools test failed with `outcome_unknown: false`; supervisor test failed with available permits `left: 1 right: 0`; missing-proc test failed with `left: Gone right: Unknown`. Restoring the code made all pass. The pidfd test was also synchronized with its owned child environment after an intermittent fast-gate failure; final fast passed. Final-head `scripts/verify fast` output (verbatim): ``` PASS fast: migrations are append-only PASS fast: JavaScript dependencies PASS fast: build web app PASS fast: cargo fmt --check PASS fast: cargo clippy PASS fast: cargo test PASS fast: svelte-check PASS fast: UI package svelte-check PASS fast: stream package typecheck PASS fast: Vitest PASS fast: UI package Vitest PASS fast: stream package Vitest PASS fast: OpenAPI and TypeScript client drift PASS fast: terminal TypeScript PASS fast: terminal Vitest ``` Final-head `scripts/verify integration` output (verbatim): ``` PASS integration: JavaScript dependencies PASS integration: build web app PASS integration: build release binary PASS integration: real HTTP behavior PASS integration: Docker socket _ping PASS integration: rootless Podman socket _ping PASS integration: engine adapters against Docker and Podman PASS integration: bounded follow:false log snapshots against Docker and rootless Podman PASS integration: TypeScript stream client against Docker and Podman PASS integration: stream authorization and CLOSE regressions against Docker PASS integration: persistent PTY termination preempts blocked send PASS integration: resource HTTP API against Docker and rootless Podman FAIL integration: compiled CLI and MCP against real server and engines Compiling infraternal-server v0.0.1 (/home/kayg/wt/process-boundary/crates/server) Compiling infraternal v0.0.1 (/home/kayg/wt/process-boundary/crates/infraternal) Finished `test` profile [unoptimized] target(s) in 25.29s Running tests/cli_mcp.rs (target/debug/deps/cli_mcp-94557349a527577f) running 1 test test cli_and_mcp_use_the_public_http_contract ... CLI_MCP_SLOW_COMMAND command=logs elapsed_ms=10023 STREAM_EXEC_RTT_MS runtime=docker elapsed_ms=358 CLI_MCP_SLOW_COMMAND command=logs elapsed_ms=10015 STREAM_EXEC_RTT_MS runtime=podman elapsed_ms=502 STREAM_SLOW_READER bytes=8388613 rss_delta_kib=80 thread 'cli_and_mcp_use_the_public_http_contract' (3200006) panicked at crates/infraternal/tests/cli_mcp.rs:2332:5: remote stty size should receive the SIGWINCH resize: [87, 97, 114, 110, 105, 110, 103, 58, 32, 116, 104, 101, 32, 79, 83, 32, 107, 101, 121, 99, 104, 97, 105, 110, 32, 105, 115, 32, 117, 110, 97, 118, 97, 105, 108, 97, 98, 108, 101, 59, 32, 117, 115, 105, 110, 103, 32, 97, 32, 112, 101, 114, 109, 105, 115, 115, 105, 111, 110, 45, 114, 101, 115, 116, 114, 105, 99, 116, 101, 100, 32, 99, 114, 101, 100, 101, 110, 116, 105, 97, 108, 32, 102, 105, 108, 101, 46, 13, 10, 50, 52, 32, 56, 48, 13, 10, 50, 52, 32, 56, 48, 13, 10] note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace FAILED failures: failures: cli_and_mcp_use_the_public_http_contract test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 106.66s error: test failed, to rerun pass `-p infraternal --test cli_mcp` ``` The final integration run began at load5=4.57; load5 rose above 12 during the CLI/MCP SSH timing check. That check was rerun directly once after load dropped. Direct rerun output (verbatim): ``` Finished `test` profile [unoptimized] target(s) in 0.41s Running tests/cli_mcp.rs (target/debug/deps/cli_mcp-94557349a527577f) running 1 test CLI_MCP_SLOW_COMMAND command=logs elapsed_ms=10008 STREAM_EXEC_RTT_MS runtime=docker elapsed_ms=180 test cli_and_mcp_use_the_public_http_contract has been running for over 60 seconds CLI_MCP_SLOW_COMMAND command=logs elapsed_ms=10008 STREAM_EXEC_RTT_MS runtime=podman elapsed_ms=248 STREAM_SLOW_READER bytes=8388613 rss_delta_kib=80 STREAM_EXEC_STDIN_EOF=blocked_by_current_pty Warning: the OS keychain is unavailable; using a permission-restricted credential file. thread 'cli_and_mcp_use_the_public_http_contract' (3236233) panicked at crates/infraternal/tests/cli_mcp.rs:1696:9: schema type "object" rejected null note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace Warning: the OS keychain is unavailable; using a permission-restricted credential file. test cli_and_mcp_use_the_public_http_contract ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 126.69s ``` The earlier integration run on the pre-safety-commit head also failed the CLI/MCP SSH exit-wait harness after 12 PASS checks; its one direct rerun passed. Those exact logs remain in `/tmp/fix-11c-integration.log` and `/tmp/fix-11c-cli-mcp-rerun.log`, and that flake was noted above.
Author
Owner

Verification started for issue #28. Branch: process-boundary, current head 15cd8694140d5347670bfab70c4da6d12a7b5775, based on 1337efd. I will merge dev at ce194d4 as the only permitted repository commit, verify Findings 1–3 (including throwaway reversion worktrees), then run the requested gates when load permits.

Verification started for issue #28. Branch: `process-boundary`, current head `15cd8694140d5347670bfab70c4da6d12a7b5775`, based on `1337efd`. I will merge `dev` at `ce194d4` as the only permitted repository commit, verify Findings 1–3 (including throwaway reversion worktrees), then run the requested gates when load permits.
Author
Owner

Regression sensitivity results, isolated in /home/kayg/wt/verify-11c-tmp at 15cd869; all temporary edits are confined to that worktree.

  1. cleanup_tests::host_marker_scan_detects_survivors_and_reports_incomplete_inspection passes at the fix head. Reverting termination_unconfirmed from EngineError::unknown_outcome to an ordinary engine error fails with assertion failed: error.outcome_unknown.
  2. A temporary strengthened version of host_marker_scan_signals_only_the_marked_pid_through_pidfd passed with the live child carrying a different marker from the synthetic /proc entry. Removing the marker read/check after pidfd_open fails with a reused PID without the marker must not be signaled: 1.
  3. cleanup_tests::timed_out_cleanup_reports_unknown_but_retains_its_slot_until_exit passes at the fix head. Dropping the permit in the timeout branch fails with left: 1 right: 0 at helper still owns cleanup capacity.

I will remove the throwaway worktree after the remaining checks.

Regression sensitivity results, isolated in `/home/kayg/wt/verify-11c-tmp` at `15cd869`; all temporary edits are confined to that worktree. 1. `cleanup_tests::host_marker_scan_detects_survivors_and_reports_incomplete_inspection` passes at the fix head. Reverting `termination_unconfirmed` from `EngineError::unknown_outcome` to an ordinary engine error fails with `assertion failed: error.outcome_unknown`. 2. A temporary strengthened version of `host_marker_scan_signals_only_the_marked_pid_through_pidfd` passed with the live child carrying a different marker from the synthetic `/proc` entry. Removing the marker read/check after `pidfd_open` fails with `a reused PID without the marker must not be signaled: 1`. 3. `cleanup_tests::timed_out_cleanup_reports_unknown_but_retains_its_slot_until_exit` passes at the fix head. Dropping the permit in the timeout branch fails with `left: 1 right: 0` at `helper still owns cleanup capacity`. I will remove the throwaway worktree after the remaining checks.
Author
Owner

Verdict: verified for Findings 1–3 on merge head dbd66850f8a5d6190e65b4bd364e075950033425. origin/dev at ce194d4bf53bf370a6bcccb76745c74c74240494 merged cleanly; this is the only commit on the branch after 15cd869. No push.

Finding 1 — incomplete scans do not report successful termination. crates/engine/src/lib.rs::terminate_exec returns termination_unconfirmed if the fallback scan or final verification is incomplete. The persistent-session completion paths write unconfirmed in crates/server/src/personal.rs and crates/server/src/stream.rs. The unit test host_marker_scan_detects_survivors_and_reports_incomplete_inspection passes; when I reverted termination_unconfirmed to an ordinary engine error in the throwaway worktree, it failed with assertion failed: error.outcome_unknown. The real Podman missing_container_scan_tools_report_unconfirmed_when_host_proc_is_restricted regression also ran in the integration gate.

Finding 2 — helper timeout signaling is identity checked, and cleanup capacity remains owned through observed exit. run_exec_helper supplies a unique helper marker; signal_host_process opens a pidfd, checks /proc/<pid>/environ for that marker, then sends through the pidfd. Linux documents the pidfd as a stable reference to the selected process, avoiding signals to a different process after PID reuse: https://man7.org/linux/man-pages/man2/pidfd_send_signal.2.html. A temporary strengthened host_marker_scan_signals_only_the_marked_pid_through_pidfd test passed with a mismatched live-process marker; removing the post-pidfd marker check made it fail with a reused PID without the marker must not be signaled: 1. timed_out_cleanup_reports_unknown_but_retains_its_slot_until_exit passed; explicitly dropping the permit on timeout made it fail with left: 1 right: 0 at helper still owns cleanup capacity.

Finding 3 — missing host /proc does not prove the original group is gone. host_exec_group_status_at returns Unknown when /proc cannot be read. missing_host_proc_is_unknown_not_a_dead_original_group passed; reverting that branch to Gone failed with left: Gone right: Unknown.

Audit presentation check: the server records the outcome as the literal unconfirmed. There is no current audit read endpoint or CLI audit command. The web /audit-log route is a placeholder, and the dashboard says activity will appear when the audit-log API lands. So there is no API, CLI, or web audit query/render path on this head to mis-map the value; presentation is not yet implemented.

scripts/verify fast first failed only steady_state_framing_does_not_allocate_per_chunk at load5 6.23 (load at 06:11:56 UTC: load average: 7.44, 6.23, 6.40). Verbatim failure excerpt:

FAIL fast: cargo test
test logs::tests::steady_state_framing_does_not_allocate_per_chunk ... FAILED

failures:

---- logs::tests::steady_state_framing_does_not_allocate_per_chunk stdout ----

thread 'logs::tests::steady_state_framing_does_not_allocate_per_chunk' (3306283) panicked at crates/engine/src/logs.rs:349:9:
log framing allocated per chunk after warmup: Stats { allocations: 103, deallocations: 12, reallocations: 25, bytes_allocated: 120463, bytes_deallocated: 4724, bytes_reallocated: 8085 }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

failures:
    logs::tests::steady_state_framing_does_not_allocate_per_chunk

test result: FAILED. 14 passed; 1 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.02s

error: test failed, to rerun pass `-p infraternal-engine --lib`

Direct rerun, verbatim:

cargo test: 1 passed, 15 filtered out (1 suite, 0.00s)

A second full fast run passed. Verbatim output:

PASS fast: migrations are append-only
PASS fast: workspace package dependencies
PASS fast: JavaScript dependencies
PASS fast: build web app
PASS fast: cargo fmt --check
PASS fast: cargo clippy
PASS fast: cargo test
PASS fast: svelte-check
PASS fast: UI package svelte-check
PASS fast: stream package typecheck
PASS fast: Vitest
PASS fast: UI package Vitest
PASS fast: stream package Vitest
PASS fast: OpenAPI and TypeScript client drift
PASS fast: terminal TypeScript
PASS fast: terminal Vitest

scripts/verify integration started with load5 2.95 and passed. Verbatim output:

PASS integration: JavaScript dependencies
PASS integration: build web app
PASS integration: build release binary
PASS integration: real HTTP behavior
PASS integration: Docker socket _ping
PASS integration: rootless Podman socket _ping
PASS integration: engine adapters against Docker and Podman
PASS integration: bounded follow:false log snapshots against Docker and rootless Podman
PASS integration: TypeScript stream client against Docker and Podman
PASS integration: stream authorization and CLOSE regressions against Docker
PASS integration: persistent PTY termination preempts blocked send
PASS integration: resource HTTP API against Docker and rootless Podman
PASS integration: compiled CLI and MCP against real server and engines
PASS integration: SSH contract against isolated real sshd
PASS integration: SSH product API and stream against isolated real sshd
Verdict: verified for Findings 1–3 on merge head `dbd66850f8a5d6190e65b4bd364e075950033425`. `origin/dev` at `ce194d4bf53bf370a6bcccb76745c74c74240494` merged cleanly; this is the only commit on the branch after `15cd869`. No push. Finding 1 — incomplete scans do not report successful termination. `crates/engine/src/lib.rs::terminate_exec` returns `termination_unconfirmed` if the fallback scan or final verification is incomplete. The persistent-session completion paths write `unconfirmed` in `crates/server/src/personal.rs` and `crates/server/src/stream.rs`. The unit test `host_marker_scan_detects_survivors_and_reports_incomplete_inspection` passes; when I reverted `termination_unconfirmed` to an ordinary engine error in the throwaway worktree, it failed with `assertion failed: error.outcome_unknown`. The real Podman `missing_container_scan_tools_report_unconfirmed_when_host_proc_is_restricted` regression also ran in the integration gate. Finding 2 — helper timeout signaling is identity checked, and cleanup capacity remains owned through observed exit. `run_exec_helper` supplies a unique helper marker; `signal_host_process` opens a pidfd, checks `/proc/<pid>/environ` for that marker, then sends through the pidfd. Linux documents the pidfd as a stable reference to the selected process, avoiding signals to a different process after PID reuse: https://man7.org/linux/man-pages/man2/pidfd_send_signal.2.html. A temporary strengthened `host_marker_scan_signals_only_the_marked_pid_through_pidfd` test passed with a mismatched live-process marker; removing the post-pidfd marker check made it fail with `a reused PID without the marker must not be signaled: 1`. `timed_out_cleanup_reports_unknown_but_retains_its_slot_until_exit` passed; explicitly dropping the permit on timeout made it fail with `left: 1 right: 0` at `helper still owns cleanup capacity`. Finding 3 — missing host `/proc` does not prove the original group is gone. `host_exec_group_status_at` returns `Unknown` when `/proc` cannot be read. `missing_host_proc_is_unknown_not_a_dead_original_group` passed; reverting that branch to `Gone` failed with `left: Gone right: Unknown`. Audit presentation check: the server records the outcome as the literal `unconfirmed`. There is no current audit read endpoint or CLI audit command. The web `/audit-log` route is a placeholder, and the dashboard says activity will appear when the audit-log API lands. So there is no API, CLI, or web audit query/render path on this head to mis-map the value; presentation is not yet implemented. `scripts/verify fast` first failed only `steady_state_framing_does_not_allocate_per_chunk` at load5 `6.23` (load at 06:11:56 UTC: `load average: 7.44, 6.23, 6.40`). Verbatim failure excerpt: ```text FAIL fast: cargo test test logs::tests::steady_state_framing_does_not_allocate_per_chunk ... FAILED failures: ---- logs::tests::steady_state_framing_does_not_allocate_per_chunk stdout ---- thread 'logs::tests::steady_state_framing_does_not_allocate_per_chunk' (3306283) panicked at crates/engine/src/logs.rs:349:9: log framing allocated per chunk after warmup: Stats { allocations: 103, deallocations: 12, reallocations: 25, bytes_allocated: 120463, bytes_deallocated: 4724, bytes_reallocated: 8085 } note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace failures: logs::tests::steady_state_framing_does_not_allocate_per_chunk test result: FAILED. 14 passed; 1 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.02s error: test failed, to rerun pass `-p infraternal-engine --lib` ``` Direct rerun, verbatim: ```text cargo test: 1 passed, 15 filtered out (1 suite, 0.00s) ``` A second full fast run passed. Verbatim output: ```text PASS fast: migrations are append-only PASS fast: workspace package dependencies PASS fast: JavaScript dependencies PASS fast: build web app PASS fast: cargo fmt --check PASS fast: cargo clippy PASS fast: cargo test PASS fast: svelte-check PASS fast: UI package svelte-check PASS fast: stream package typecheck PASS fast: Vitest PASS fast: UI package Vitest PASS fast: stream package Vitest PASS fast: OpenAPI and TypeScript client drift PASS fast: terminal TypeScript PASS fast: terminal Vitest ``` `scripts/verify integration` started with load5 `2.95` and passed. Verbatim output: ```text PASS integration: JavaScript dependencies PASS integration: build web app PASS integration: build release binary PASS integration: real HTTP behavior PASS integration: Docker socket _ping PASS integration: rootless Podman socket _ping PASS integration: engine adapters against Docker and Podman PASS integration: bounded follow:false log snapshots against Docker and rootless Podman PASS integration: TypeScript stream client against Docker and Podman PASS integration: stream authorization and CLOSE regressions against Docker PASS integration: persistent PTY termination preempts blocked send PASS integration: resource HTTP API against Docker and rootless Podman PASS integration: compiled CLI and MCP against real server and engines PASS integration: SSH contract against isolated real sshd PASS integration: SSH product API and stream against isolated real sshd ```
Commenting is not possible because the repository is archived.
No labels
No milestone
No project
No assignees
1 participant
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/infraternal-v0#28
No description provided.