Step 24 (S2): compose stacks, images and volumes (read-only) #41

Open
opened 2026-09-25 05:16:51 +00:00 by kayg · 89 comments
Owner

PLAN step 24: Compose stacks are grouped by Compose labels and read-only, with per-stack logs and restart; images and volumes are listable and inspectable read-only. This first pass targets connected local engines; remote hosts arrive with step 19. It is not a security surface, so it runs on Luna max.

PLAN step 24: Compose stacks are grouped by Compose labels and read-only, with per-stack logs and restart; images and volumes are listable and inspectable read-only. This first pass targets connected local engines; remote hosts arrive with step 19. It is not a security surface, so it runs on Luna max.
Author
Owner

Starting PLAN step 24 on branch stacks-images, based on dev dbd66850f8a5d6190e65b4bd364e075950033425. I am following the contract-first sequence: define the stack, image and volume API/scope/audit behavior in docs/contracts.md, commit it, then implement the engine, server, CLI/MCP and lazy web routes.

Starting PLAN step 24 on branch `stacks-images`, based on `dev` `dbd66850f8a5d6190e65b4bd364e075950033425`. I am following the contract-first sequence: define the stack, image and volume API/scope/audit behavior in `docs/contracts.md`, commit it, then implement the engine, server, CLI/MCP and lazy web routes.
Author
Owner

Contract first pass committed on stacks-images at 63ccc28ef676d393104304d2d7c0a1a36dba55e9 in docs/contracts.md §6.

  • Adds host/engine-scoped list and inspect routes for images and volumes, all under the existing containers:read scope.
  • Defines Compose grouping by com.docker.compose.project, with io.podman.compose.project as fallback; the Docker label wins when both are non-empty. Groups are per host and engine.
  • Adds stack inspect and restart. Restart checks every member, uses one existing containers:restart plan with fixed per-member versions for AI agent tokens, and records per-target outcomes without a migration.
  • Stack logs open the existing per-container logs stream for each member; no new stream kind or frame format.
  • Defines the requested CLI commands and matching MCP tools.

Research sources: Docker Compose service labels, Podman Compose provider source, Docker Engine API, Podman REST API. Real-engine label and inspect verification is still pending.

Contract first pass committed on `stacks-images` at `63ccc28ef676d393104304d2d7c0a1a36dba55e9` in `docs/contracts.md` §6. - Adds host/engine-scoped list and inspect routes for images and volumes, all under the existing `containers:read` scope. - Defines Compose grouping by `com.docker.compose.project`, with `io.podman.compose.project` as fallback; the Docker label wins when both are non-empty. Groups are per host and engine. - Adds stack inspect and restart. Restart checks every member, uses one existing `containers:restart` plan with fixed per-member versions for AI agent tokens, and records per-target outcomes without a migration. - Stack logs open the existing per-container `logs` stream for each member; no new stream kind or frame format. - Defines the requested CLI commands and matching MCP tools. Research sources: [Docker Compose service labels](https://docs.docker.com/reference/compose-file/services/#labels), [Podman Compose provider source](https://github.com/containers/podman-compose/blob/main/podman_compose.py), [Docker Engine API](https://docs.docker.com/reference/api/engine/version/v1.40/), [Podman REST API](https://docs.podman.io/en/latest/_static/api.html). Real-engine label and inspect verification is still pending.
Author
Owner

Implementation progress for s2-stacks

Commits on branch stacks-images (not pushed): 973fe95 engine image/volume adapters and Compose grouping; 8529c0a stack/image/volume API, multi-target plans and generated OpenAPI client; 3c21b3c CLI/MCP commands; 469142a inventory pages and browser coverage.

Decision/evidence: stack restart uses one existing plan target row per sorted member, with the canonical address and resource version checked before dispatch. Outcomes are recorded per member; any unconfirmed dispatch yields indeterminate. No migration was needed because plan_targets already supports multiple rows. Compose grouping prefers com.docker.compose.project and falls back to io.podman.compose.project.

Checks so far: the focused engine label test passed (1 passed, 16 filtered out); bun --cwd=packages/web run check passed with 0 errors and 0 warnings; production web build succeeded. The fast gate exposed three rounds of Clippy findings; those findings are fixed, and a clean fast rerun plus real-engine and browser gates are still pending.

Implementation progress for s2-stacks Commits on branch `stacks-images` (not pushed): `973fe95` engine image/volume adapters and Compose grouping; `8529c0a` stack/image/volume API, multi-target plans and generated OpenAPI client; `3c21b3c` CLI/MCP commands; `469142a` inventory pages and browser coverage. Decision/evidence: stack restart uses one existing plan target row per sorted member, with the canonical address and resource version checked before dispatch. Outcomes are recorded per member; any unconfirmed dispatch yields `indeterminate`. No migration was needed because `plan_targets` already supports multiple rows. Compose grouping prefers `com.docker.compose.project` and falls back to `io.podman.compose.project`. Checks so far: the focused engine label test passed (`1 passed, 16 filtered out`); `bun --cwd=packages/web run check` passed with 0 errors and 0 warnings; production web build succeeded. The fast gate exposed three rounds of Clippy findings; those findings are fixed, and a clean fast rerun plus real-engine and browser gates are still pending.
Author
Owner

Integration findings and corrections:

  • The first full integration run failed at crates/engine/tests/integration.rs:926 (left: 2, right: 1). The added Compose member name began with the primary container name, and ContainerQuery.name is a substring filter. Renamed the fixture member so it cannot match that filter.
  • The focused test then showed Docker reports the image tag as busybox:1.36, while rootless Podman reports docker.io/library/busybox:1.36. The adapter preserves engine metadata, so the test now checks that inspect tags equal the corresponding list tags.
  • Focused real-engine verification passed: test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 10 filtered out; finished in 91.05s.
  • scripts/verify fast passed all stages after the corrections. Full integration is being rerun.

Fixture corrections committed as f522b93.

Integration findings and corrections: - The first full integration run failed at `crates/engine/tests/integration.rs:926` (`left: 2`, `right: 1`). The added Compose member name began with the primary container name, and `ContainerQuery.name` is a substring filter. Renamed the fixture member so it cannot match that filter. - The focused test then showed Docker reports the image tag as `busybox:1.36`, while rootless Podman reports `docker.io/library/busybox:1.36`. The adapter preserves engine metadata, so the test now checks that inspect tags equal the corresponding list tags. - Focused real-engine verification passed: `test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 10 filtered out; finished in 91.05s`. - `scripts/verify fast` passed all stages after the corrections. Full integration is being rerun. Fixture corrections committed as `f522b93`.
Author
Owner

Resource HTTP integration findings:

  • After correcting the project label, stack detail reached restart. The Compose fixture had passed label name and project as separate --label values, which leaves the project label empty; it now passes the required key=value argument.
  • The stack restart test read outcomes[i].status, but the DTO serializes TargetOutcome under outcomes[i].outcome, where its tagged status lives. The assertion now checks outcome.status and includes the returned JSON in failure output.
  • The rerun passed the engine adapter, bounded log snapshot, TypeScript stream, authorization/CLOSE, and persistent PTY checks before the resource HTTP test stopped at the fixture assertion. The corrected focused resource HTTP test is pending load5 < 8.
Resource HTTP integration findings: - After correcting the project label, stack detail reached restart. The Compose fixture had passed label name and project as separate `--label` values, which leaves the project label empty; it now passes the required `key=value` argument. - The stack restart test read `outcomes[i].status`, but the DTO serializes `TargetOutcome` under `outcomes[i].outcome`, where its tagged status lives. The assertion now checks `outcome.status` and includes the returned JSON in failure output. - The rerun passed the engine adapter, bounded log snapshot, TypeScript stream, authorization/CLOSE, and persistent PTY checks before the resource HTTP test stopped at the fixture assertion. The corrected focused resource HTTP test is pending load5 < 8.
Author
Owner

The corrected resource HTTP integration test now passes against both real engines:

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

This covers image and volume list/detail, stack list/detail, planned per-member restart, and the neighboring container authorization checks in the same test. Commit: d18cd6d.

The corrected resource HTTP integration test now passes against both real engines: `test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 24.94s` This covers image and volume list/detail, stack list/detail, planned per-member restart, and the neighboring container authorization checks in the same test. Commit: `d18cd6d`.
Author
Owner

Timing-sensitive stream regression evidence from the integration run:

Initial full-suite run:

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

The one failure was every_grant_shrink_closes_logs_and_persistent_exec_on_both_engines: Podman user_disabled exec did not appear in the 10-second process-observation loop.

Required direct single-test rerun:

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

The direct rerun passed for Docker and Podman without source changes, consistent with a load-sensitive timing failure.

Timing-sensitive stream regression evidence from the integration run: Initial full-suite run: `test result: FAILED. 11 passed; 1 failed; 0 ignored; 0 measured; 7 filtered out; finished in 111.02s` The one failure was `every_grant_shrink_closes_logs_and_persistent_exec_on_both_engines`: Podman `user_disabled` exec did not appear in the 10-second process-observation loop. Required direct single-test rerun: `test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 18 filtered out; finished in 66.51s` The direct rerun passed for Docker and Podman without source changes, consistent with a load-sensitive timing failure.
Author
Owner

CLI/MCP integration finding:

  • The real compiled CLI/MCP test reached tools/list and returned the eight new image, volume, and stack tools, but its exact-name assertion still expected the prior nine-tool set. The expected set now contains all 17 tools, matching the MCP unit test. Commit: 68b981c.
  • The two CLI_MCP_SLOW_COMMAND command=logs elapsed_ms=... lines reached the test's intentional 10-second --timeout; its helper records commands over 5 seconds and explains that logs are truncated at this requested deadline. This was not the test failure.
  • Full integration had passed the engine, stream, and resource HTTP steps before this assertion. The CLI/MCP integration and remaining SSH steps still need a successful full-gate run.
CLI/MCP integration finding: - The real compiled CLI/MCP test reached `tools/list` and returned the eight new image, volume, and stack tools, but its exact-name assertion still expected the prior nine-tool set. The expected set now contains all 17 tools, matching the MCP unit test. Commit: `68b981c`. - The two `CLI_MCP_SLOW_COMMAND command=logs elapsed_ms=...` lines reached the test's intentional 10-second `--timeout`; its helper records commands over 5 seconds and explains that logs are truncated at this requested deadline. This was not the test failure. - Full integration had passed the engine, stream, and resource HTTP steps before this assertion. The CLI/MCP integration and remaining SSH steps still need a successful full-gate run.
Author
Owner

The updated CLI/MCP integration now passes the exact 17-tool inventory assertion. It then failed the existing terminal resize assertion: the test observed the initial 24 80 twice, although it requested 47 123 after 300ms and the remote command sleeps for one second. No product code was changed for this failure; one direct rerun is pending load5 < 8 per the timing-test rule.

The two logged CLI_MCP_SLOW_COMMAND command=logs elapsed_ms=... observations are the test's requested 10-second log timeout and did not fail the test.

The updated CLI/MCP integration now passes the exact 17-tool inventory assertion. It then failed the existing terminal resize assertion: the test observed the initial `24 80` twice, although it requested `47 123` after 300ms and the remote command sleeps for one second. No product code was changed for this failure; one direct rerun is pending load5 < 8 per the timing-test rule. The two logged `CLI_MCP_SLOW_COMMAND command=logs elapsed_ms=...` observations are the test's requested 10-second log timeout and did not fail the test.
Author
Owner

572effc adds a bounded readiness check to the real-engine CLI/MCP fixture. The direct cli_and_mcp_use_the_public_http_contract rerun reached the Podman log-tail check before the PTY case and observed empty output with --timeout 1. The engine integration test already notes asynchronous Docker/Podman file-backed log flushes, so the fixture now polls each run-scoped container's real logs --tail 4 output for all four expected records before checking the public stream. cargo fmt --all --check passes; the next integration rerun is waiting for the shared release/browser job and load threshold.

`572effc` adds a bounded readiness check to the real-engine CLI/MCP fixture. The direct `cli_and_mcp_use_the_public_http_contract` rerun reached the Podman log-tail check before the PTY case and observed empty output with `--timeout 1`. The engine integration test already notes asynchronous Docker/Podman file-backed log flushes, so the fixture now polls each run-scoped container's real `logs --tail 4` output for all four expected records before checking the public stream. `cargo fmt --all --check` passes; the next integration rerun is waiting for the shared release/browser job and load threshold.
Author
Owner

The full integration run on 572effc passed the release build, HTTP behavior, Docker/Podman socket pings, engine adapters, bounded log snapshots, and TypeScript stream client. The stream regression suite reported test result: FAILED. 11 passed; 1 failed; 0 ignored; 0 measured; 7 filtered out; finished in 122.48s. The failure was authorization_loss_terminates_stalled_stdin_on_both_engines: Docker completed, but the Podman helper did not observe its sleep 60 process within the test window. A separate real-engine integration suite and Docker tunnel test loop were active concurrently; I will do the required single direct rerun when they clear and load5 is below 8.

The full integration run on `572effc` passed the release build, HTTP behavior, Docker/Podman socket pings, engine adapters, bounded log snapshots, and TypeScript stream client. The stream regression suite reported `test result: FAILED. 11 passed; 1 failed; 0 ignored; 0 measured; 7 filtered out; finished in 122.48s`. The failure was `authorization_loss_terminates_stalled_stdin_on_both_engines`: Docker completed, but the Podman helper did not observe its `sleep 60` process within the test window. A separate real-engine integration suite and Docker tunnel test loop were active concurrently; I will do the required single direct rerun when they clear and load5 is below 8.
Author
Owner

The timing failure was rerun once directly after competing engine/browser work cleared and load5 was below 8. First run (full integration): test result: FAILED. 11 passed; 1 failed; 0 ignored; 0 measured; 7 filtered out; finished in 122.48s; authorization_loss_terminates_stalled_stdin_on_both_engines did not observe the Podman sleep process before its deadline. Direct rerun: cargo test: 1 passed, 18 filtered out (1 suite, 19.44s). The full integration gate is being rerun now to complete the final gate.

The timing failure was rerun once directly after competing engine/browser work cleared and load5 was below 8. First run (full integration): `test result: FAILED. 11 passed; 1 failed; 0 ignored; 0 measured; 7 filtered out; finished in 122.48s`; `authorization_loss_terminates_stalled_stdin_on_both_engines` did not observe the Podman `sleep` process before its deadline. Direct rerun: `cargo test: 1 passed, 18 filtered out (1 suite, 19.44s)`. The full integration gate is being rerun now to complete the final gate.
Author
Owner

The second full integration run passed stream authorization/CLOSE, persistent PTY termination, and the resource HTTP API, then the compiled CLI/MCP test failed after 114.08s at truncation_reasons.contains(&json!("duration_limit")). The bounded MCP log call uses follow:false and a 1-second maximum; read_container_logs only adds duration_limit if the snapshot reaches its deadline, so a faster EOF may legitimately omit it. The schema type "object" rejected null line came from a caught anyOf branch and was not the failing assertion. I will rerun the CLI/MCP test once in isolation at load5 < 8 before deciding whether to adjust that fixture expectation.

The second full integration run passed stream authorization/CLOSE, persistent PTY termination, and the resource HTTP API, then the compiled CLI/MCP test failed after 114.08s at `truncation_reasons.contains(&json!("duration_limit"))`. The bounded MCP log call uses `follow:false` and a 1-second maximum; `read_container_logs` only adds `duration_limit` if the snapshot reaches its deadline, so a faster EOF may legitimately omit it. The `schema type "object" rejected null` line came from a caught `anyOf` branch and was not the failing assertion. I will rerun the CLI/MCP test once in isolation at load5 < 8 before deciding whether to adjust that fixture expectation.
Author
Owner

Committed cb655e0 test: allow fast bounded log snapshots. The large Docker log fixture still asserts byte_limit; it no longer requires duration_limit when a follow:false snapshot completes before the one-second deadline. The same CLI/MCP test separately verifies the duration limit against a continuously writing real container. cargo fmt --all --check passes. The isolated CLI/MCP rerun is pending a quiet engine window.

Committed `cb655e0 test: allow fast bounded log snapshots`. The large Docker log fixture still asserts `byte_limit`; it no longer requires `duration_limit` when a `follow:false` snapshot completes before the one-second deadline. The same CLI/MCP test separately verifies the duration limit against a continuously writing real container. `cargo fmt --all --check` passes. The isolated CLI/MCP rerun is pending a quiet engine window.
Author
Owner

Final verification is still queued by shared-engine load. The isolated CLI/MCP rerun after cb655e0 cannot start while another worktree repeatedly runs pty_session_drop_and_close_terminate_exec_processes_on_all_real_engines; at the latest check load5 was 10.47 (the job threshold is < 8). No process or file from that job has been touched. The fast gate passed before the final fixture assertion change; cargo fmt --all --check and code-graph impact pass after it.

Final verification is still queued by shared-engine load. The isolated CLI/MCP rerun after `cb655e0` cannot start while another worktree repeatedly runs `pty_session_drop_and_close_terminate_exec_processes_on_all_real_engines`; at the latest check load5 was 10.47 (the job threshold is < 8). No process or file from that job has been touched. The fast gate passed before the final fixture assertion change; `cargo fmt --all --check` and code-graph impact pass after it.
Author
Owner

The isolated CLI/MCP rerun after cb655e0 passed the bounded log snapshot assertions, then reproduced the PTY resize race: remote output was 24 80 twice, missing the expected 47 123. Commit f50d484 fixes the fixture handshake: it waits for the remote initial size output, resizes the local PTY, and has the remote shell poll stty size until the remote geometry changes. cargo fmt --all --check and graph impact checks pass. The final full integration gate will verify this change when the shared engine queue clears and load5 is below 8.

The isolated CLI/MCP rerun after `cb655e0` passed the bounded log snapshot assertions, then reproduced the PTY resize race: remote output was `24 80` twice, missing the expected `47 123`. Commit `f50d484` fixes the fixture handshake: it waits for the remote initial size output, resizes the local PTY, and has the remote shell poll `stty size` until the remote geometry changes. `cargo fmt --all --check` and graph impact checks pass. The final full integration gate will verify this change when the shared engine queue clears and load5 is below 8.
Author
Owner

verify fast passes on the current head after cb655e0 and f50d484. 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

Full integration and browser gates remain queued behind active release builds and load5 > 8.

`verify fast` passes on the current head after `cb655e0` and `f50d484`. 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 ``` Full integration and browser gates remain queued behind active release builds and load5 > 8.
Author
Owner

Integration attempt on f50d484 passed dependency setup, web build, release build, real HTTP behavior, Docker and rootless Podman socket checks, engine adapters, and bounded follow:false log snapshots. It then failed at TypeScript stream client against Docker and Podman:

FAIL integration: TypeScript stream client against Docker and Podman
error: TTY exec from h_01M3CMPY1BBV3ESFWJP3BCP00N/docker/f8fb8353169a7ab5c4fa8a5e5036d19c577ddeed07233c3d04f71830dca6152c: stream channel open failed: {"code":"engine_error","message":"The exec process could not be started.","request_id":"req_01M3CMPZDPN61K7MGSAHGZKT6Q","retryable":true,"details":{"engine_code":"500"}}
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 4 filtered out; finished in 6.30s

At the failure, load5 was 11.20 with an external browser build and 20-run real-engine shrink check active. Following the job rule for load-sensitive timing tests, I will perform the one direct retry when load5 is below 8. No source change has been made for this failure.

Integration attempt on `f50d484` passed dependency setup, web build, release build, real HTTP behavior, Docker and rootless Podman socket checks, engine adapters, and bounded `follow:false` log snapshots. It then failed at `TypeScript stream client against Docker and Podman`: ``` FAIL integration: TypeScript stream client against Docker and Podman error: TTY exec from h_01M3CMPY1BBV3ESFWJP3BCP00N/docker/f8fb8353169a7ab5c4fa8a5e5036d19c577ddeed07233c3d04f71830dca6152c: stream channel open failed: {"code":"engine_error","message":"The exec process could not be started.","request_id":"req_01M3CMPZDPN61K7MGSAHGZKT6Q","retryable":true,"details":{"engine_code":"500"}} test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 4 filtered out; finished in 6.30s ``` At the failure, load5 was 11.20 with an external browser build and 20-run real-engine shrink check active. Following the job rule for load-sensitive timing tests, I will perform the one direct retry when load5 is below 8. No source change has been made for this failure.
Author
Owner

The direct retry required by the timing-test rule passed once:

cargo test: 1 passed, 4 filtered out (1 suite, 9.74s)

A full scripts/verify integration rerun on the unchanged f50d484 then passed release build, HTTP, both engine socket checks, Docker/Podman engine adapters and bounded log snapshots, but failed at the same TypeScript TTY-exec case:

FAIL integration: TypeScript stream client against Docker and Podman
error: TTY exec from h_01M3CRZDN7RYAW0A8ZZP3JXR8B/docker/197d95d0072e1b055c28a437c323ce5cf943c2562ee4fe594cc7dd26115404fc: stream channel open failed: {"code":"engine_error","message":"The exec process could not be started.","request_id":"req_01M3CRZF055EB2TV84SW7XM5QD","retryable":true,"details":{"engine_code":"500"}}
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 4 filtered out; finished in 6.41s

At 19:14 UTC, load5 was 11.89, with an external browser suite and a repeated real-engine regression loop active. The permitted direct retry has already passed; no source change has been made for this environmental failure. A clean full-gate run remains pending a quiet window.

The direct retry required by the timing-test rule passed once: ``` cargo test: 1 passed, 4 filtered out (1 suite, 9.74s) ``` A full `scripts/verify integration` rerun on the unchanged `f50d484` then passed release build, HTTP, both engine socket checks, Docker/Podman engine adapters and bounded log snapshots, but failed at the same TypeScript TTY-exec case: ``` FAIL integration: TypeScript stream client against Docker and Podman error: TTY exec from h_01M3CRZDN7RYAW0A8ZZP3JXR8B/docker/197d95d0072e1b055c28a437c323ce5cf943c2562ee4fe594cc7dd26115404fc: stream channel open failed: {"code":"engine_error","message":"The exec process could not be started.","request_id":"req_01M3CRZF055EB2TV84SW7XM5QD","retryable":true,"details":{"engine_code":"500"}} test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 4 filtered out; finished in 6.41s ``` At 19:14 UTC, load5 was 11.89, with an external browser suite and a repeated real-engine regression loop active. The permitted direct retry has already passed; no source change has been made for this environmental failure. A clean full-gate run remains pending a quiet window.
Author
Owner

Verification remains blocked on shared VM capacity at 21:21 UTC. An external browser gate has been active for 2h04m and an external 20-run real-engine shrink loop for 12m; load5 is 12.01. These processes are outside this job and have not been touched.

The single direct retry for the stream-client failure passed (cargo test: 1 passed, 4 filtered out; 9.74s). A full integration rerun after that again failed on the same retryable Docker TTY-exec 500 while external browser/regression work was active. No source change was made for the engine error. The branch is clean at f50d484; the remaining work is a clean full integration gate followed by the browser screenshot matrix when a quiet window is available.

Verification remains blocked on shared VM capacity at 21:21 UTC. An external browser gate has been active for 2h04m and an external 20-run real-engine shrink loop for 12m; load5 is 12.01. These processes are outside this job and have not been touched. The single direct retry for the stream-client failure passed (`cargo test: 1 passed, 4 filtered out; 9.74s`). A full integration rerun after that again failed on the same retryable Docker TTY-exec 500 while external browser/regression work was active. No source change was made for the engine error. The branch is clean at `f50d484`; the remaining work is a clean full integration gate followed by the browser screenshot matrix when a quiet window is available.
Author
Owner

Starting independent review of step 24.

Branch: stacks-images
Base: dbd6685 (dev at job start)
Review head: f50d484
Scope: read-only review of compose stacks, images, volumes, UI, auth, performance and tests; requested throwaway worktree checks only.

Starting independent review of step 24. Branch: `stacks-images` Base: `dbd6685` (`dev` at job start) Review head: `f50d484` Scope: read-only review of compose stacks, images, volumes, UI, auth, performance and tests; requested throwaway worktree checks only.
Author
Owner

Starting independent review of step 24.

Branch: stacks-images
Base: dbd6685 (dev at job start)
Review head: f50d484
Scope: read-only review of compose stacks, images, volumes, UI, auth, performance and tests; requested throwaway worktree checks only.

Starting independent review of step 24. Branch: `stacks-images` Base: `dbd6685` (`dev` at job start) Review head: `f50d484` Scope: read-only review of compose stacks, images, volumes, UI, auth, performance and tests; requested throwaway worktree checks only.
Author
Owner

Review finding on f50d484: the step-24 image, volume and stack routes remain local-only after step 19 is merged. authorize_resource_list (crates/server/src/resources.rs:2727) accepts only state.inventory.host_id() and maps other HostIds to not_found; resolve_resource_engine does the same for details. The list handlers iterate only state.inventory.engine(...), while the merged remote container route reads snapshots through state.tunnels. So a visible connected remote host selected in these pages gets a 404, and an unfiltered request silently omits it, contrary to contracts §6 and PLAN step 24. Please route the resources through the remote tunnel, or return an explicit unsupported/offline result for visible remote hosts until that transport exists.

I also found that image and volume list responses are unbounded: the Bollard adapter calls list_images(None) / list_volumes(None) and the HTTP handlers materialize and sort every summary before returning it (crates/engine/src/bollard_adapter.rs:873,910; crates/server/src/resources.rs:1669–1685,1751–1767). Each list makes one bulk engine call per engine; detail requests inspect only the selected row, so I found no per-item inspect N+1. The new browser filter handlers also allow concurrent requests to overwrite results out of order (packages/web/src/routes/{images,volumes,stacks}/+page.svelte).

Review finding on `f50d484`: the step-24 image, volume and stack routes remain local-only after step 19 is merged. `authorize_resource_list` (`crates/server/src/resources.rs:2727`) accepts only `state.inventory.host_id()` and maps other `HostId`s to `not_found`; `resolve_resource_engine` does the same for details. The list handlers iterate only `state.inventory.engine(...)`, while the merged remote container route reads snapshots through `state.tunnels`. So a visible connected remote host selected in these pages gets a 404, and an unfiltered request silently omits it, contrary to contracts §6 and PLAN step 24. Please route the resources through the remote tunnel, or return an explicit unsupported/offline result for visible remote hosts until that transport exists. I also found that image and volume list responses are unbounded: the Bollard adapter calls `list_images(None)` / `list_volumes(None)` and the HTTP handlers materialize and sort every summary before returning it (`crates/engine/src/bollard_adapter.rs:873,910`; `crates/server/src/resources.rs:1669–1685,1751–1767`). Each list makes one bulk engine call per engine; detail requests inspect only the selected row, so I found no per-item inspect N+1. The new browser filter handlers also allow concurrent requests to overwrite results out of order (`packages/web/src/routes/{images,volumes,stacks}/+page.svelte`).
Author
Owner

TTY exec triage: I ran cargo test -p infraternal-server --test stream_e2e typescript_client_streams_logs_and_exec_for_docker_and_podman -- --ignored --nocapture --test-threads=1 twice on origin/dev (1 passed in 11.75s and 10.74s), then cherry-picked fda1a45 into the throwaway worktree and ran it once more (1 passed in 10.14s). The patch explicitly passes tty: options.tty to Bollard StartExecOptions; step 24's engine diff does not change start_exec. The reported 500 did not reproduce before or after the patch, so the evidence supports flake; it does not show that fda1a45 fixed this instance or that step 24 caused it.

TTY exec triage: I ran `cargo test -p infraternal-server --test stream_e2e typescript_client_streams_logs_and_exec_for_docker_and_podman -- --ignored --nocapture --test-threads=1` twice on `origin/dev` (`1 passed` in 11.75s and 10.74s), then cherry-picked `fda1a45` into the throwaway worktree and ran it once more (`1 passed` in 10.14s). The patch explicitly passes `tty: options.tty` to Bollard `StartExecOptions`; step 24's engine diff does not change `start_exec`. The reported 500 did not reproduce before or after the patch, so the evidence supports `flake`; it does not show that `fda1a45` fixed this instance or that step 24 caused it.
Author
Owner

Additional performance finding: virtualized inventory cells still scan the full inventory array once for every cell (images/+page.svelte:152, volumes/+page.svelte:132, stacks/+page.svelte:282). VirtualTable invokes the cell snippet per column (VirtualTable.svelte:210), and the route's tableRows already spread the original image/volume/stack record. On large inventories that makes each render redo several full-list searches per visible row. Use the fields already on the row or a precomputed address map.

Additional performance finding: virtualized inventory cells still scan the full inventory array once for every cell (`images/+page.svelte:152`, `volumes/+page.svelte:132`, `stacks/+page.svelte:282`). `VirtualTable` invokes the cell snippet per column (`VirtualTable.svelte:210`), and the route's `tableRows` already spread the original image/volume/stack record. On large inventories that makes each render redo several full-list searches per visible row. Use the fields already on the row or a precomputed address map.
Author
Owner

review-24 complete

Verdict: request changes. Reviewed stacks-images at f50d4848e4bc712df5265971cc7933571d551fb5 against dbd66850f8a5d6190e65b4bd364e075950033425. No changes were made to the branch.

Full findings and linked screenshots: /mnt/hdd/infraternal/jobs/review-24/findings.md.

Blocking findings:

  • P1: Resource routes still resolve only LocalInventory after step 19. A visible remote host returns not_found, while omitting host silently returns only local results.
  • P1: Production-browser cold JS gzip exceeds the 120 KiB cap on every new route: stacks 218,763 bytes; images 204,185; volumes 203,832 (cap 122,880).
  • P2: Image and volume lists are unbounded; the virtualized resource tables call find() over the full list per cell; filter requests can overwrite newer results.
  • UI: At 390×844 the Images identity name and digest are clipped without ellipsis. Detail errors have no retry, stack restart confirmation lists only a member count, and loading skeletons are not announced.

The production browser gate output was:

FAIL browser: Playwright against release server

It stopped at the mobile Images content-bound check: image identity boxes reached x=525 while the table content ended at x=374; the body stayed 390px wide. A focused Playwright capture against the production binary collected all resource-route sizes, exercised stack detail/logs and image/volume details, and saved light/dark screenshots at desktop and 390×844. Its captured metrics were:

METRIC /stacks first-load JS gzip: 218763 bytes
METRIC /images first-load JS gzip: 204185 bytes
METRIC /volumes first-load JS gzip: 203832 bytes

Other evidence:

  • scripts/verify fast passed after merging origin/dev at d9197837c5adb20ad0da2a6b8872ed2826a92aee in the throwaway worktree. 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
  • The throwaway merge reported conflicts in crates/infraternal/src/main.rs, crates/infraternal/src/mcp.rs, crates/infraternal/tests/cli_mcp.rs, crates/server/src/lib.rs, crates/server/src/resources.rs and docs/contracts.md. They were resolved only in that worktree; it was removed after review.
  • Real Docker/rootless Podman resource HTTP test: cargo test: 1 passed (1 suite, 14.86s). Removing the Podman Compose-label fallback failed its grouping assertion; returning an empty image list failed resources_http.rs:124 (images.total > 0). Both mutations were restored.
  • TTY incident classification: flake. On unpatched origin/dev, the TypeScript stream test passed twice (11.75s, 10.74s); after cherry-picking fda1a45, it passed once (10.14s). The 500 did not reproduce, so the patch is not proven to have fixed that incident.
## review-24 complete **Verdict: request changes.** Reviewed `stacks-images` at `f50d4848e4bc712df5265971cc7933571d551fb5` against `dbd66850f8a5d6190e65b4bd364e075950033425`. No changes were made to the branch. Full findings and linked screenshots: `/mnt/hdd/infraternal/jobs/review-24/findings.md`. Blocking findings: - **P1:** Resource routes still resolve only `LocalInventory` after step 19. A visible remote host returns `not_found`, while omitting `host` silently returns only local results. - **P1:** Production-browser cold JS gzip exceeds the 120 KiB cap on every new route: stacks 218,763 bytes; images 204,185; volumes 203,832 (cap 122,880). - **P2:** Image and volume lists are unbounded; the virtualized resource tables call `find()` over the full list per cell; filter requests can overwrite newer results. - **UI:** At 390×844 the Images identity name and digest are clipped without ellipsis. Detail errors have no retry, stack restart confirmation lists only a member count, and loading skeletons are not announced. The production browser gate output was: ```text FAIL browser: Playwright against release server ``` It stopped at the mobile Images content-bound check: image identity boxes reached x=525 while the table content ended at x=374; the body stayed 390px wide. A focused Playwright capture against the production binary collected all resource-route sizes, exercised stack detail/logs and image/volume details, and saved light/dark screenshots at desktop and 390×844. Its captured metrics were: ```text METRIC /stacks first-load JS gzip: 218763 bytes METRIC /images first-load JS gzip: 204185 bytes METRIC /volumes first-load JS gzip: 203832 bytes ``` Other evidence: - `scripts/verify fast` passed after merging `origin/dev` at `d9197837c5adb20ad0da2a6b8872ed2826a92aee` in the throwaway worktree. 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 ``` - The throwaway merge reported conflicts in `crates/infraternal/src/main.rs`, `crates/infraternal/src/mcp.rs`, `crates/infraternal/tests/cli_mcp.rs`, `crates/server/src/lib.rs`, `crates/server/src/resources.rs` and `docs/contracts.md`. They were resolved only in that worktree; it was removed after review. - Real Docker/rootless Podman resource HTTP test: `cargo test: 1 passed (1 suite, 14.86s)`. Removing the Podman Compose-label fallback failed its grouping assertion; returning an empty image list failed `resources_http.rs:124` (`images.total > 0`). Both mutations were restored. - TTY incident classification: **flake**. On unpatched `origin/dev`, the TypeScript stream test passed twice (`11.75s`, `10.74s`); after cherry-picking `fda1a45`, it passed once (`10.14s`). The 500 did not reproduce, so the patch is not proven to have fixed that incident.
Author
Owner

Starting fix-24 on branch stacks-images, base head f50d4848e4bc712df5265971cc7933571d551fb5. I will first merge current origin/dev (9d4876a), then fix all findings in /mnt/hdd/infraternal/jobs/review-24/findings.md and run the requested gates and screenshot matrix.

Starting fix-24 on branch `stacks-images`, base head `f50d4848e4bc712df5265971cc7933571d551fb5`. I will first merge current `origin/dev` (`9d4876a`), then fix all findings in `/mnt/hdd/infraternal/jobs/review-24/findings.md` and run the requested gates and screenshot matrix.
Author
Owner

Merge decision: origin/dev adds the remote tunnel contract as its own §6, which overlaps the step 24 resource contract. I preserved the resource contract in §6 as requested and retained the remote tunnel material under §7; resource pages will explicitly report remote hosts as unsupported until #45 adds transport. The seven source conflicts were resolved by combining the resource CLI/MCP/API additions with dev’s tunnel and SSH-session additions.

Merge decision: `origin/dev` adds the remote tunnel contract as its own §6, which overlaps the step 24 resource contract. I preserved the resource contract in §6 as requested and retained the remote tunnel material under §7; resource pages will explicitly report remote hosts as unsupported until #45 adds transport. The seven source conflicts were resolved by combining the resource CLI/MCP/API additions with dev’s tunnel and SSH-session additions.
Author
Owner

Evidence and decision for image/volume paging: the pinned Docker-compatible APIs expose bulk image and volume list operations. Bollard's ListImagesOptions has all, filters, shared_size, digests and manifests, and its list call returns Vec<ImageSummary>; there is no page size or cursor. The current server therefore caps retained summaries and each HTTP page at 100 while scanning one engine inventory at a time. The full compatible-engine response still reaches Bollard for each page request, so daemon-side transfer cannot be capped without replacing the adapter transport. The §6 contract states this limit plainly. Sources: https://docs.rs/bollard/0.21.1/bollard/query_parameters/struct.ListImagesOptions.html and https://docs.docker.com/reference/api/engine/version/v1.40/.

Evidence and decision for image/volume paging: the pinned Docker-compatible APIs expose bulk image and volume list operations. Bollard's `ListImagesOptions` has `all`, `filters`, `shared_size`, `digests` and `manifests`, and its list call returns `Vec<ImageSummary>`; there is no page size or cursor. The current server therefore caps retained summaries and each HTTP page at 100 while scanning one engine inventory at a time. The full compatible-engine response still reaches Bollard for each page request, so daemon-side transfer cannot be capped without replacing the adapter transport. The §6 contract states this limit plainly. Sources: https://docs.rs/bollard/0.21.1/bollard/query_parameters/struct.ListImagesOptions.html and https://docs.docker.com/reference/api/engine/version/v1.40/.
Author
Owner

P2 paging implementation update (commit ab0d6dd): the HTTP handlers retain at most 101 normalized summaries and consume one engine at a time. The Bollard adapter now yields normalized rows incrementally, avoiding a second full-sized summary vector. Docker-compatible image and volume list endpoints still return complete bulk inventories with no native cursor or limit, so the adapter must receive each engine response in full; that protocol limitation remains documented in §6 and is cited in the earlier research note.

P2 paging implementation update (commit `ab0d6dd`): the HTTP handlers retain at most 101 normalized summaries and consume one engine at a time. The Bollard adapter now yields normalized rows incrementally, avoiding a second full-sized summary vector. Docker-compatible image and volume list endpoints still return complete bulk inventories with no native cursor or limit, so the adapter must receive each engine response in full; that protocol limitation remains documented in §6 and is cited in the earlier research note.
Author
Owner

Remote log routing is now covered at the stream boundary too. The real TLS fake-tunnel resource test sends a remote container OPEN over the client WebSocket and receives OPEN_ERR unsupported; the focused test passes (cargo test: 1 passed, 1 filtered out (1 suite, 0.24s)). Commit: 5baa4ed.

Remote log routing is now covered at the stream boundary too. The real TLS fake-tunnel resource test sends a remote container `OPEN` over the client WebSocket and receives `OPEN_ERR unsupported`; the focused test passes (`cargo test: 1 passed, 1 filtered out (1 suite, 0.24s)`). Commit: `5baa4ed`.
Author
Owner

Post-reboot integration finding at 85f5d78: scripts/verify integration stopped in the real engine adapter step. Two Podman-backed tests failed during Compose grouping with Podman 5.4.2 returning:

getting graph driver info "d1046e97ba7810cde4b670d81aa95299f2e24a8d9fd5b77f5ad89c908e23a618": readlink /home/kayg/.local/share/containers/storage/overlay: invalid argument

A direct podman inspect of that exited container reproduces the same error. podman ps and docker ps both respond. The container is named infraternal-engine-foreign-podman-742248-1790425491847-2 and labeled with test run ID 742248-1790425491847; I have not removed it because the run's ownership is not confirmed. Podman has an upstream report with the same error text: https://github.com/containers/podman/issues/16641. No product source change was made for this runtime failure.

Post-reboot integration finding at `85f5d78`: `scripts/verify integration` stopped in the real engine adapter step. Two Podman-backed tests failed during Compose grouping with Podman 5.4.2 returning: ``` getting graph driver info "d1046e97ba7810cde4b670d81aa95299f2e24a8d9fd5b77f5ad89c908e23a618": readlink /home/kayg/.local/share/containers/storage/overlay: invalid argument ``` A direct `podman inspect` of that exited container reproduces the same error. `podman ps` and `docker ps` both respond. The container is named `infraternal-engine-foreign-podman-742248-1790425491847-2` and labeled with test run ID `742248-1790425491847`; I have not removed it because the run's ownership is not confirmed. Podman has an upstream report with the same error text: https://github.com/containers/podman/issues/16641. No product source change was made for this runtime failure.
Author
Owner

Cold production route measurement before the optimization (gzip): /stacks 162,733 B, /images 153,141 B, /volumes 152,950 B; the budget is 122,880 B. The loaded resource pages shared the 9,686 B general VirtualTable chunk, and the custom filter brought in 9,489 B select plus 12,034 B popper-layer-force-mount. This round switches the resource filters to native token-styled controls, uses a small shared virtual resource table with an 8-row overscan, and imports stack log streaming only after opening a stack with members. Screenshot baseline matrix and the prior production interaction checks completed under /mnt/hdd/infraternal/artifacts/s2-stacks/. Fast gate has passed after these edits; production bundle metrics still need remeasurement.

Cold production route measurement before the optimization (gzip): `/stacks` 162,733 B, `/images` 153,141 B, `/volumes` 152,950 B; the budget is 122,880 B. The loaded resource pages shared the 9,686 B general `VirtualTable` chunk, and the custom filter brought in 9,489 B `select` plus 12,034 B `popper-layer-force-mount`. This round switches the resource filters to native token-styled controls, uses a small shared virtual resource table with an 8-row overscan, and imports stack log streaming only after opening a stack with members. Screenshot baseline matrix and the prior production interaction checks completed under `/mnt/hdd/infraternal/artifacts/s2-stacks/`. Fast gate has passed after these edits; production bundle metrics still need remeasurement.
Author
Owner

Post-edit scripts/verify browser attempt exited before building the release binary: FAIL browser: terminal fixture Playwright because port 4175 was already in use. No updated resource-route assertion ran. The listener was gone by the follow-up port check, but another job started integration/browser gates; I will retry when no other integration/browser gate is active and load5 is below 8.

Post-edit `scripts/verify browser` attempt exited before building the release binary: `FAIL browser: terminal fixture Playwright` because port 4175 was already in use. No updated resource-route assertion ran. The listener was gone by the follow-up port check, but another job started integration/browser gates; I will retry when no other integration/browser gate is active and load5 is below 8.
Author
Owner

Ownership check and cleanup: the exited infraternal-engine-podman-742248-1790425491847 container was created at 2026-09-26 14:25 and carried this integration test run's PID/timestamp ID. integration.rs::unique_run_id builds that exact ID, spawn_foreign_event_churn uses it for the foreign fixture, and ContainerGuard labels fixtures infraternal.test=<run_id>. The previously failing foreign container ID is no longer present; I removed only the remaining exited container with this exact run ID. Both exact Podman label filters are now empty and podman ps responds.

Ownership check and cleanup: the exited `infraternal-engine-podman-742248-1790425491847` container was created at 2026-09-26 14:25 and carried this integration test run's PID/timestamp ID. `integration.rs::unique_run_id` builds that exact ID, `spawn_foreign_event_churn` uses it for the foreign fixture, and `ContainerGuard` labels fixtures `infraternal.test=<run_id>`. The previously failing foreign container ID is no longer present; I removed only the remaining exited container with this exact run ID. Both exact Podman label filters are now empty and `podman ps` responds.
Author
Owner

The first post-cleanup integration run passed the engine adapters, bounded log snapshots, and resource HTTP API, then failed in cli_and_mcp_use_the_public_http_contract at the MCP continuous-log assertion. The tool calls read_container_logs(..., follow = false) and its description says it returns a finite snapshot; truncated only includes duration_limit when the absolute read deadline actually expires. The test fixture may already contain its 100-line tail, so a snapshot can reach EOF before the one-second deadline while the container continues writing. Updated the integration assertion to retain the hard response-time check and verify schema, 100-record/1 MiB caps, supported truncation reasons, and truncated consistency without requiring a timeout. Fast gate now passes; integration will be rerun when the shared gate slot is clear.

The first post-cleanup integration run passed the engine adapters, bounded log snapshots, and resource HTTP API, then failed in `cli_and_mcp_use_the_public_http_contract` at the MCP continuous-log assertion. The tool calls `read_container_logs(..., follow = false)` and its description says it returns a finite snapshot; `truncated` only includes `duration_limit` when the absolute read deadline actually expires. The test fixture may already contain its 100-line tail, so a snapshot can reach EOF before the one-second deadline while the container continues writing. Updated the integration assertion to retain the hard response-time check and verify schema, 100-record/1 MiB caps, supported truncation reasons, and `truncated` consistency without requiring a timeout. Fast gate now passes; integration will be rerun when the shared gate slot is clear.
Author
Owner

The post-MCP integration rerun passed release build, HTTP behavior and Docker/Podman socket checks, then failed docker_and_podman_share_the_real_engine_contract at crates/engine/tests/integration.rs:1255: the Podman log snapshot did not contain an untruncated stdout-line after the existing 30 × 100 ms reread loop. This same engine adapter stage passed in the earlier integration run (/tmp/fix24-integration-post-fix.log). The test guards removed their fixtures; exact Docker and Podman filters for run id 216044-1790440293344 are empty. I’m treating this as unresolved until a focused diagnosis/retry determines whether it reproduces.

The post-MCP integration rerun passed release build, HTTP behavior and Docker/Podman socket checks, then failed `docker_and_podman_share_the_real_engine_contract` at `crates/engine/tests/integration.rs:1255`: the Podman log snapshot did not contain an untruncated `stdout-line` after the existing 30 × 100 ms reread loop. This same engine adapter stage passed in the earlier integration run (`/tmp/fix24-integration-post-fix.log`). The test guards removed their fixtures; exact Docker and Podman filters for run id `216044-1790440293344` are empty. I’m treating this as unresolved until a focused diagnosis/retry determines whether it reproduces.
Author
Owner

Focused real-engine follow-up for the prior adapter failure passed: cargo test: 1 passed, 10 filtered out (1 suite, 91.32s). The failure-only record summary was not emitted, and the exact test passes on rootless Podman again. This points to an intermittent Podman log-flush/visibility window rather than a repeatable decoding regression; the failure now carries bounded stream/line metadata if it recurs. I will retain the same 3 s async-flush reread contract and rerun the full integration gate.

Focused real-engine follow-up for the prior adapter failure passed: `cargo test: 1 passed, 10 filtered out (1 suite, 91.32s)`. The failure-only record summary was not emitted, and the exact test passes on rootless Podman again. This points to an intermittent Podman log-flush/visibility window rather than a repeatable decoding regression; the failure now carries bounded stream/line metadata if it recurs. I will retain the same 3 s async-flush reread contract and rerun the full integration gate.
Author
Owner

The browser gate successfully ran after avoiding the occupied terminal-test port, but its real production-browser cold-route assertion still fails at /stacks: METRIC /stacks first-load JS gzip: 184290 bytes, exceeding 120 KiB. The gate stops before measuring /images and /volumes. I’m tracing the remaining eager route chunks and will capture the three-route matrix again after reducing the actual cold payload; no browser pass is claimed yet.

The browser gate successfully ran after avoiding the occupied terminal-test port, but its real production-browser cold-route assertion still fails at `/stacks`: `METRIC /stacks first-load JS gzip: 184290 bytes`, exceeding 120 KiB. The gate stops before measuring `/images` and `/volumes`. I’m tracing the remaining eager route chunks and will capture the three-route matrix again after reducing the actual cold payload; no browser pass is claimed yet.
Author
Owner

Browser cold-route evidence: the gate repeatedly reports /stacks at 184,290 B gzip, including extra chunks DcCBQFQt.js (40,957 B), Cf4njSv0.js (12,207 B), and th4EFyoC.js (7,718 B). The standalone production capture measured /stacks, /images, /volumes at 119,915 / 118,894 / 118,707 B and captured all 12 requested screenshots. Foregrounding the gate's new page and moving its cursor off the sidebar did not change the gate result, so pointer position was not the cause. The checker now measures in a fresh browser context initialized from authenticated storage state, with cache disabled, to isolate context-level state. This is still under verification; no browser pass is claimed. Latest failed run: /tmp/fix24-browser-final-pointer.log; current isolated-context run is pending a free browser-gate slot.

Browser cold-route evidence: the gate repeatedly reports `/stacks` at 184,290 B gzip, including extra chunks `DcCBQFQt.js` (40,957 B), `Cf4njSv0.js` (12,207 B), and `th4EFyoC.js` (7,718 B). The standalone production capture measured `/stacks`, `/images`, `/volumes` at 119,915 / 118,894 / 118,707 B and captured all 12 requested screenshots. Foregrounding the gate's new page and moving its cursor off the sidebar did not change the gate result, so pointer position was not the cause. The checker now measures in a fresh browser context initialized from authenticated storage state, with cache disabled, to isolate context-level state. This is still under verification; no browser pass is claimed. Latest failed run: `/tmp/fix24-browser-final-pointer.log`; current isolated-context run is pending a free browser-gate slot.
Author
Owner

Browser evidence from /tmp/fix24-browser-focus-and-initiators.log: the focus-handoff wait passed. Moving the cold page's pointer after document commit and before hydration measured /stacks at 121,050 B gzip, within the 122,880 B limit. The following /images check timed out waiting for its heading after each route was given a separate browser context initialized from the original storage state. Likely cause is an auth cookie refresh that was not carried into the next context (inference). The checker now reuses the authenticated context, opens a new page per route, disables and clears HTTP cache, and parks the cursor after commit. This adjustment is pending a full browser-gate run; no browser pass is claimed.

Browser evidence from `/tmp/fix24-browser-focus-and-initiators.log`: the focus-handoff wait passed. Moving the cold page's pointer after document commit and before hydration measured `/stacks` at 121,050 B gzip, within the 122,880 B limit. The following `/images` check timed out waiting for its heading after each route was given a separate browser context initialized from the original storage state. Likely cause is an auth cookie refresh that was not carried into the next context (inference). The checker now reuses the authenticated context, opens a new page per route, disables and clears HTTP cache, and parks the cursor after commit. This adjustment is pending a full browser-gate run; no browser pass is claimed.
Author
Owner

The production browser gate now captured all three cold route sizes (before /stacks navigated to the next route):

METRIC /stacks first-load JS gzip: 121050 bytes
METRIC /images first-load JS gzip: 120035 bytes
METRIC /volumes first-load JS gzip: 119839 bytes

All are within the 122,880 B budget. The same browser run then failed in checkInitialResourceLoading: getByRole("status") matched three live regions, so the loading assertion was ambiguous. Its cleanup also reported the intercepted request as already handled after that assertion aborted. Narrowed the assertion to the status region containing the expected resource loading text. This is a browser-check fix; a full passing browser gate is still pending. Log: /tmp/fix24-browser-reused-auth-context.log.

The production browser gate now captured all three cold route sizes (before `/stacks` navigated to the next route): ``` METRIC /stacks first-load JS gzip: 121050 bytes METRIC /images first-load JS gzip: 120035 bytes METRIC /volumes first-load JS gzip: 119839 bytes ``` All are within the 122,880 B budget. The same browser run then failed in `checkInitialResourceLoading`: `getByRole("status")` matched three live regions, so the loading assertion was ambiguous. Its cleanup also reported the intercepted request as already handled after that assertion aborted. Narrowed the assertion to the status region containing the expected resource loading text. This is a browser-check fix; a full passing browser gate is still pending. Log: `/tmp/fix24-browser-reused-auth-context.log`.
Author
Owner

Correction to the preceding browser note: the following browser run did not keep all three pages under budget. It measured /stacks at 121,050 B, /images at 120,035 B, and /volumes at 183,079 B. Thus /volumes is still a reproducible cold-load risk despite the preceding 119,839 B result. The extra chunks are the same large script-initiated group; Chrome's event gave no source frames. Added pointer/focus event capture and optional CDP debug-stack attachment to the failure details, committed as 0b92219. Next browser run is pending the shared browser slot. No final browser pass is claimed.

Correction to the preceding browser note: the following browser run did not keep all three pages under budget. It measured `/stacks` at 121,050 B, `/images` at 120,035 B, and `/volumes` at 183,079 B. Thus `/volumes` is still a reproducible cold-load risk despite the preceding 119,839 B result. The extra chunks are the same large script-initiated group; Chrome's event gave no source frames. Added pointer/focus event capture and optional CDP debug-stack attachment to the failure details, committed as `0b92219`. Next browser run is pending the shared browser slot. No final browser pass is claimed.
Author
Owner

The latest browser run measured all three cold routes within budget: 121,050 / 120,035 / 119,839 B (/tmp/fix24-browser-volume-trigger.log). It then failed in the restart-plan flow because the dialog has two role=status live regions (stack-log status and restart-plan status). I filtered by the expected plan text and also scoped the Load more count locator to its volume-count text; committed as a8eddee. This run did not complete the browser gate, so no pass is claimed.

The latest browser run measured all three cold routes within budget: 121,050 / 120,035 / 119,839 B (`/tmp/fix24-browser-volume-trigger.log`). It then failed in the restart-plan flow because the dialog has two `role=status` live regions (stack-log status and restart-plan status). I filtered by the expected plan text and also scoped the Load more count locator to its volume-count text; committed as `a8eddee`. This run did not complete the browser gate, so no pass is claimed.
Author
Owner

The latest browser run (/tmp/fix24-browser-final-pass.log) again measured /stacks 121,050 B, /images 120,035 B, and /volumes 119,839 B. It then failed the paused restart-plan assertion: the test's broad getByRole("dialog") also saw the stack sheet's Restart stack trigger, even though the plan dialog correctly omits its confirm button until a plan exists. Scoped the locator to the accessible dialog name Restart ${stackProject}?; committed as 9ad327e. The reported route-already-handled error followed the aborted assertion. The full browser gate still needs to complete.

The latest browser run (`/tmp/fix24-browser-final-pass.log`) again measured `/stacks` 121,050 B, `/images` 120,035 B, and `/volumes` 119,839 B. It then failed the paused restart-plan assertion: the test's broad `getByRole("dialog")` also saw the stack sheet's `Restart stack` trigger, even though the plan dialog correctly omits its confirm button until a plan exists. Scoped the locator to the accessible dialog name `Restart ${stackProject}?`; committed as `9ad327e`. The reported route-already-handled error followed the aborted assertion. The full browser gate still needs to complete.
Author
Owner

The browser gate at a8eddee measured /stacks, /images, and /volumes at 121050, 120035, and 119839 bytes gzip, all below the 122880-byte cold-route limit. It then failed the restart-plan assertion because the broad dialog locator also matched the detail sheet Restart stack button. Scoped the assertion to the accessible restart-plan dialog and committed the test fix as 9ad327e. The next full browser run is waiting for the shared browser slot.

The browser gate at a8eddee measured /stacks, /images, and /volumes at 121050, 120035, and 119839 bytes gzip, all below the 122880-byte cold-route limit. It then failed the restart-plan assertion because the broad dialog locator also matched the detail sheet Restart stack button. Scoped the assertion to the accessible restart-plan dialog and committed the test fix as 9ad327e. The next full browser run is waiting for the shared browser slot.
Author
Owner

A browser rerun at 9ad327e reproduced the cold-load flake: /stacks measured 184290 B gzip and requested the sidebar interaction chunks. The captured event trace showed the new page reaching sidebar pointer events at (0,0) before the checker moved the cursor after response commit. Moved the cursor before navigation in 3e84e08; scripts/verify fast passes at that head. Full browser verification is pending because another browser gate is active.

A browser rerun at 9ad327e reproduced the cold-load flake: /stacks measured 184290 B gzip and requested the sidebar interaction chunks. The captured event trace showed the new page reaching sidebar pointer events at (0,0) before the checker moved the cursor after response commit. Moved the cursor before navigation in 3e84e08; scripts/verify fast passes at that head. Full browser verification is pending because another browser gate is active.
Author
Owner

Integration at 8f508b5 passed the real engine and resource HTTP stages, then the CLI/MCP stage failed in its Podman exec case with could not write command output: Broken pipe. The test read exactly the expected 12 bytes and dropped stdout before waiting for the CLI's remote CLOSE. Changed the test reader to drain to EOF while still asserting the exact bytes. A targeted run with INFRATERNAL_CLI_MCP_SIGHUP_ONLY=1 then passed in 12.71s. Full integration rerun is pending.

Integration at 8f508b5 passed the real engine and resource HTTP stages, then the CLI/MCP stage failed in its Podman exec case with `could not write command output: Broken pipe`. The test read exactly the expected 12 bytes and dropped stdout before waiting for the CLI's remote CLOSE. Changed the test reader to drain to EOF while still asserting the exact bytes. A targeted run with INFRATERNAL_CLI_MCP_SIGHUP_ONLY=1 then passed in 12.71s. Full integration rerun is pending.
Author
Owner

The production browser run at 134bb1c measured cold JS gzip at /stacks 121048 B, /images 120033 B, and /volumes 119837 B, under 122880 B on all three routes. The run then failed because the image filter test's generic Engine label matched both its select and the Sort by Engine button. Scoped the locator to the Engine combobox and committed 6962fd1; final browser rerun is pending.

The production browser run at 134bb1c measured cold JS gzip at /stacks 121048 B, /images 120033 B, and /volumes 119837 B, under 122880 B on all three routes. The run then failed because the image filter test's generic Engine label matched both its select and the Sort by Engine button. Scoped the locator to the Engine combobox and committed 6962fd1; final browser rerun is pending.
Author
Owner

The browser gate on 6962fd1 measured cold JS gzip at /stacks 121048 B, /images 120033 B, and /volumes 119837 B, and passed the image filter check. It then failed during the light desktop dashboard screenshot pass on an HTTP 500 console error; the prior diagnostic omitted its URL. Added bounded same-origin 5xx method/path reporting (excluding expected 503) in 5836450. The diagnostic rerun is waiting for the shared browser slot.

The browser gate on 6962fd1 measured cold JS gzip at /stacks 121048 B, /images 120033 B, and /volumes 119837 B, and passed the image filter check. It then failed during the light desktop dashboard screenshot pass on an HTTP 500 console error; the prior diagnostic omitted its URL. Added bounded same-origin 5xx method/path reporting (excluding expected 503) in 5836450. The diagnostic rerun is waiting for the shared browser slot.
Author
Owner

The 500 path from the browser diagnostic matches the deliberate image-detail retry test, which fulfills one 500 response before retrying successfully. The global console check treated that expected response as an app error. Added separate counters for the injected response and its browser console message, asserted both are observed, and retained diagnostics for all other same-origin 5xx responses; committed as 0cb68f2. A final browser rerun will verify whether any unrelated 500 remains.

The 500 path from the browser diagnostic matches the deliberate image-detail retry test, which fulfills one 500 response before retrying successfully. The global console check treated that expected response as an app error. Added separate counters for the injected response and its browser console message, asserted both are observed, and retained diagnostics for all other same-origin 5xx responses; committed as 0cb68f2. A final browser rerun will verify whether any unrelated 500 remains.
Author
Owner

Additional browser finding and fix for #41:

At 0cb68f2, the browser gate reached the tablet layout check after all resource cold-route budgets passed (stacks 121,048 B, images 120,033 B, volumes 119,837 B gzip). At a 768px viewport the desktop sidebar left the resource page's main content 520px wide; viewport-based filter breakpoints did not apply, so the Host filter extended 56px past the content edge. The existing clipping assertion exposed this on the pre-fix render.

Changed the shared ResourceFilterBar.svelte to use available container width for its layout breakpoints. Committed as 08a82e8 fix(web): adapt resource filters to card width. scripts/verify fast passed; output is recorded verbatim in the final gate report. A different worktree's browser gate is active, so I am waiting before launching integration.

Additional browser finding and fix for #41: At `0cb68f2`, the browser gate reached the tablet layout check after all resource cold-route budgets passed (stacks 121,048 B, images 120,033 B, volumes 119,837 B gzip). At a 768px viewport the desktop sidebar left the resource page's main content 520px wide; viewport-based filter breakpoints did not apply, so the Host filter extended 56px past the content edge. The existing clipping assertion exposed this on the pre-fix render. Changed the shared `ResourceFilterBar.svelte` to use available container width for its layout breakpoints. Committed as `08a82e8 fix(web): adapt resource filters to card width`. `scripts/verify fast` passed; output is recorded verbatim in the final gate report. A different worktree's browser gate is active, so I am waiting before launching integration.
Author
Owner

fix-24 complete

Branch: stacks-images
Head: 08a82e85ef4adc8a19ceb7efa352ecaa07ec7819

The remote-host error/skip contract, bounded image and volume paging, stale-filter response handling, row-backed resource cells, detail/log code splitting, cold-route budgets, and review/UI findings are implemented. The final responsive fix makes shared resource filters adapt to the card width available alongside the desktop sidebar.

Two regressions were observed failing before their fixes: the fake tunnel inventory test failed its unsupported_hosts assertion before remote-host reporting was added; the pre-fix browser clipping assertion found the Host filter 56px beyond the main content edge at 768px. Both checks pass after the fixes.

Cold-route gzip budget was 122,880 bytes. Before the cold-load optimization: stacks 162,733 B, images 153,141 B, volumes 152,950 B. Final production browser run: stacks 121,073 B, images 120,058 B, volumes 119,862 B.

The requested screenshot matrix is present under /mnt/hdd/infraternal/artifacts/s2-stacks/: stacks, images, and volumes in light and dark at 1440×900 and 390×844. file confirmed all 12 PNG dimensions.

scripts/verify fast (verbatim)

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 (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
PASS integration: remote mTLS infraternal agent enroll, rotate, list, exec and revoke against Docker

scripts/verify browser (verbatim)

PASS browser: JavaScript dependencies
PASS browser: install Chromium and system dependencies
PASS browser: terminal fixture Playwright
PASS browser: build web app
PASS browser: build release binary
METRIC /stacks first-load JS gzip: 121073 bytes
METRIC /images first-load JS gzip: 120058 bytes
METRIC /volumes first-load JS gzip: 119862 bytes
METRIC browser log flood: {"recordsPerSecond":17487,"frames":109,"fps":60,"retained":10000,"bytes":210000,"dropped":48400,"renderedRows":29}
PASS browser: Playwright against release server
PASS browser: terminal production first-load budget
## fix-24 complete Branch: `stacks-images` Head: `08a82e85ef4adc8a19ceb7efa352ecaa07ec7819` The remote-host error/skip contract, bounded image and volume paging, stale-filter response handling, row-backed resource cells, detail/log code splitting, cold-route budgets, and review/UI findings are implemented. The final responsive fix makes shared resource filters adapt to the card width available alongside the desktop sidebar. Two regressions were observed failing before their fixes: the fake tunnel inventory test failed its `unsupported_hosts` assertion before remote-host reporting was added; the pre-fix browser clipping assertion found the Host filter 56px beyond the main content edge at 768px. Both checks pass after the fixes. Cold-route gzip budget was 122,880 bytes. Before the cold-load optimization: stacks 162,733 B, images 153,141 B, volumes 152,950 B. Final production browser run: stacks 121,073 B, images 120,058 B, volumes 119,862 B. The requested screenshot matrix is present under `/mnt/hdd/infraternal/artifacts/s2-stacks/`: stacks, images, and volumes in light and dark at 1440×900 and 390×844. `file` confirmed all 12 PNG dimensions. ### `scripts/verify fast` (verbatim) ```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` (verbatim) ```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 PASS integration: remote mTLS infraternal agent enroll, rotate, list, exec and revoke against Docker ``` ### `scripts/verify browser` (verbatim) ```text PASS browser: JavaScript dependencies PASS browser: install Chromium and system dependencies PASS browser: terminal fixture Playwright PASS browser: build web app PASS browser: build release binary METRIC /stacks first-load JS gzip: 121073 bytes METRIC /images first-load JS gzip: 120058 bytes METRIC /volumes first-load JS gzip: 119862 bytes METRIC browser log flood: {"recordsPerSecond":17487,"frames":109,"fps":60,"retained":10000,"bytes":210000,"dropped":48400,"renderedRows":29} PASS browser: Playwright against release server PASS browser: terminal production first-load budget ```
Author
Owner

verify-24 starting. Branch: stacks-images; base head: 08a82e85ef4adc8a19ceb7efa352ecaa07ec7819. I will verify the review findings against this fix-round head, prove two regressions in a throwaway worktree, merge origin/dev at the specified head, and run the required gates subject to the load and gate-priority checks.

verify-24 starting. Branch: `stacks-images`; base head: `08a82e85ef4adc8a19ceb7efa352ecaa07ec7819`. I will verify the review findings against this fix-round head, prove two regressions in a throwaway worktree, merge `origin/dev` at the specified head, and run the required gates subject to the load and gate-priority checks.
Author
Owner

Verification evidence on the fix-round head 08a82e85ef4adc8a19ceb7efa352ecaa07ec7819:

  • In /home/kayg/wt/verify-24-tmp at 08a82e8, removing .min(MAX_RESOURCE_PAGE_SIZE) made resource_page_options_cap_size_and_reject_invalid_or_foreign_cursors fail (left: usize::MAX, right: 100).
  • In that throwaway worktree, restoring not_found(request_id, "host") for the visible remote resource path made remote_resource_reads_surface_unsupported_hosts_instead_of_not_found fail at /api/v1/images?host=… (404 vs expected 501). Before reaching that assertion, the same test confirmed omitted-host lists include the connected remote host with reason: "unsupported". Both temporary mutations were restored; the worktree is clean.
  • Current-code review: resources.rs clamps list limits at 100 and retains limit + 1 candidates while counting all matching local summaries; the resource contract documents that Docker-compatible engine calls still return full upstream inventories. Each resource page captures a request generation before awaiting, and the browser race check delays the old response and asserts it cannot replace newer results on images, volumes or stacks. Resource table snippets receive precomputed rows; route cells do not scan the inventory. The browser check exercises page clipping and unmarked text clipping at 390×844.

Full post-merge gate verification remains pending.

Verification evidence on the fix-round head `08a82e85ef4adc8a19ceb7efa352ecaa07ec7819`: - In `/home/kayg/wt/verify-24-tmp` at `08a82e8`, removing `.min(MAX_RESOURCE_PAGE_SIZE)` made `resource_page_options_cap_size_and_reject_invalid_or_foreign_cursors` fail (`left: usize::MAX`, `right: 100`). - In that throwaway worktree, restoring `not_found(request_id, "host")` for the visible remote resource path made `remote_resource_reads_surface_unsupported_hosts_instead_of_not_found` fail at `/api/v1/images?host=…` (`404` vs expected `501`). Before reaching that assertion, the same test confirmed omitted-host lists include the connected remote host with `reason: "unsupported"`. Both temporary mutations were restored; the worktree is clean. - Current-code review: `resources.rs` clamps list limits at 100 and retains `limit + 1` candidates while counting all matching local summaries; the resource contract documents that Docker-compatible engine calls still return full upstream inventories. Each resource page captures a request generation before awaiting, and the browser race check delays the old response and asserts it cannot replace newer results on images, volumes or stacks. Resource table snippets receive precomputed rows; route cells do not scan the inventory. The browser check exercises page clipping and unmarked text clipping at 390×844. Full post-merge gate verification remains pending.
Author
Owner

Integration attempt on the merged tree failed in compiled CLI and MCP against real server and engines after all preceding integration steps passed. The real SSH client process printed a session ID but did not exit within the existing five-second bound for piped stdin EOF:

thread 'cli_and_mcp_use_the_public_http_contract' panicked at crates/infraternal/tests/cli_mcp.rs:2297:13:
SSH remote exit after piped stdin EOF did not finish within five seconds: Warning: the OS keychain is unavailable; using a permission-restricted credential file.
Session\ts_01M3G15J3BJ32C1RHFVZDJ0DHR

I am checking whether this reproduces in isolation before attributing it to the merge. No source fix has been made.

Integration attempt on the merged tree failed in `compiled CLI and MCP against real server and engines` after all preceding integration steps passed. The real SSH client process printed a session ID but did not exit within the existing five-second bound for piped stdin EOF: ``` thread 'cli_and_mcp_use_the_public_http_contract' panicked at crates/infraternal/tests/cli_mcp.rs:2297:13: SSH remote exit after piped stdin EOF did not finish within five seconds: Warning: the OS keychain is unavailable; using a permission-restricted credential file. Session\ts_01M3G15J3BJ32C1RHFVZDJ0DHR ``` I am checking whether this reproduces in isolation before attributing it to the merge. No source fix has been made.
Author
Owner

Integration initially failed twice in compiled CLI and MCP against real server and engines; a targeted full-path rerun also reproduced the same SSH EOF hang. The timeout diagnostic captured the remote shell output ending in exit\r\n while the local CLI remained attached, so the shell had exited but the SSH channel close handshake was not completing.

The failing path was in the merged dev commit 0719596 (Fix CLI exec stdin EOF handling). In crates/ssh/src/session.rs::drive_pty, receiving ChannelMsg::ExitStatus recorded the status and then waited indefinitely for the peer close. Added a bounded local channel close request on exit-status while continuing to drain channel data. This follows RFC 4254 §6.10, which says the channel is closed after exit-status.

After the change, the full real CLI/MCP integration test passed against Docker and Podman:

cargo test: 1 passed (1 suite, 133.15s)

The failure diagnostic now includes stdout as well as stderr for future timeout evidence. Full scripts/verify integration still needs a passing rerun.

Integration initially failed twice in `compiled CLI and MCP against real server and engines`; a targeted full-path rerun also reproduced the same SSH EOF hang. The timeout diagnostic captured the remote shell output ending in `exit\r\n` while the local CLI remained attached, so the shell had exited but the SSH channel close handshake was not completing. The failing path was in the merged `dev` commit `0719596` (`Fix CLI exec stdin EOF handling`). In `crates/ssh/src/session.rs::drive_pty`, receiving `ChannelMsg::ExitStatus` recorded the status and then waited indefinitely for the peer close. Added a bounded local channel close request on exit-status while continuing to drain channel data. This follows [RFC 4254 §6.10](https://www.rfc-editor.org/rfc/rfc4254.html#section-6.10), which says the channel is closed after exit-status. After the change, the full real CLI/MCP integration test passed against Docker and Podman: ``` cargo test: 1 passed (1 suite, 133.15s) ``` The failure diagnostic now includes stdout as well as stderr for future timeout evidence. Full `scripts/verify integration` still needs a passing rerun.
Author
Owner

Browser-gate evidence after merged head 47a2890b28b3af2a3962bf1ee2a5a90d8071a0ed: cold dashboard JavaScript was 123,754 bytes gzip against the 122,880-byte cap. The built chunk containing DragDropManager/dnd-kit is 40,644 bytes gzip. Sidebar.svelte lazily imports SidebarInteractive.svelte, but currently starts that import on any sidebar pointermove; I am narrowing that prefetch to navigation-content intent while preserving click, keyboard-focus and mobile-open loading. The run passed “Jump to latest” and then stopped at the initial bundle budget, so the three 390×844 resource clipping checks did not run.

Browser-gate evidence after merged head `47a2890b28b3af2a3962bf1ee2a5a90d8071a0ed`: cold dashboard JavaScript was 123,754 bytes gzip against the 122,880-byte cap. The built chunk containing `DragDropManager`/`dnd-kit` is 40,644 bytes gzip. `Sidebar.svelte` lazily imports `SidebarInteractive.svelte`, but currently starts that import on any sidebar `pointermove`; I am narrowing that prefetch to navigation-content intent while preserving click, keyboard-focus and mobile-open loading. The run passed “Jump to latest” and then stopped at the initial bundle budget, so the three 390×844 resource clipping checks did not run.
Author
Owner

The production browser run after 0f03fba028a3424ecabf45ebeccf2d04d9aa4f8d still measured 123,786 B gzip for the initial route against the 122,880 B cap (906 B over), so narrowing prefetch to .sidebar-content did not close the budget finding. Sidebar persistence and both exercised “Jump to latest” checks passed; the gate stopped before the three 390×844 resource clipping assertions. I am instrumenting the captured script URLs and pointer event before the next correction.

The production browser run after `0f03fba028a3424ecabf45ebeccf2d04d9aa4f8d` still measured 123,786 B gzip for the initial route against the 122,880 B cap (906 B over), so narrowing prefetch to `.sidebar-content` did not close the budget finding. Sidebar persistence and both exercised “Jump to latest” checks passed; the gate stopped before the three 390×844 resource clipping assertions. I am instrumenting the captured script URLs and pointer event before the next correction.
Author
Owner

Browser evidence on 82994a7: setup, terminal fixture, web build and release build passed, then the sidebar persistence check timed out waiting for data-controls-ready="true" after .app-sidebar.hover() (5s). The cursor was already in the sidebar, so the hover issued no nonzero pointer movement and did not request the sortable module. The test helper now deliberately moves 8px through .sidebar-content before asserting readiness; this exercises the intended pointer-prefetch path. The run stopped before the cold-route metric and before 390×844 resource checks.

Browser evidence on `82994a7`: setup, terminal fixture, web build and release build passed, then the sidebar persistence check timed out waiting for `data-controls-ready="true"` after `.app-sidebar.hover()` (5s). The cursor was already in the sidebar, so the hover issued no nonzero pointer movement and did not request the sortable module. The test helper now deliberately moves 8px through `.sidebar-content` before asserting readiness; this exercises the intended pointer-prefetch path. The run stopped before the cold-route metric and before 390×844 resource checks.
Author
Owner

Browser gate on 82994a7 still fails the cold-route JavaScript budget: 123,806 bytes against the 122,880-byte cap (+926 bytes). “Jump to latest” passed twice, and sidebar drag/reload/second-context checks passed. The captured initial-route chunk list did not include the sortable controls chunk, so deferring that import did not remove the excess. The gate stops before the mobile clipping checks. I am tracing the eager sidebar persistence code from the dev merge; the higher-priority port-c1 browser gate is currently running, so I have not started another browser run.

Browser gate on 82994a7 still fails the cold-route JavaScript budget: 123,806 bytes against the 122,880-byte cap (+926 bytes). “Jump to latest” passed twice, and sidebar drag/reload/second-context checks passed. The captured initial-route chunk list did not include the sortable controls chunk, so deferring that import did not remove the excess. The gate stops before the mobile clipping checks. I am tracing the eager sidebar persistence code from the dev merge; the higher-priority port-c1 browser gate is currently running, so I have not started another browser run.
Author
Owner

After commit 11e1bd2 moved conflict-only rebasing out of the initial route, the browser gate measured 123,006 B of initial-route JS gzip, 126 B over the 122,880 B cap (previous run: 123,806 B). The separately compiled navigation-rebase entry is not in the captured initial-route chunk list. “Jump to latest” passed twice, and sidebar persistence drag/reload/second-context checks passed. The gate still stops before the 390×844 clipping assertions. I am reducing another rare migration path from the initial route before the next browser run.

After commit 11e1bd2 moved conflict-only rebasing out of the initial route, the browser gate measured 123,006 B of initial-route JS gzip, 126 B over the 122,880 B cap (previous run: 123,806 B). The separately compiled navigation-rebase entry is not in the captured initial-route chunk list. “Jump to latest” passed twice, and sidebar persistence drag/reload/second-context checks passed. The gate still stops before the 390×844 clipping assertions. I am reducing another rare migration path from the initial route before the next browser run.
Author
Owner

Final integration run at f8eb4db failed in compiled CLI and MCP against real server and engines, in the SSH killed-client replay scenario. The reattached CLI captured the replay markers and echoed finish and exit, but the PTY harness did not observe process termination within 15 seconds. This is after the earlier piped-stdin EOF exit fix and points to the persistent SSH channel exit/close path; I am checking whether it reproduces in the focused CLI/MCP test before treating it as a regression.

Final integration run at f8eb4db failed in `compiled CLI and MCP against real server and engines`, in the SSH killed-client replay scenario. The reattached CLI captured the replay markers and echoed `finish` and `exit`, but the PTY harness did not observe process termination within 15 seconds. This is after the earlier piped-stdin EOF exit fix and points to the persistent SSH channel exit/close path; I am checking whether it reproduces in the focused CLI/MCP test before treating it as a regression.
Author
Owner

Integration gate attempt on f8eb4db1 plus the uncommitted merge-related SSH close/drain fix failed before SSH product checks. Docker/Podman adapters, bounded log snapshots and the TS stream client passed. stream authorization and CLOSE regressions against Docker failed at crates/server/tests/stream_regressions.rs:1589: rootless Podman did not consume pre-commit input within 5 seconds; the log also records stream authorization revalidation timed out; closing attachments timeout_ms=250. Full gate output is /tmp/verify24-integration-ssh.log. I am reproducing the exact test before deciding whether this is a code defect or an intermittent gate failure.

Integration gate attempt on `f8eb4db1` plus the uncommitted merge-related SSH close/drain fix failed before SSH product checks. Docker/Podman adapters, bounded log snapshots and the TS stream client passed. `stream authorization and CLOSE regressions against Docker` failed at `crates/server/tests/stream_regressions.rs:1589`: rootless Podman did not consume pre-commit input within 5 seconds; the log also records `stream authorization revalidation timed out; closing attachments timeout_ms=250`. Full gate output is `/tmp/verify24-integration-ssh.log`. I am reproducing the exact test before deciding whether this is a code defect or an intermittent gate failure.
Author
Owner

Update on the stream-regression failure: reran the exact real-engine test in isolation with --ignored --exact --test-threads=1. Result: cargo test: 1 passed, 18 filtered out (1 suite, 14.89s). The full-suite Podman timeout did not reproduce in isolation; I will retry the integration gate when the shared slot clears.

Update on the stream-regression failure: reran the exact real-engine test in isolation with `--ignored --exact --test-threads=1`. Result: `cargo test: 1 passed, 18 filtered out (1 suite, 14.89s)`. The full-suite Podman timeout did not reproduce in isolation; I will retry the integration gate when the shared slot clears.
Author
Owner

Browser gate on a70825a passed setup, build and the “Jump to latest” check, then stopped at the cold-route JavaScript budget: 122,941 bytes against 122,880 (61 bytes over). The checker emitted per-chunk gzip sizes; the 390×844 resource-route clipping assertions did not run because this earlier budget assertion stopped the suite. I am tracing the emitted route chunks for a small merge-related size reduction, then will rerun the browser gate.

Browser gate on `a70825a` passed setup, build and the “Jump to latest” check, then stopped at the cold-route JavaScript budget: 122,941 bytes against 122,880 (61 bytes over). The checker emitted per-chunk gzip sizes; the 390×844 resource-route clipping assertions did not run because this earlier budget assertion stopped the suite. I am tracing the emitted route chunks for a small merge-related size reduction, then will rerun the browser gate.
Author
Owner

Browser rerun after the initial-route trim: cold / now passes at 122,862 bytes. Both “Jump to latest” checks passed. The browser gate then stopped at /stacks first-load JavaScript: 125,474 bytes against 122,880 (2,594 bytes over), with the emitted chunk list and initiators in /tmp/verify24-browser-final-selector.log. The 390×844 route assertions did not run because the stacks budget assertion stopped the suite. I am tracing the stacks route chunks before the next browser run.

Browser rerun after the initial-route trim: cold `/` now passes at 122,862 bytes. Both “Jump to latest” checks passed. The browser gate then stopped at `/stacks` first-load JavaScript: 125,474 bytes against 122,880 (2,594 bytes over), with the emitted chunk list and initiators in `/tmp/verify24-browser-final-selector.log`. The 390×844 route assertions did not run because the stacks budget assertion stopped the suite. I am tracing the stacks route chunks before the next browser run.
Author
Owner

After moving stack member-log coordination to the dynamic stack-member-logs.ts module, the cold / route passed at 122,850 bytes and /stacks fell from 125,474 to 124,850 bytes, but still exceeds 122,880 by 1,970. The browser gate stopped at the stacks route before 390×844 checks; full output is /tmp/verify24-browser-stack-logs.log. I am moving the remaining detail/restart path out of the inventory route.

After moving stack member-log coordination to the dynamic `stack-member-logs.ts` module, the cold `/` route passed at 122,850 bytes and `/stacks` fell from 125,474 to 124,850 bytes, but still exceeds 122,880 by 1,970. The browser gate stopped at the stacks route before 390×844 checks; full output is `/tmp/verify24-browser-stack-logs.log`. I am moving the remaining detail/restart path out of the inventory route.
Author
Owner

Browser verification on c3c3cfa: web and release builds passed, as did multi-tab auth and sidebar persistence. Playwright then timed out after 30 seconds waiting for the Container logs heading while opening a real container detail route; the run log also records a refused curl to its local test port 127.0.0.1:17033. It failed before resource-route bundle or 390×844 assertions, so this run provides no result for the stacks filter split. I will retry the browser gate after the active port-c1 browser run clears.

Browser verification on `c3c3cfa`: web and release builds passed, as did multi-tab auth and sidebar persistence. Playwright then timed out after 30 seconds waiting for the `Container logs` heading while opening a real container detail route; the run log also records a refused curl to its local test port `127.0.0.1:17033`. It failed before resource-route bundle or 390×844 assertions, so this run provides no result for the stacks filter split. I will retry the browser gate after the active port-c1 browser run clears.
Author
Owner

Browser retry on c3c3cfa reached the resource routes after the container log checks passed. “Jump to latest” passed twice. The dashboard measured 122,871 B; /stacks measured 123,764 B, 884 B above its 122,880 B cap. The lazy filter disclosure alone was insufficient. I’m switching the stacks page to the existing shared tokenized Surface primitive to remove its direct Card dependency, then I’ll rerun fast and browser verification.

Browser retry on `c3c3cfa` reached the resource routes after the container log checks passed. “Jump to latest” passed twice. The dashboard measured 122,871 B; `/stacks` measured 123,764 B, 884 B above its 122,880 B cap. The lazy filter disclosure alone was insufficient. I’m switching the stacks page to the existing shared tokenized `Surface` primitive to remove its direct Card dependency, then I’ll rerun fast and browser verification.
Author
Owner

Browser retry on e35ac25 completed its setup, sidebar/multi-tab and container-log checks; “Jump to latest” passed twice. The cold dashboard metric was 122,887 B against the 122,880 B cap, so the gate stopped before route-specific resource checks and the 390×844 clipping assertions. The measured chunks and byte counts are in /tmp/verify24-browser-surface.log. I’m trimming shared cold-route output and will rerun once the active port-c1 browser gate clears.

Browser retry on `e35ac25` completed its setup, sidebar/multi-tab and container-log checks; “Jump to latest” passed twice. The cold dashboard metric was 122,887 B against the 122,880 B cap, so the gate stopped before route-specific resource checks and the 390×844 clipping assertions. The measured chunks and byte counts are in `/tmp/verify24-browser-surface.log`. I’m trimming shared cold-route output and will rerun once the active `port-c1` browser gate clears.
Author
Owner

The first e35ac25 browser attempt was blocked before resource-route checks by the dashboard cold-route JS budget (122,887 B versus 122,880 B). I removed the tiny engineCount helper and used host.engines.length directly; behavior is unchanged. The change is commit f69597b54206c45657370cfd2a2f290288d984ee. scripts/verify fast passed in full; verbatim output is in /tmp/verify24-fast-budget-final.log. A new port-c1 browser gate became active before my retry, so I’m waiting for it to finish.

The first `e35ac25` browser attempt was blocked before resource-route checks by the dashboard cold-route JS budget (122,887 B versus 122,880 B). I removed the tiny `engineCount` helper and used `host.engines.length` directly; behavior is unchanged. The change is commit `f69597b54206c45657370cfd2a2f290288d984ee`. `scripts/verify fast` passed in full; verbatim output is in `/tmp/verify24-fast-budget-final.log`. A new `port-c1` browser gate became active before my retry, so I’m waiting for it to finish.
Author
Owner

Browser gate on f69597b now clears the dashboard cold-route cap at 122,872 B. The route budget then measured /stacks at 137,324 B (17,396 B above the 120 KiB cap) and stopped before mobile clipping checks. The route bundle map shows the stacks page resolving Surface through the @infraternal/ui barrel while its detail component also dynamically imports VirtualLogList from that barrel; I’m switching the route to the configured deep @ui/Surface.svelte import so the cold list route does not pull the package-wide entry.

Browser gate on `f69597b` now clears the dashboard cold-route cap at 122,872 B. The route budget then measured `/stacks` at 137,324 B (17,396 B above the 120 KiB cap) and stopped before mobile clipping checks. The route bundle map shows the stacks page resolving `Surface` through the `@infraternal/ui` barrel while its detail component also dynamically imports `VirtualLogList` from that barrel; I’m switching the route to the configured deep `@ui/Surface.svelte` import so the cold list route does not pull the package-wide entry.
Author
Owner

The @infraternal/ui barrel was bringing its package-wide import graph into the cold /stacks page. I changed the stacks route to import the same shared Surface component through the configured @ui/Surface.svelte alias; this preserves the component and avoids coupling its cold chunk to unrelated package exports. The change is commit 749188baa893ddcc9cbcbe10dd72a670d3841d08; scripts/verify fast passed in full (/tmp/verify24-fast-deep-surface.log). Browser retry is next, subject to gate priority.

The `@infraternal/ui` barrel was bringing its package-wide import graph into the cold `/stacks` page. I changed the stacks route to import the same shared `Surface` component through the configured `@ui/Surface.svelte` alias; this preserves the component and avoids coupling its cold chunk to unrelated package exports. The change is commit `749188baa893ddcc9cbcbe10dd72a670d3841d08`; `scripts/verify fast` passed in full (`/tmp/verify24-fast-deep-surface.log`). Browser retry is next, subject to gate priority.
Author
Owner

The deep Surface import removed the package-wide /stacks dependency, but it shifted the shared app chunk: the latest dashboard first-load measurement is 122,892 B, 12 B over the cap. The dashboard also still has unfinished UI: fixed 0 inbox data, three — host telemetry values with copy saying telemetry is not reported, and a recent-activity card whose description says the audit-log API has not landed. I’m removing those unbuilt status fields/sections as required by the owner rule; the change also gives the shared route budget a safe margin.

The deep `Surface` import removed the package-wide `/stacks` dependency, but it shifted the shared app chunk: the latest dashboard first-load measurement is 122,892 B, 12 B over the cap. The dashboard also still has unfinished UI: fixed `0` inbox data, three `—` host telemetry values with copy saying telemetry is not reported, and a recent-activity card whose description says the audit-log API has not landed. I’m removing those unbuilt status fields/sections as required by the owner rule; the change also gives the shared route budget a safe margin.
Author
Owner

On the latest browser run, the dashboard missed the 120 KiB cold-route cap by 12 B after route chunking changed. I removed three unfinished dashboard features in line with the owner rule: static inbox count, unpopulated host telemetry fields, and the recent-activity section that linked to an API-later page. Host/engine/container status and functional needs-attention reporting remain. This also removes false data and should leave margin under the cold-route cap. The browser gate did not reach any mobile assertions in that run.

On the latest browser run, the dashboard missed the 120 KiB cold-route cap by 12 B after route chunking changed. I removed three unfinished dashboard features in line with the owner rule: static inbox count, unpopulated host telemetry fields, and the recent-activity section that linked to an API-later page. Host/engine/container status and functional needs-attention reporting remain. This also removes false data and should leave margin under the cold-route cap. The browser gate did not reach any mobile assertions in that run.
Author
Owner

The dashboard cleanup passed scripts/verify fast in full (/tmp/verify24-fast-dashboard-final.log) and is committed as d5d2f1a667d471c8bdffc721fea0ad6c18f90936. It removes the fixed inbox count, empty telemetry fields and API-not-yet-built activity card. I’m rerunning browser against this head now, with port-ab/other browser gate priority respected.

The dashboard cleanup passed `scripts/verify fast` in full (`/tmp/verify24-fast-dashboard-final.log`) and is committed as `d5d2f1a667d471c8bdffc721fea0ad6c18f90936`. It removes the fixed inbox count, empty telemetry fields and API-not-yet-built activity card. I’m rerunning browser against this head now, with `port-ab`/other browser gate priority respected.
Author
Owner

After removing the unfinished dashboard fields, the root cold-route metric passed at 122,585 B, but /stacks was still 123,296 B (416 B over). StackSummary already contains the table cell and sort fields, so the route was redundantly copying each bounded page row just to add host/running aliases. I replaced that per-row copy with direct row reuse and changed the sortable column IDs to host_id/running_count; this reduces allocations and generated route code without changing displayed data. Targeted fast/browser gates pending.

After removing the unfinished dashboard fields, the root cold-route metric passed at 122,585 B, but `/stacks` was still 123,296 B (416 B over). `StackSummary` already contains the table cell and sort fields, so the route was redundantly copying each bounded page row just to add `host`/`running` aliases. I replaced that per-row copy with direct row reuse and changed the sortable column IDs to `host_id`/`running_count`; this reduces allocations and generated route code without changing displayed data. Targeted fast/browser gates pending.
Author
Owner

The bounded-row reuse update passes scripts/verify fast in full (/tmp/verify24-fast-row-reuse.log). The browser route budget and responsive checks are pending; no source commit has been made until the browser result is known.

The bounded-row reuse update passes `scripts/verify fast` in full (`/tmp/verify24-fast-row-reuse.log`). The browser route budget and responsive checks are pending; no source commit has been made until the browser result is known.
Author
Owner

The stack table now sorts directly by host_id, container_count and running_count, and uses the typed StackSummary cell values without String/Number conversion wrappers. This removes redundant row processing and some route code without changing the displayed values. scripts/verify fast passed in full (/tmp/verify24-fast-stack-fields.log); the browser route budget and mobile checks are pending.

The stack table now sorts directly by `host_id`, `container_count` and `running_count`, and uses the typed `StackSummary` cell values without `String`/`Number` conversion wrappers. This removes redundant row processing and some route code without changing the displayed values. `scripts/verify fast` passed in full (`/tmp/verify24-fast-stack-fields.log`); the browser route budget and mobile checks are pending.
Author
Owner

The production browser retry on the row-field revision stopped before resource-route checks: its log shows a refused connection to its ephemeral stream fixture port 127.0.0.1:23249, then the container log region did not receive focus. No /stacks budget or 390×844 check ran. The code path is unchanged from the earlier passing “Jump to latest” interaction; I’m retrying the browser gate once with a fresh run/artifact ID.

The production browser retry on the row-field revision stopped before resource-route checks: its log shows a refused connection to its ephemeral stream fixture port `127.0.0.1:23249`, then the container log region did not receive focus. No `/stacks` budget or 390×844 check ran. The code path is unchanged from the earlier passing “Jump to latest” interaction; I’m retrying the browser gate once with a fresh run/artifact ID.
Author
Owner

The fresh browser retry passed both “Jump to latest” checks, sidebar persistence and container log control. Dashboard cold JS measured 122,586 B; /stacks measured 123,247 B, 367 B over the 122,880 B limit. Mobile clipping checks still did not run because the route budget fails first. The bounded summary row reuse/direct-field sort update is committed as a7acb98d9e0f08968b4935ad734fb1782a0b4c78; it passes full scripts/verify fast (/tmp/verify24-fast-stack-fields.log). I’m making one more measured route-size reduction before another browser run.

The fresh browser retry passed both “Jump to latest” checks, sidebar persistence and container log control. Dashboard cold JS measured 122,586 B; `/stacks` measured 123,247 B, 367 B over the 122,880 B limit. Mobile clipping checks still did not run because the route budget fails first. The bounded summary row reuse/direct-field sort update is committed as `a7acb98d9e0f08968b4935ad734fb1782a0b4c78`; it passes full `scripts/verify fast` (`/tmp/verify24-fast-stack-fields.log`). I’m making one more measured route-size reduction before another browser run.
Author
Owner

The latest browser run on a7acb98 passed the container log flow and measured /stacks at 123,247 B, 367 B over the 122,880 B cap. The route was loading a 463 B Skeleton component only for a fixed 18 rem loading block. I removed that component dependency while keeping the tokenized Surface, static muted block, and existing live status announcement. This is a cold-route bundle reduction that does not delay or hide the inventory table; fast/browser verification is pending.

The latest browser run on `a7acb98` passed the container log flow and measured `/stacks` at 123,247 B, 367 B over the 122,880 B cap. The route was loading a 463 B Skeleton component only for a fixed 18 rem loading block. I removed that component dependency while keeping the tokenized `Surface`, static muted block, and existing live status announcement. This is a cold-route bundle reduction that does not delay or hide the inventory table; fast/browser verification is pending.
Author
Owner

Correction from generated chunk inspection: the 463 B DgwHNVrZ.js route chunk was Surface.svelte, not the Skeleton component. Removing the stack loading Skeleton saved 7 B because that chunk remained. I replaced the three stack-page Surface instances with native wrappers using one shared route class and the same design tokens, and documented the 463 B cold-route tradeoff beside the styles. Fast/browser verification is pending.

Correction from generated chunk inspection: the 463 B `DgwHNVrZ.js` route chunk was `Surface.svelte`, not the Skeleton component. Removing the stack loading Skeleton saved 7 B because that chunk remained. I replaced the three stack-page `Surface` instances with native wrappers using one shared route class and the same design tokens, and documented the 463 B cold-route tradeoff beside the styles. Fast/browser verification is pending.
Author
Owner

The cold route now uses one token-backed native surface class for its three static wrappers, avoiding the 463 B shared Surface.svelte component chunk while retaining the same spacing, border, radius and surface colors. The change is committed as 7dc5ac158fdb8be97eeecb28d0994a062b4e69d9. Full scripts/verify fast passed (/tmp/verify24-fast-native-surface.log). Browser verification is next, subject to gate priority.

The cold route now uses one token-backed native surface class for its three static wrappers, avoiding the 463 B shared `Surface.svelte` component chunk while retaining the same spacing, border, radius and surface colors. The change is committed as `7dc5ac158fdb8be97eeecb28d0994a062b4e69d9`. Full `scripts/verify fast` passed (`/tmp/verify24-fast-native-surface.log`). Browser verification is next, subject to gate priority.
Author
Owner

The production browser run on 7dc5ac1 now passes /stacks at 122,697 B under the 122,880 B cap. It stops at /images first load, 124,726 B (1,846 B over), before /volumes and the 390×844 clipping checks. Unlike stacks, Images still statically imports and renders ResourceFilterBar; I’m deferring those controls behind the same accessible filter disclosure, while keeping filter-race test coverage by opening it before typing.

The production browser run on `7dc5ac1` now passes `/stacks` at 122,697 B under the 122,880 B cap. It stops at `/images` first load, 124,726 B (1,846 B over), before `/volumes` and the 390×844 clipping checks. Unlike stacks, Images still statically imports and renders `ResourceFilterBar`; I’m deferring those controls behind the same accessible filter disclosure, while keeping filter-race test coverage by opening it before typing.
Author
Owner

Verification update (2026-09-27):

  • scripts/verify fast passes on the current UI source.
  • scripts/verify integration passed once on the current API/backend and resource-surface source. Two subsequent runs failed only in compiled CLI and MCP against real server and engines, at separate SSH PTY exit waits (one after piped stdin EOF; the retry after command exit 37). No SSH or CLI source was changed. This appears timing-sensitive and needs the final integration rerun recorded before verdict.
  • Browser first-load measurements now pass the 120 KiB budget: Stacks 122,788 B, Images 122,391 B, Volumes 122,208 B. The run stopped in the stack detail check with the sheet still loading after five seconds. The browser check now records the detail API status and verifies the fixture member before waiting for the Members section.
  • port-ab currently owns the browser gate slot; this job is waiting and will not overlap it.
Verification update (2026-09-27): - `scripts/verify fast` passes on the current UI source. - `scripts/verify integration` passed once on the current API/backend and resource-surface source. Two subsequent runs failed only in `compiled CLI and MCP against real server and engines`, at separate SSH PTY exit waits (one after piped stdin EOF; the retry after command exit 37). No SSH or CLI source was changed. This appears timing-sensitive and needs the final integration rerun recorded before verdict. - Browser first-load measurements now pass the 120 KiB budget: Stacks 122,788 B, Images 122,391 B, Volumes 122,208 B. The run stopped in the stack detail check with the sheet still loading after five seconds. The browser check now records the detail API status and verifies the fixture member before waiting for the Members section. - `port-ab` currently owns the browser gate slot; this job is waiting and will not overlap it.
Author
Owner

Browser resource budgets now pass (Stacks 122,788 B; Images 122,391 B; Volumes 122,208 B). The stack detail endpoint returned HTTP 200 with one container and memberPresent=true, but the sheet did not render the Members section within 15 seconds. I added a browser failure diagnostic for the sheet's loading, section and error state. port-ab currently owns the browser slot; this job is waiting before its next browser run.

Browser resource budgets now pass (Stacks 122,788 B; Images 122,391 B; Volumes 122,208 B). The stack detail endpoint returned HTTP 200 with one container and `memberPresent=true`, but the sheet did not render the Members section within 15 seconds. I added a browser failure diagnostic for the sheet's loading, section and error state. `port-ab` currently owns the browser slot; this job is waiting before its next browser run.
Author
Owner

Browser detail root cause and fix:

  • Playwright observed the stack detail endpoint return HTTP 200 with memberPresent=true, while the sheet remained loading:true after 15 seconds.
  • StackDetailSheet started loadStackDetail inside $effect. The request synchronously reads the reactive access token, so token refresh retriggered the effect and invalidated the active detail generation before it could update the sheet.
  • Wrapped the request launch in Svelte untrack, matching existing use in VirtualLogList.svelte and VirtualTable.svelte.
  • Fast and browser verification are pending; port-ab currently owns the browser gate slot.
Browser detail root cause and fix: - Playwright observed the stack detail endpoint return HTTP 200 with `memberPresent=true`, while the sheet remained `loading:true` after 15 seconds. - `StackDetailSheet` started `loadStackDetail` inside `$effect`. The request synchronously reads the reactive access token, so token refresh retriggered the effect and invalidated the active detail generation before it could update the sheet. - Wrapped the request launch in Svelte `untrack`, matching existing use in `VirtualLogList.svelte` and `VirtualTable.svelte`. - Fast and browser verification are pending; `port-ab` currently owns the browser gate slot.
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#41
No description provided.