Conversation
|
@zozo123 thanks for the PR! I think this is exciting in the sense that the PR does show how the harness can be decoupled from the execution environment. I'm not familiar with Box but checking the wiring with mecatl I think this is missing a builder composition (something like Thanks again for the contribution! Feel free to jump on Discord or GH discussions if you want to discuss anything! |
|
@jhrozek to answer your question on #1604: this is the current PR. I've closed #1604; #1598 and #1603 were already closed. New since your review (6a22253): the vendor rebranded from Ascii Box to boat.dev, so the adapter now lives at On the live API, the opt-in contract smoke Your builder-composition point and lining this up with the k8s environment in #1614 ( 🤖 Generated with Claude Code |
The vendor formerly known as Ascii Box now ships as boat.dev, and the old ascii.dev/api/box/v1 endpoint sunsets 2026-10-31. Rename the package internal/adapter/boxenv -> internal/adapter/boatenv and move every call onto the current API, verified against the live service: - base URL https://boat.dev/api/v1 and the /sandboxes resource family (create, get, resume, stop, commands) replacing /boxes - response envelope key "sandbox" replacing "box" - EnvironmentKind "boat", revision prefix boat-v1-, workspace root boat:<sandbox-id>:<workdir>, live-smoke credential BOAT_API_KEY - Config.BoxType -> Config.MachineType (the API's machine-size field) Two behavioural fixes found while retargeting: - ensureReady no longer lets waitReady issue a second resume for a sandbox it just resumed; boat.dev reports the transitional states provisioning/provisioned/resuming/archiving, which are now polled through rather than acted on. - the opt-in live smoke now proves the durability claim end to end: archive the provisional sandbox, reattach the persisted ref, and read the file back, plus stale-CAS and stale-revision rejection. Also clears two lint findings the package carried (unchecked resp.Body.Close, unused versionOf). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…nce tables Answers the review ask for a builder composition that makes the adapter testable end to end, and lines it up with stacklok#1614. Composition (internal/adapter/boatenv/composition_test.go): drive scripted sessions through the real app.Build with the Boat provider as the deployment PlacementProvider. Doing so surfaced three gaps that library tests could not see, all fixed here: - Startup preflight provisioned a sandbox. configuredPlacementBinder validated the default placement by binding it, which for Boat creates and archives a billable sandbox on every daemon start. Add the side-effect-free server.PlacementValidator seam, with the same name, signature and call site as stacklok#1614 so the two merge cleanly, and implement it for Boat as GET /me. Providers without the seam keep the legacy bind-to-validate path (both pinned in placement_validator_test.go). - File tools were denied by authority: the workspace could not derive an authority resource identity. Implement tool.AuthorityResourceResolver with a guest round trip that resolves symlinks under the same confinement as file access, so policy and access name the same file. - Shell needs a local workspace and shell on main even when the placement supplies the runner. stacklok#1614's RemoteExecution posture removes that gate; until it lands, the Shell composition test enables it with a throwaway host workspace and proves nothing runs there. Conformance: run engine/adapter/fsconformance Run and RunNamespace against the Boat workspace, offline over the fake API and live over one sandbox with a directory per case. It caught a contract bug: ReplaceFile with a zero FileVersion on a missing path reported a version mismatch instead of fs.ErrNotExist. A zero version still goes to the guest, which now reports a missing file before a stale version. Live, against boat.dev: TestLiveBoat (lifecycle, CAS, archive and exact reattach), TestLiveBoatConformance (30/30) and TestLiveBoatComposition (app.Build, one sandbox, Write/Edit/Grep/Shell/Read) all pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6a22253 to
d4c006b
Compare
|
@jhrozek following up on your two points (d4c006b): Builder composition. The adapter is now composed through the real
Coordinating with #1614.
The workspace now also passes the shared All three live suites pass against boat.dev; the numbers are in the updated description. 🤖 Generated with Claude Code |
… boat.dev A six-lens review of this PR, with three independent skeptics per finding, confirmed defects the earlier tests could not see. Each is fixed here and pinned by a regression test; the live limits they depend on were measured against boat.dev before changing code. Commands - The guest time limit now follows the caller's deadline (clamped to the API's 1-600s) instead of a hard 60s cap, and the client has no client-wide HTTP timeout. A command over 600s reports Boat's ceiling rather than the caller's value. - A signal-killed process (live: exitCode null, signal SIGKILL) reports exit -1 and names the signal instead of passing as exit 0. - API errors carry the server's code and message; redirects are refused so the bearer credential is never replayed; responses have a size bound with an explicit error; truncated stdout is flagged. Files - Content no longer travels on the command line (live: commands over ~128 KiB fail with E2BIG). Large requests are staged through the files API (5 MiB per call, chunked); large reads come back as a guest snapshot fetched in ranges and are checked against their version hash. - The helper runs as python3 -I, so workspace files cannot shadow its imports, and it now lives in helper.py (embedded). - Symlinks follow the osfs contract: Remove and Rename act on the link, ReadDir uses lstat and reports ModeSymlink, a dangling link no longer fails a listing, and writes through an escaping link report ErrPathEscape. New files are published atomically. - Glob runs doublestar with osfs's exact options over a guest listing that never follows symlinks, so brace patterns, a leading "/" and "dir/**" behave as on osfs. Grep compiles with Go's RE2, translates the parsed pattern into an equivalent Python pattern (explicit ranges, no reliance on Python's \w, [[:space:]] or (?i)), splits lines on "\n", skips binary files, and applies osfs's budgets and 201-match cap. Both are checked against an osfs workspace on the same directory. Lifecycle - Reattach costs one GET; readiness (resume, workdir creation) happens on the first operation and re-arms the TTL. A non-default Workdir is now created (live: a missing cwd is rejected). - Close schedules archival after a 5-minute grace instead of archiving at once, so the run that follows CreateSession finds the sandbox warm. The live app.Build composition went from 46.8s to 4.2s. - A 409 from a command or a concurrent resume is retried once after readiness; the command never ran twice. Server seam: a validator's non-sentinel error keeps its cause and is classified as ErrPlacementUnavailable. Live tests now build under the repo's e2e tag, use dedicated workdirs, and register sandbox cleanup the moment a sandbox exists. The cloud-native inventory gains row 76 for the provider's client, handles and guest staging. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Update (c7f3a0a): before asking for review, I ran an adversarial review of this PR and fixed what it found. Each fix has a regression test, and the tests were mutation-checked.
Live against boat.dev: CI: pull-request workflows on this PR are still waiting for approval because it comes from a fork. The same workflows ran on my fork at this exact commit: CI run, with 29 jobs passing and 1 skipped (race partitions, lint, analysis, docs, domain model, api-compat and vuln all green). Deslop also passed. Details are in the updated description. 🤖 Generated with Claude Code |
JAORMX
left a comment
There was a problem hiding this comment.
Hey! Thanks a lot for pushing this. I'm working on refining the definitions for execution environments. I'll get to this tomorrow and hopefully it'll be in better shape then. Super excited to see this!
Summary
Adds a Boat (boat.dev) remote execution-environment provider on top of mecatl's existing placement / durable
EnvironmentRefseam, ininternal/adapter/boatenv.noEnv: trueon every create and resumeWorkspaceandCommandRunnerto the same sandbox namespace and exposes the server affinity proofWorkspace+WorkspaceNamespacecontract and passes both sharedfsconformancetables (30/30, offline and live)tool.AuthorityResourceResolver, so authority-bound sessions can use the file toolsserver.PlacementValidator, so daemon startup never provisions a billable sandboxEnvironmentRef.Revision; the API credential never enters refs, logs, metadata, or sourceNaming
The vendor formerly known as Ascii Box now ships as boat.dev. This PR targets the current API (
https://boat.dev/api/v1, the/sandboxesresource family); the oldascii.dev/api/box/v1endpoint sunsets 2026-10-31.EnvironmentKindis"boat".How it matches the sibling backends
The adapter is held to osfs's behavior, not just the interface:
doublestar.GlobWalkwith osfs's exact options (WithNoFollow, symlink leaves dropped, leading/and./stripped). It walks a guest listing that never follows symlinks and is fetched in one round trip, bounded to the pattern's depth.regexp(RE2, the syntax the tool advertises). It translates the parsed pattern into an equivalent Python pattern, with every class and case fold expanded to explicit ranges, and runs it next to the files. Lines split on\n, binary files are skipped, and osfs's budgets and 201-match cap apply.Remove/Renameact on the link itself,ReadDiruses lstat and reportsfs.ModeSymlink, and a write through an escaping link returnsErrPathEscape.FileVersionis never a wildcard. New files are published atomically.These are tested against an osfs workspace on the same directory, so osfs itself is the oracle.
Composition, and coordination with #1614
The adapter is composed through the real
app.Buildwithapp.Config.PlacementProviderset to the Boat provider, and driven by scripted sessions (offline and live). That surfaced three composition gaps:server.PlacementValidatorwith the same name, signature and call site as feat: draft native Kubernetes execution provider #1614, so the two merge cleanly whichever lands first. Boat implements it as oneGET /me. A non-sentinel validator error keeps its cause and is classified asErrPlacementUnavailable.AuthorityResourceResolver(above), resolved in the guest under the same confinement as file access.RemoteExecutionposture (runner != nil || remote) removes that gate, and this PR does not duplicate it. Until feat: draft native Kubernetes execution provider #1614 lands, the Shell composition test switches Shell on with a throwaway host workspace and asserts nothing is written there.This PR adds no operator flag or config grammar while #580 and #1614 settle that surface.
Lifecycle and limits (measured against boat.dev)
E2BIGstdoutTruncatedsettimeoutSecondsruns 1–600; a 75s command is honoredexitCode: nullplus asignalcwdis rejectedWorkdiris created on first use.PATCH ttlSecondsre-arms archival relative to nowCloseschedules archival after a 5-minute grace instead of archiving at once, so the run that followsCreateSessionfinds the sandbox warm. Readiness re-arms the full TTL.Reattachcosts oneGET. The resume, if needed, happens on the first operation. A 409 from a command, or from a concurrent resume, is retried once after readiness, and the command never runs twice. Redirects are refused, so the bearer credential is never replayed. The helper runs aspython3 -I, so workspace files cannot shadow its imports.Tests
The offline fake enforces the live limits above (argv size, files-API size, missing
cwd, stream caps, signals, timeouts, 409). An adapter that only works against a lenient fake fails here first. Coverage:fsconformancetablesapp.BuildcompositionPlacementValidatorseamThe regression tests were mutation-checked: reintroducing any of four original defects fails them.
go testpasses for./internal/adapter/boatenv/,./internal/adapter/server/...,./internal/app/...,./cmd/mecated/,./cmd/mecak8s/and./docs/lint.-racepasses on the changed packages, the pinned golangci-lint v2.13.1 reports 0 issues, andmatlatl check --strictis clean. The cloud-native inventory (ADR 0027, List 1) gains row 76 for the provider's client, handles and guest staging.Live verification
Opt-in, under the repo's
e2ebuild tag and driven only byBOAT_API_KEY:BOAT_API_KEY=... go test -tags e2e ./internal/adapter/boatenv/ -run TestLive -v -timeout 45mRun against boat.dev on 2026-09-23:
TestLiveBoatRemoveon a symlink, a brace glob, a POSIX-class grep, and stale-revision rejectionTestLiveBoatConformanceTestLiveBoatCompositionapp.Build:GET /mepreflight, exactly one sandbox, authority-checked Write/Edit/Grep/Read plus Shell, nothing on the hostObserved lifecycle:
provisioning -> idle -> archiving -> archived -> provisioned -> ready.🤖 Generated with Claude Code