Skip to content

review: capture-intent race, preview 404, silkd session identity, compat removals, docs, cuts - #457

Merged
CMGS merged 19 commits into
mainfrom
review/post-456
Nov 5, 2026
Merged

CMGS merged 19 commits into
mainfrom
review/post-456

Conversation

@CMGS

@CMGS CMGS commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Whole-repo static audit of main at e897bbe: every source file re-read in full under the style, comments, modern-Go, correctness, simplify/LOC, perf and ownership lenses, all 13 declared flows re-read in full (not only changed windows) under correctness, perf and judge, every doc checked statement by statement against the code. No hardware or E2E run in this round.

Bugs fixed (each with a test that fails on the old code)

Commit Bug Reproduction
pool: settle a failed capture's intent before a fork or checkpoint captures the claim A checkpoint, fork or template request waiting on the claim's transition could win it before the background settle a failed hibernate leaves. It then saved the paused guest (snapshot save resumes it), so a later adoption of the listed hibernate snapshot restored memory older than the disk. Now exportSource and forkSource settle first, as hibernate and wake already do; zero cost when no intent is set. TestACaptureSettlesTheIntentAFailedHibernateLeft (fork and checkpoint): old code saves the guest and leaves the intent unadopted.
server: answer a preview of a released sandbox 404 The preview proxy mapped three pool errors by hand and answered every other one 502 with an Error log, so a tab polling a released sandbox logged an error per request. It now uses the shared pool error map. TestPreviewAnswers404ForAReleasedSandbox: old code answers 502.
silkd: drop a dead session command's session only if its id still names that session A session command whose shell died removed the session by id. When a setsid child kept the pipe open past session_rm, the late EOF killed a session recreated under the same id. a_dead_session_command_leaves_a_recreated_session_alone: old code answers no such session for the recreated one.
silkd: count a frame's newline against the cap render_frame allowed a body of exactly MAX_FRAME bytes; the Go scanner and the Python reader count the newline, so such a frame killed the client stream instead of producing an error frame. render_frame_counts_the_newline_against_the_cap.
usage: warn when the last landed line rotated out of the journal The shipper then restarts at the oldest kept line and the lines in between are lost without a log, against the API doc's "never loses a window silently". Log line only; no cheap test.
ci: run the moves registry's PostgreSQL tests sandboxd/moves/pg (#445) was never run in CI with a database. Run locally against PostgreSQL 17: TestRegistryContract, TestCellsKeepTheirOwnRecords, TestADownDatabaseIsUnavailable pass, no SKIP.

Removed on the owner's ruling (no backward compatibility)

  • The info probe kept for silkd before proto 2. Every silkd since sdk/go: keep a handle's relay connection across calls #198 serves RPCs back to back, so the Go and Python SDKs send the request on their first dial and park the connection without asking; ServeConnOnce and the proto parameter leave silkdtest. The reader router no longer holds a lone info frame for a pipelined call: info on a sleeping claim always wakes it, since a reader would answer with its own processes and uptime (TestSleepingInfoAfterAReadWakes fails on the old routing). Guests captured before sdk/go: keep a handle's relay connection across calls #198 now get one RPC per connection and the SDK reports their close as an error.
  • volumes_attach_only, WithVolumesAttachOnly and the Python mount=False. Per-entry attach_only makes the same claim; nothing outside this repository sends the claim-wide flag.
  • Image build cleanup. The silkd build no longer downloads rustfmt, clippy and the toolchain file's x86_64 target (as boot/init since build: drop the boot image's duplicate kernel mirror and its unused rustfmt/clippy download #456), the Dockerfile and release workflow no longer strip what the release profile strips, boot/base/android take their version args only from the Makefile and CI (a bare build fails instead of building a stale pin), and PATCHES becomes PATCHES.md, which the image hash already skips. Cost: merging rebuilds the silkd carrier and the os-images; the boot image changes only an ARG default the builds always override.

Cleanups

  • docs: API 403 list (reload, tenants, move), legacy volume-load sentence, relay connections per handle (8, not 1), benchmark attribution (the pre-config: keep an explicit warm 0 in the config file #185 warm: 0 default, not the watermark), root-only move endpoints, GET /v1/info sample, preview token keys, volume claims refuse WithArchiveOnExpire too, reader lane, egress_bytes for tunnels cut by a stop; godocs for SecretSpec, VersionWarning, byID, probeReady, SweepStaging, archives.Put, PoolSpec, silkd Ready, guestExecPATH, rpcbench, the LangChain toolkit and boot init.
  • comments: restating, stale and narrative comments deleted, two-line unexported docs folded to one.
  • layout and naming: utility funcs below type blocks (SDK keep-alive, port, silkdtest, e2e harness and drivers, MCP tools with the server methods in server.go), MoveOut ahead of Sandbox; logger tags name the real function (pool.lift, server.router.fail, usage.shipper.*); tapOf names the inspect call.
  • cuts: single-caller helpers folded (stampIdentity, confirmGone, roundBytes/roundTimeout, fenceGuest, pullFrom), a guard that cannot fail in Prebind, a JSON tag nothing encodes, the PoolResult alias, buffered exec decodes into one scratch buffer, GuestFile appends without a copy, volume patch slices omit when empty; silkd drops the detached stdin flag, an #[inline] hint and lsp name filters a guest-root caller could bypass anyway; the Python SDK drops an unreachable status check and names its chunk sizes as Go and Rust do.
  • tests: assertions that could not fail, a b.Fatal in a RunParallel worker, a silent wait timeout, a literal instead of cacheDir, a shallow-copy view helper replaced by the cloning one, dead fixture fields.
  • ci: the silkd carrier no longer rebuilds on protocol/** (its build context is silkd/), and a manual silkd run can skip the cache.

Numbers

Production -186 net (excluding the 18 test lines inside silkd/src/proto.rs), tests -137 net, docs +31 -36, CI and image files +15 -19. Comment lines in Go and Rust: +30 -51.

Coverage

Flow / area How covered this round
13 flows (claim-lifecycle, warm-pool, failover, capture-store, live-volumes, api-relay, silkd-protocol, settle-capture, hibernate-wake, live-move, cocoon-cli, restart-reconcile, egress-metering) Read in full, every member file, window tables of 24-40 rows each; one BUG row (the capture-intent race above), fixed
Background settle vs ReaderSafe (#455 fix) Proved by the lock argument (every intent writer holds the transition, every successful capture clears its intent before releasing it); the regression test fails on the pre-fix ordering
Every Go, Rust and Python file, infra, docs Read in full
Crash windows on hardware Not covered this round (static only)

Gates

  • GOWORK=off make go-lint (5 modules x linux/darwin): 10 x 0 issues.
  • asl -forwarder=false ./... on both GOOS, all 5 modules: clean
  • make lint + make test (Rust) on macOS and in rust:1 linux/arm64: green
  • ruff format --check, ruff check, pytest under Python 3.12: sdk/python 278, openai 10, langchain 15 passed
  • GOWORK=off make go-test (race), all 5 modules: green at ef52a34 (the wire-parity test caught a tag change, reverted in that commit)
  • moves/pg against PostgreSQL 17 locally: 3 tests pass, no SKIP
  • The last three commits (probe removal, attach-only flag removal, image cleanup): gates and a second outside review re-run before merge

Kept without code

  • skipIdle, finalPause forwarders: the names carry the predicate and the estimate.
  • Engine Remove answering "vm not found" as an error: the test pins it; returning nil changes the engine contract.
  • Volume revalidation in resolveVolumes and VolumeSpec.directIO: guards at an exported package boundary.
  • readChunk vs io.ReadFull: not equivalent when the last read returns data with EOF.
  • A sandboxd kill between the claims commit and the claim usage event: the client never received that claim.
  • A reload turning reader off: one extra wait per sleeping claim, until its next wake.
  • Background settle reads intents under m.mu, settlePendingSnap wrapper, template key filter, egress empty-Layer fallback: earlier rulings stand.
  • Unmeasured perf leads (S3 label GETs per listing, peer probe on a shared store, archived-wake store delete inline, live-move double SHA-256, silkd per-frame decode buffer, fs_replace regex on the runtime, egress usage append before the response, VM-event scan under the manager lock): need a measurement before code.

CMGS added 14 commits November 5, 2026 14:05
…ptures the claim

A capture waiting on the transition can win it before the failed capture's background
settle; it then saved the paused guest, so a later adoption of the hibernate snapshot
restored memory older than the disk.
… pool error map

The preview proxy mapped three pool errors by hand and answered every other one 502
with an Error log, so a tab polling a released sandbox logged an error per request.
…es that session

A removed session's command can see its shell die long after the removal when a
setsid child keeps the pipe open; removing by id then killed a session recreated
under the same id.
A body of exactly MAX_FRAME bytes passed render_frame but overflowed the Go scanner
and the Python reader, which count the newline, killing the stream instead of
answering an error frame.
The shipper then lands from the oldest kept line and the lines in between are lost;
the API doc promises a collector never loses a window silently.
…tion, use stdlib names and imports in tests

Moves only: utility funcs below type blocks (sdk keepalive, port, silkdtest, e2e
harness, guestserver, rpcbench, mcp tools with the server methods in server.go),
MoveOut ahead of Sandbox. Logger tags pool.lift, server.router.fail,
usage.shipper.drain/read; tapOf names the inspect call.
… and the PoolResult alias

stampIdentity, confirmGone, roundBytes/roundTimeout, fenceGuest and pullFrom fold into
their one caller; Prebind's guarded check cannot fail once a pool policy exists;
TemplateDigest is never encoded; DefaultMoveDowntime has no reader outside pool.
Buffered exec decodes into one scratch buffer, GuestFile appends without a copy, and
the volume patch slices omit when empty.
…and the lsp name filters a guest root caller can bypass anyway
…s out, and reuse the shared helpers

refillkick's elapsed check is always zero under synctest; a RunParallel worker
reports with Error, not Fatal; waitFile fails the test instead of returning; the
fetch-cache check names cacheDir; tuneView's shallow copy gives way to editView;
dead fields, parameters and a mutex that guards nothing go.
@CMGS CMGS changed the title review: whole-repo static audit after #456 (capture-intent race, preview 404, silkd session identity, docs, cuts) review: capture-intent race, preview 404, silkd session identity, docs, cuts Nov 5, 2026
CMGS added 4 commits November 5, 2026 14:31
…eping claim wakes it

Every silkd since #198 serves RPCs back to back, so the SDKs send the request on
their first dial and park the connection without asking. The reader router no
longer holds a lone info frame for a pipelined call: info always wakes the claim,
since a reader would answer with its own processes and uptime.
…nly and the Python mount=False

Per-entry attach_only gives the same claim; nothing outside this repo sends the
legacy flag.
…cargo already does, and the image version defaults

silkd builds with the pinned toolchain minus rustfmt, clippy and the toolchain
file's x86_64 target, as boot/init does since #456; the release profile strips, so
the Dockerfile and release workflow no longer strip again. boot, base and android
take their version args from the Makefile and CI only, so a bare build fails loudly
instead of building from a stale pin. PATCHES becomes PATCHES.md, which the image
hash already skips.
@CMGS CMGS changed the title review: capture-intent race, preview 404, silkd session identity, docs, cuts review: whole-repo static audit after #456 (capture-intent race, preview 404, silkd session identity, compat removals, docs, cuts) Nov 5, 2026
@CMGS CMGS changed the title review: whole-repo static audit after #456 (capture-intent race, preview 404, silkd session identity, compat removals, docs, cuts) review: capture-intent race, preview 404, silkd session identity, compat removals, docs, cuts Nov 5, 2026
…onger did it

legacySandbox kept these fakes on a dial per call; without it a second call could
take the parked connection the fake is about to close.
@CMGS
CMGS merged commit 03fc25d into main Nov 5, 2026
4 checks passed
@CMGS
CMGS deleted the review/post-456 branch November 5, 2026 06:56
CMGS added a commit that referenced this pull request Nov 5, 2026
…ion and pty, compat cuts (#469)

* engine: read cocoon daemon's opening snapshot of a node past 3,000 VMs

cocoon sends every VM record in the sync event's one data line, about 300 bytes a
record, so the 1 MiB line cap broke the stream at every connect on a full node
and liveness fell back to polling vm list for good.

* pool: record a release's usage event once the release commits, not after its teardown

The claim is durably gone at the commit; a restart or crash during a slow teardown
(volume quiesce, vm rm) left a claim event with no release, which the folding rule
bills up to the claim's deadline.

* pool: load a forked claim's tenant for operator forks too, so its cap applies

The quota check reads the tenant cache only; an operator fork skipped the lookup
that fills it, so after a cache flush or restart the tenant's max_claims read as
none and the fork overshot it. A removed tenant still does not stop the operator.

* pool: answer an invalid PUT /v1/pools spec as invalid config naming its pool, not as a bad fork count

* server: count a checkpoint claim handed to a peer in sandboxd_claim_redirects_total

* test: fix the pool benchmarks the claim index and running count broke, and run every benchmark once in go-test

Four benchmarks seeded claims around addClaimLocked or without a store row and
failed the cleanup invariants; no CI step ran -bench, so nobody saw it.

* cut: drop the info frame's proto field and the keep-alive constant nothing reads since the SDKs stopped probing

* cut: resolve a claim's egress layers from its recorded layer alone, dropping the live-pool fallback for records older than the field

Every claim records its layer at commit since #216 and adopt and move carry it, so
the fallback served only pre-#216 records; the Pooled callback threaded through
outbound existed for it alone.

* pool: keep a claim's last replica attempt across ticks it cannot replicate

replicateOnce dropped the attempt time whenever a claim was hibernated, archiving
or settling, so a claim that idles and wakes inside every interval never came due,
had no registry row, and was lost with its node.

* cut: decode request bodies, config and MCP tool arguments with json/v2's exact names, dropping the v1 case-insensitive matching the v2 move kept

A case-variant key is now an unknown member: config and strict bodies reject it,
other bodies ignore it.

* silkd: end a session command when its shell exits, not only on the shell's stdout EOF

A background job started in the session holds the stdout pipe past the shell, so
an exit, a set -e failure or an OOM kill of the shell left the command hung, its
session's io lock held and the session never reaped.

* silkd: open the pty master and slave close-on-exec so the shell and its jobs do not inherit them

libc openpty leaves both fds inheritable; every pty shell held a spare master, so a
job outliving the shell never saw its terminal hang up.

* docs: match the docs and godocs to the code after #457 and #459-#467

Drops the Python mount row and the pre-proto-2 sentences #457 left, the images
trigger path, and the old-template digest note; corrects the data_dir entries,
the drain error, the shared-store probe, idle upstream conns, env snapshot reads,
lineage markers, secret valuation, exit codes, Ready, the tenant_claims help,
Metas, the checkpoint fallback and the Python lookup and keep_alive docs.

* review: delete restating comments and fold over-long ones to the one fact the code cannot carry

* cut: inline single-caller helpers in the reader router, SDK and silkdtest, drop an unreachable preview guard and lint exclusions for directories that do not exist

* test: settle refills before counting clones, fail fast on a claims load error, scope table contexts to their subtest, cover every decoder with a fixture, and drop dead or always-true checks

The fixture test now ranges the decoder tables instead of a hand list a new verb
could miss; the watcher garbage test asserts the error it promises; smoke and
bench tools type their 409 check and stop racing the handshake reassignment.

* pool: refuse a move while a failed save capture is still settling

moveBlocker refused an unsettled hibernate intent but not an unsettled save; a move
that won the transition over the save's settle carried SaveSnap to the target and
left a listed fork snapshot on the source that the startup sweep keeps.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant