Step 24 (S2): compose stacks, images and volumes (read-only) #41
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
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.
Starting PLAN step 24 on branch
stacks-images, based ondevdbd66850f8a5d6190e65b4bd364e075950033425. I am following the contract-first sequence: define the stack, image and volume API/scope/audit behavior indocs/contracts.md, commit it, then implement the engine, server, CLI/MCP and lazy web routes.Contract first pass committed on
stacks-imagesat63ccc28ef676d393104304d2d7c0a1a36dba55e9indocs/contracts.md§6.containers:readscope.com.docker.compose.project, withio.podman.compose.projectas fallback; the Docker label wins when both are non-empty. Groups are per host and engine.containers:restartplan with fixed per-member versions for AI agent tokens, and records per-target outcomes without a migration.logsstream for each member; no new stream kind or frame format.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.
Implementation progress for s2-stacks
Commits on branch
stacks-images(not pushed):973fe95engine image/volume adapters and Compose grouping;8529c0astack/image/volume API, multi-target plans and generated OpenAPI client;3c21b3cCLI/MCP commands;469142ainventory 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 becauseplan_targetsalready supports multiple rows. Compose grouping preferscom.docker.compose.projectand falls back toio.podman.compose.project.Checks so far: the focused engine label test passed (
1 passed, 16 filtered out);bun --cwd=packages/web run checkpassed 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.Integration findings and corrections:
crates/engine/tests/integration.rs:926(left: 2,right: 1). The added Compose member name began with the primary container name, andContainerQuery.nameis a substring filter. Renamed the fixture member so it cannot match that filter.busybox:1.36, while rootless Podman reportsdocker.io/library/busybox:1.36. The adapter preserves engine metadata, so the test now checks that inspect tags equal the corresponding list tags.test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 10 filtered out; finished in 91.05s.scripts/verify fastpassed all stages after the corrections. Full integration is being rerun.Fixture corrections committed as
f522b93.Resource HTTP integration findings:
--labelvalues, which leaves the project label empty; it now passes the requiredkey=valueargument.outcomes[i].status, but the DTO serializesTargetOutcomeunderoutcomes[i].outcome, where its tagged status lives. The assertion now checksoutcome.statusand includes the returned JSON in failure output.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.94sThis 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.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.02sThe one failure was
every_grant_shrink_closes_logs_and_persistent_exec_on_both_engines: Podmanuser_disabledexec 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.51sThe direct rerun passed for Docker and Podman without source changes, consistent with a load-sensitive timing failure.
CLI/MCP integration finding:
tools/listand 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.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.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 80twice, although it requested47 123after 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.572effcadds a bounded readiness check to the real-engine CLI/MCP fixture. The directcli_and_mcp_use_the_public_http_contractrerun 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 reallogs --tail 4output for all four expected records before checking the public stream.cargo fmt --all --checkpasses; the next integration rerun is waiting for the shared release/browser job and load threshold.The full integration run on
572effcpassed the release build, HTTP behavior, Docker/Podman socket pings, engine adapters, bounded log snapshots, and TypeScript stream client. The stream regression suite reportedtest result: FAILED. 11 passed; 1 failed; 0 ignored; 0 measured; 7 filtered out; finished in 122.48s. The failure wasauthorization_loss_terminates_stalled_stdin_on_both_engines: Docker completed, but the Podman helper did not observe itssleep 60process 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 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_enginesdid not observe the Podmansleepprocess 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 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 usesfollow:falseand a 1-second maximum;read_container_logsonly addsduration_limitif the snapshot reaches its deadline, so a faster EOF may legitimately omit it. Theschema type "object" rejected nullline came from a caughtanyOfbranch 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.Committed
cb655e0 test: allow fast bounded log snapshots. The large Docker log fixture still assertsbyte_limit; it no longer requiresduration_limitwhen afollow:falsesnapshot 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 --checkpasses. The isolated CLI/MCP rerun is pending a quiet engine window.Final verification is still queued by shared-engine load. The isolated CLI/MCP rerun after
cb655e0cannot start while another worktree repeatedly runspty_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 --checkand code-graph impact pass after it.The isolated CLI/MCP rerun after
cb655e0passed the bounded log snapshot assertions, then reproduced the PTY resize race: remote output was24 80twice, missing the expected47 123. Commitf50d484fixes the fixture handshake: it waits for the remote initial size output, resizes the local PTY, and has the remote shell pollstty sizeuntil the remote geometry changes.cargo fmt --all --checkand graph impact checks pass. The final full integration gate will verify this change when the shared engine queue clears and load5 is below 8.verify fastpasses on the current head aftercb655e0andf50d484. Verbatim output:Full integration and browser gates remain queued behind active release builds and load5 > 8.
Integration attempt on
f50d484passed dependency setup, web build, release build, real HTTP behavior, Docker and rootless Podman socket checks, engine adapters, and boundedfollow:falselog snapshots. It then failed atTypeScript stream client against Docker and Podman: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.
The direct retry required by the timing-test rule passed once:
A full
scripts/verify integrationrerun on the unchangedf50d484then 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: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.
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 atf50d484; the remaining work is a clean full integration gate followed by the browser screenshot matrix when a quiet window is available.Starting independent review of step 24.
Branch:
stacks-imagesBase:
dbd6685(devat job start)Review head:
f50d484Scope: 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-imagesBase:
dbd6685(devat job start)Review head:
f50d484Scope: read-only review of compose stacks, images, volumes, UI, auth, performance and tests; requested throwaway worktree checks only.
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 onlystate.inventory.host_id()and maps otherHostIds tonot_found;resolve_resource_enginedoes the same for details. The list handlers iterate onlystate.inventory.engine(...), while the merged remote container route reads snapshots throughstate.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).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=1twice onorigin/dev(1 passedin 11.75s and 10.74s), then cherry-pickedfda1a45into the throwaway worktree and ran it once more (1 passedin 10.14s). The patch explicitly passestty: options.ttyto BollardStartExecOptions; step 24's engine diff does not changestart_exec. The reported 500 did not reproduce before or after the patch, so the evidence supportsflake; it does not show thatfda1a45fixed this instance or that step 24 caused it.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).VirtualTableinvokes the cell snippet per column (VirtualTable.svelte:210), and the route'stableRowsalready 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.review-24 complete
Verdict: request changes. Reviewed
stacks-imagesatf50d4848e4bc712df5265971cc7933571d551fb5againstdbd66850f8a5d6190e65b4bd364e075950033425. No changes were made to the branch.Full findings and linked screenshots:
/mnt/hdd/infraternal/jobs/review-24/findings.md.Blocking findings:
LocalInventoryafter step 19. A visible remote host returnsnot_found, while omittinghostsilently returns only local results.find()over the full list per cell; filter requests can overwrite newer results.The production browser gate output was:
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:
Other evidence:
scripts/verify fastpassed after mergingorigin/devatd9197837c5adb20ad0da2a6b8872ed2826a92aeein the throwaway worktree. Output: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.rsanddocs/contracts.md. They were resolved only in that worktree; it was removed after review.cargo test: 1 passed (1 suite, 14.86s). Removing the Podman Compose-label fallback failed its grouping assertion; returning an empty image list failedresources_http.rs:124(images.total > 0). Both mutations were restored.origin/dev, the TypeScript stream test passed twice (11.75s,10.74s); after cherry-pickingfda1a45, it passed once (10.14s). The 500 did not reproduce, so the patch is not proven to have fixed that incident.Starting fix-24 on branch
stacks-images, base headf50d4848e4bc712df5265971cc7933571d551fb5. I will first merge currentorigin/dev(9d4876a), then fix all findings in/mnt/hdd/infraternal/jobs/review-24/findings.mdand run the requested gates and screenshot matrix.Merge decision:
origin/devadds 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.Evidence and decision for image/volume paging: the pinned Docker-compatible APIs expose bulk image and volume list operations. Bollard's
ListImagesOptionshasall,filters,shared_size,digestsandmanifests, and its list call returnsVec<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/.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.Remote log routing is now covered at the stream boundary too. The real TLS fake-tunnel resource test sends a remote container
OPENover the client WebSocket and receivesOPEN_ERR unsupported; the focused test passes (cargo test: 1 passed, 1 filtered out (1 suite, 0.24s)). Commit:5baa4ed.Post-reboot integration finding at
85f5d78:scripts/verify integrationstopped in the real engine adapter step. Two Podman-backed tests failed during Compose grouping with Podman 5.4.2 returning:A direct
podman inspectof that exited container reproduces the same error.podman psanddocker psboth respond. The container is namedinfraternal-engine-foreign-podman-742248-1790425491847-2and labeled with test run ID742248-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.Cold production route measurement before the optimization (gzip):
/stacks162,733 B,/images153,141 B,/volumes152,950 B; the budget is 122,880 B. The loaded resource pages shared the 9,686 B generalVirtualTablechunk, and the custom filter brought in 9,489 Bselectplus 12,034 Bpopper-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.Post-edit
scripts/verify browserattempt exited before building the release binary:FAIL browser: terminal fixture Playwrightbecause 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.Ownership check and cleanup: the exited
infraternal-engine-podman-742248-1790425491847container was created at 2026-09-26 14:25 and carried this integration test run's PID/timestamp ID.integration.rs::unique_run_idbuilds that exact ID,spawn_foreign_event_churnuses it for the foreign fixture, andContainerGuardlabels fixturesinfraternal.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 andpodman psresponds.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_contractat the MCP continuous-log assertion. The tool callsread_container_logs(..., follow = false)and its description says it returns a finite snapshot;truncatedonly includesduration_limitwhen 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, andtruncatedconsistency without requiring a timeout. Fast gate now passes; integration will be rerun when the shared gate slot is clear.The post-MCP integration rerun passed release build, HTTP behavior and Docker/Podman socket checks, then failed
docker_and_podman_share_the_real_engine_contractatcrates/engine/tests/integration.rs:1255: the Podman log snapshot did not contain an untruncatedstdout-lineafter 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 id216044-1790440293344are empty. I’m treating this as unresolved until a focused diagnosis/retry determines whether it reproduces.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.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/imagesand/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.Browser cold-route evidence: the gate repeatedly reports
/stacksat 184,290 B gzip, including extra chunksDcCBQFQt.js(40,957 B),Cf4njSv0.js(12,207 B), andth4EFyoC.js(7,718 B). The standalone production capture measured/stacks,/images,/volumesat 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 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/stacksat 121,050 B gzip, within the 122,880 B limit. The following/imagescheck 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.The production browser gate now captured all three cold route sizes (before
/stacksnavigated to the next route):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.Correction to the preceding browser note: the following browser run did not keep all three pages under budget. It measured
/stacksat 121,050 B,/imagesat 120,035 B, and/volumesat 183,079 B. Thus/volumesis 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 as0b92219. Next browser run is pending the shared browser slot. No final browser 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 tworole=statuslive 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 asa8eddee. This run did not complete the browser gate, so no pass is claimed.The latest browser run (
/tmp/fix24-browser-final-pass.log) again measured/stacks121,050 B,/images120,035 B, and/volumes119,839 B. It then failed the paused restart-plan assertion: the test's broadgetByRole("dialog")also saw the stack sheet'sRestart stacktrigger, even though the plan dialog correctly omits its confirm button until a plan exists. Scoped the locator to the accessible dialog nameRestart ${stackProject}?; committed as9ad327e. The reported route-already-handled error followed the aborted assertion. The full browser gate still needs to complete.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.
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.
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.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 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 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.
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.svelteto use available container width for its layout breakpoints. Committed as08a82e8 fix(web): adapt resource filters to card width.scripts/verify fastpassed; output is recorded verbatim in the final gate report. A different worktree's browser gate is active, so I am waiting before launching integration.fix-24 complete
Branch:
stacks-imagesHead:
08a82e85ef4adc8a19ceb7efa352ecaa07ec7819The 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_hostsassertion 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.fileconfirmed all 12 PNG dimensions.scripts/verify fast(verbatim)scripts/verify integration(verbatim)scripts/verify browser(verbatim)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, mergeorigin/devat the specified head, and run the required gates subject to the load and gate-priority checks.Verification evidence on the fix-round head
08a82e85ef4adc8a19ceb7efa352ecaa07ec7819:/home/kayg/wt/verify-24-tmpat08a82e8, removing.min(MAX_RESOURCE_PAGE_SIZE)maderesource_page_options_cap_size_and_reject_invalid_or_foreign_cursorsfail (left: usize::MAX,right: 100).not_found(request_id, "host")for the visible remote resource path maderemote_resource_reads_surface_unsupported_hosts_instead_of_not_foundfail at/api/v1/images?host=…(404vs expected501). Before reaching that assertion, the same test confirmed omitted-host lists include the connected remote host withreason: "unsupported". Both temporary mutations were restored; the worktree is clean.resources.rsclamps list limits at 100 and retainslimit + 1candidates 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.
Integration attempt on the merged tree failed in
compiled CLI and MCP against real server and enginesafter 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:I am checking whether this reproduces in isolation before attributing it to the merge. No source fix has been made.
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 inexit\r\nwhile 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
devcommit0719596(Fix CLI exec stdin EOF handling). Incrates/ssh/src/session.rs::drive_pty, receivingChannelMsg::ExitStatusrecorded 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:
The failure diagnostic now includes stdout as well as stderr for future timeout evidence. Full
scripts/verify integrationstill needs a passing rerun.Browser-gate evidence after merged head
47a2890b28b3af2a3962bf1ee2a5a90d8071a0ed: cold dashboard JavaScript was 123,754 bytes gzip against the 122,880-byte cap. The built chunk containingDragDropManager/dnd-kitis 40,644 bytes gzip.Sidebar.sveltelazily importsSidebarInteractive.svelte, but currently starts that import on any sidebarpointermove; 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.The production browser run after
0f03fba028a3424ecabf45ebeccf2d04d9aa4f8dstill measured 123,786 B gzip for the initial route against the 122,880 B cap (906 B over), so narrowing prefetch to.sidebar-contentdid 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.Browser evidence on
82994a7: setup, terminal fixture, web build and release build passed, then the sidebar persistence check timed out waiting fordata-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-contentbefore asserting readiness; this exercises the intended pointer-prefetch path. The run stopped before the cold-route metric and before 390×844 resource checks.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.
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.
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 echoedfinishandexit, 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.Integration gate attempt on
f8eb4db1plus 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 Dockerfailed atcrates/server/tests/stream_regressions.rs:1589: rootless Podman did not consume pre-commit input within 5 seconds; the log also recordsstream 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.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.Browser gate on
a70825apassed 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 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/stacksfirst-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.After moving stack member-log coordination to the dynamic
stack-member-logs.tsmodule, the cold/route passed at 122,850 bytes and/stacksfell 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.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 theContainer logsheading while opening a real container detail route; the run log also records a refused curl to its local test port127.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 retry on
c3c3cfareached the resource routes after the container log checks passed. “Jump to latest” passed twice. The dashboard measured 122,871 B;/stacksmeasured 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 tokenizedSurfaceprimitive to remove its direct Card dependency, then I’ll rerun fast and browser verification.Browser retry on
e35ac25completed 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 activeport-c1browser gate clears.The first
e35ac25browser attempt was blocked before resource-route checks by the dashboard cold-route JS budget (122,887 B versus 122,880 B). I removed the tinyengineCounthelper and usedhost.engines.lengthdirectly; behavior is unchanged. The change is commitf69597b54206c45657370cfd2a2f290288d984ee.scripts/verify fastpassed in full; verbatim output is in/tmp/verify24-fast-budget-final.log. A newport-c1browser gate became active before my retry, so I’m waiting for it to finish.Browser gate on
f69597bnow clears the dashboard cold-route cap at 122,872 B. The route budget then measured/stacksat 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 resolvingSurfacethrough the@infraternal/uibarrel while its detail component also dynamically importsVirtualLogListfrom that barrel; I’m switching the route to the configured deep@ui/Surface.svelteimport so the cold list route does not pull the package-wide entry.The
@infraternal/uibarrel was bringing its package-wide import graph into the cold/stackspage. I changed the stacks route to import the same sharedSurfacecomponent through the configured@ui/Surface.sveltealias; this preserves the component and avoids coupling its cold chunk to unrelated package exports. The change is commit749188baa893ddcc9cbcbe10dd72a670d3841d08;scripts/verify fastpassed in full (/tmp/verify24-fast-deep-surface.log). Browser retry is next, subject to gate priority.The deep
Surfaceimport removed the package-wide/stacksdependency, 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: fixed0inbox 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.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.
The dashboard cleanup passed
scripts/verify fastin full (/tmp/verify24-fast-dashboard-final.log) and is committed asd5d2f1a667d471c8bdffc721fea0ad6c18f90936. It removes the fixed inbox count, empty telemetry fields and API-not-yet-built activity card. I’m rerunning browser against this head now, withport-ab/other browser gate priority respected.After removing the unfinished dashboard fields, the root cold-route metric passed at 122,585 B, but
/stackswas still 123,296 B (416 B over).StackSummaryalready contains the table cell and sort fields, so the route was redundantly copying each bounded page row just to addhost/runningaliases. I replaced that per-row copy with direct row reuse and changed the sortable column IDs tohost_id/running_count; this reduces allocations and generated route code without changing displayed data. Targeted fast/browser gates pending.The bounded-row reuse update passes
scripts/verify fastin 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 stack table now sorts directly by
host_id,container_countandrunning_count, and uses the typedStackSummarycell values withoutString/Numberconversion wrappers. This removes redundant row processing and some route code without changing the displayed values.scripts/verify fastpassed in full (/tmp/verify24-fast-stack-fields.log); the browser route budget and mobile checks are pending.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/stacksbudget 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 fresh browser retry passed both “Jump to latest” checks, sidebar persistence and container log control. Dashboard cold JS measured 122,586 B;
/stacksmeasured 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 asa7acb98d9e0f08968b4935ad734fb1782a0b4c78; it passes fullscripts/verify fast(/tmp/verify24-fast-stack-fields.log). I’m making one more measured route-size reduction before another browser run.The latest browser run on
a7acb98passed the container log flow and measured/stacksat 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 tokenizedSurface, 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.Correction from generated chunk inspection: the 463 B
DgwHNVrZ.jsroute chunk wasSurface.svelte, not the Skeleton component. Removing the stack loading Skeleton saved 7 B because that chunk remained. I replaced the three stack-pageSurfaceinstances 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.The cold route now uses one token-backed native surface class for its three static wrappers, avoiding the 463 B shared
Surface.sveltecomponent chunk while retaining the same spacing, border, radius and surface colors. The change is committed as7dc5ac158fdb8be97eeecb28d0994a062b4e69d9. Fullscripts/verify fastpassed (/tmp/verify24-fast-native-surface.log). Browser verification is next, subject to gate priority.The production browser run on
7dc5ac1now passes/stacksat 122,697 B under the 122,880 B cap. It stops at/imagesfirst load, 124,726 B (1,846 B over), before/volumesand the 390×844 clipping checks. Unlike stacks, Images still statically imports and rendersResourceFilterBar; I’m deferring those controls behind the same accessible filter disclosure, while keeping filter-race test coverage by opening it before typing.Verification update (2026-09-27):
scripts/verify fastpasses on the current UI source.scripts/verify integrationpassed once on the current API/backend and resource-surface source. Two subsequent runs failed only incompiled 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.port-abcurrently owns the browser gate slot; this job is waiting and will not overlap it.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-abcurrently owns the browser slot; this job is waiting before its next browser run.Browser detail root cause and fix:
memberPresent=true, while the sheet remainedloading:trueafter 15 seconds.StackDetailSheetstartedloadStackDetailinside$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.untrack, matching existing use inVirtualLogList.svelteandVirtualTable.svelte.port-abcurrently owns the browser gate slot.