532 lines
34 KiB
Markdown
532 lines
34 KiB
Markdown
# Sandbox work folders
|
||
|
||
Shared folders can contain saved files from several sandboxes. The cached-file inspector keeps earlier run failures visible with a **View failed run** link, separately from the last successful save time and direct file-operation errors.
|
||
|
||
Finalization runs once per execution, including its file flush and session-state
|
||
publication. Repeated error cleanup keeps the original failure visible. A later
|
||
authorized run recovers unsaved edits from the retained sandbox before loading
|
||
incoming shared files; it does not rewrite the failed run as successful.
|
||
|
||
Reusable sandbox resume resolves provider configuration from the recorded lease,
|
||
as workspace operations and cleanup do. This preserves provider-selected defaults
|
||
such as Daytona's region when the environment leaves them unspecified. Existing
|
||
configuration and identity checks still reject incompatible reuse; stopping and
|
||
resuming a compatible lease must reopen the same account-scoped provider handle.
|
||
|
||
The deployed acceptance entry point is `pnpm test:e2e:work-folders:deployed`.
|
||
Set `PAPERCLIP_DEPLOYED_STACK_MANIFEST` to a JSON manifest matching
|
||
`tests/runner-e2e/deployed-stack.ts`, `PAPERCLIP_DEPLOYED_STACK_AUTH` to a private
|
||
0600 JSON file containing `baseURL` and a normally authorized `boardApiToken`,
|
||
and `PAPERCLIP_DEPLOYED_STACK_EVIDENCE` to an absolute output directory.
|
||
The token must belong to the manifest's `userId` and have company access. The
|
||
harness verifies that identity before file operations or paid agent execution.
|
||
The harness never launches a local server. It checks the deployed commit and
|
||
adapter inventory, exercises scoped file APIs, and starts real sandbox tasks
|
||
for every configured profile, including cold/warm Git state and real timed saves.
|
||
Cold and warm checks verify actual file bytes in all four scopes, including
|
||
nested empty executable files. Disposable Git commits use an explicit author
|
||
and committer environment because sandbox launches clear inherited Git identity.
|
||
Engine coverage is derived from each live agent's configuration. A warm pass
|
||
requires the same host-recorded physical sandbox identity and a surviving cache
|
||
marker; restoring durable files into a replacement is tested separately.
|
||
The maintainer-approved matrix is Codex, Claude, OpenCode, and Pi in both runner
|
||
generations (11 CLI/ACP profiles). Only Cursor, Gemini, Grok, and Kimi are deferred
|
||
for this campaign. Missing required profiles fail the inventory gate.
|
||
Credentials are not recorded in Playwright reports. These API checks supplement
|
||
the required browser walkthrough, two real 180-second intervals, and recovery
|
||
scenarios; passing them alone is not staging acceptance.
|
||
|
||
The separate three-turn Daytona runner qualification writes into
|
||
`PAPERCLIP_TASK_DIR` when the host supplies scoped folders. After each turn it
|
||
checks that run's final save and reads the exact bytes through the task file API.
|
||
It still checks the same sandbox, provider session, and ordered turn markers.
|
||
Runs without a scoped manifest retain the existing host-workspace assertions.
|
||
The test does not treat a missing host mirror as proof that a scoped file was lost.
|
||
|
||
The staging matrix covers legacy Codex and Claude with both CLI and ACP,
|
||
legacy OpenCode and Pi, and native Codex, OpenCode, and ACPX Claude/Codex/Pi.
|
||
Cursor, Gemini, Grok, and Kimi are excluded from this acceptance campaign by
|
||
explicit user instruction. Other required profiles must not be silently skipped.
|
||
|
||
Native Pi uses `pi-acp@0.0.33` with the official Linux x64 Pi `0.84.2`
|
||
standalone executable. The image build verifies the archive and executable
|
||
SHA-256, then starts an ACP session through the runner's descriptor-based
|
||
launcher. It does not make a model request. Live staging must still verify the
|
||
qualified OpenRouter model, work folders, saves, and recovery. The same image
|
||
exposes this Pi executable to the legacy adapter. Provider-pack shims resolve
|
||
links before locating their runtime so task-local launch paths remain valid.
|
||
Because this pinned Pi ACP adapter does not forward MCP tools, the native runner
|
||
stages a private Pi extension that reads the authenticated run-owned tool catalog.
|
||
It preserves tool schemas, idempotent call IDs, cancellation, and the bridge's
|
||
private-tool boundary. The bridge credential is injected at launch and excluded
|
||
from persisted environment records; project extensions remain untrusted.
|
||
|
||
## Existing tasks and upgrade compatibility
|
||
|
||
Tasks that have already completed a sandbox run without work-folder persistence
|
||
keep their original workspace, adapter file-sync/restore behavior, and provider
|
||
session directories. Upgrading does not move, clean, or replace those files. This
|
||
compatibility mode persists across turns and sandbox expiry; it does not claim
|
||
the new scoped-folder or repository-checkpoint durability guarantee for old tasks.
|
||
New tasks enter the scoped lifecycle below. Automatic migration of an old task's
|
||
working tree into scoped folders is not performed.
|
||
|
||
Version-1 reusable leases obtain their missing task and responsible-user identity
|
||
from company-scoped host run records. Reuse still requires matching agent, task,
|
||
user, environment, workspace, provider, and configuration fingerprint. Missing or
|
||
conflicting identity records, configuration drift, or a failed resume retain the
|
||
old sandbox and report a recovery error instead of destroying its only copy.
|
||
The provider cannot opt a new task into this compatibility mode.
|
||
|
||
Some older releases retained a sandbox without recording a workspace binding on
|
||
the task. When that binding and any explicit workspace preference are absent,
|
||
startup recovers the retained workspace from the matching task, project, agent,
|
||
responsible user, and sandbox environment. Local execution, explicit workspace
|
||
choices, and tasks that have entered scoped persistence do not use this fallback.
|
||
The normal workspace freshness and provider identity checks still apply.
|
||
After a validated legacy resume, both runner generations adopt the existing
|
||
sandbox working copy without uploading a replacement Git directory or host
|
||
overlay. This preserves its index and repository-local state. The legacy
|
||
outbound merge still saves working files to the host. A missing native sync
|
||
stamp on a pre-change lease does not make the host copy authoritative.
|
||
|
||
When an older sandbox image lacks a required runner capability, startup stages
|
||
the server-resolved runner artifact, including the vendored binary in packaged
|
||
server builds. The uploaded artifact and the controller's runner identity use
|
||
the same file. Replacement is atomic and preserves the previous launcher if
|
||
staging fails; it does not reset the task's working files or provider session.
|
||
|
||
A follow-up task run waits up to 30 seconds for a terminal predecessor to release
|
||
its reusable sandbox. While that handoff is pending, startup cannot allocate a
|
||
second workspace or resume the sandbox concurrently. An active predecessor or
|
||
an incomplete release reports a retryable resume error and preserves the lease.
|
||
The next run claims the reusable lease in Postgres before resuming the provider;
|
||
competing server processes cannot both resume the same released sandbox.
|
||
An incomplete resume keeps a provisional lease marker until provider verification
|
||
succeeds. Failed-run cleanup retains that exact lease without stopping or deleting
|
||
its sandbox, so a retry cannot silently create a replacement. Daytona refreshes
|
||
the live sandbox state on explicit resume, including externally stopped resources
|
||
whose cached handles still say running. Terminal sandbox release claims ownership
|
||
in Postgres before provider calls; duplicate completion paths and an old run's
|
||
stale lease snapshot cannot stop a newer owner. Native runs persist their selected
|
||
resource disposition independently of workspace copy-back, so recovery retains a
|
||
successful warm sandbox consistently.
|
||
|
||
An uncertain provider stop leaves a durable release claim and reports
|
||
`sandbox_release_recovery_required`. Startup cannot resume that resource or create
|
||
a replacement while its stop may still be in flight. Recovery requires verifying
|
||
that the original provider operation settled before resolving the matching claim;
|
||
time passing or an application restart never clears it automatically. The working
|
||
copy remains retained, and deletion or orphan cleanup is not a fallback.
|
||
This applies to both runner generations and leaves distinct task/user bindings
|
||
isolated.
|
||
If Daytona rejects a command because its cached shell session no longer exists,
|
||
the provider creates one replacement session and retries within the original
|
||
command deadline. This applies only to a confirmed rejection before dispatch;
|
||
errors while polling or reading output never replay a potentially executed
|
||
command. Concurrent callers share the replacement session.
|
||
Native ACPX recovery also admits a provider started lazily by model selection.
|
||
Selection waits for verified process ownership before accepting the configured
|
||
model; cleanup and out-of-band process launches remain fenced. This matters
|
||
when reopening a persisted Codex session after stopping its sandbox.
|
||
The host retains the verified command snapshot and its pinned descriptors until
|
||
runtime cleanup, so reconnecting for the first turn after model selection can
|
||
launch again without reopening a mutable executable path. A failed turn-start
|
||
signal remains observable without crashing a sidecar that consumes only the
|
||
turn's event stream and result.
|
||
The new scoped-shell startup setting is not added to an existing unscoped Codex
|
||
session's protected launch arguments. Its durable provider profile remains
|
||
unchanged during attachment.
|
||
|
||
Acceptance must resume representative pre-upgrade legacy and native tasks with
|
||
committed, staged, unstaged, and untracked work, verify their original paths and
|
||
usable continuation, and exercise their existing restore mechanism after a
|
||
sandbox restart. A newly created task passing the scoped-folder matrix does not
|
||
establish upgrade compatibility.
|
||
|
||
## New sandbox tasks
|
||
|
||
Sandbox runs use the operating-system user's home directory. Both legacy
|
||
adapters and the native runner enter the same host-owned lifecycle before
|
||
dispatch. Local execution keeps its existing workspace and home behavior.
|
||
Legacy ACP proxies use a private host staging directory while agent sessions start
|
||
in the sandbox home. After login-shell initialization, remote agents restore the
|
||
managed Git PATH ahead of paths added by the profile. This preserves both the
|
||
Git launcher and custom runtime initialization. For API-key Codex ACP runs, the adapter writes the explicit
|
||
key to an owner-only login file in the staging copy; host credentials stay unchanged.
|
||
Per-run GitHub launchers declare their own CommonJS package scope so warm runs
|
||
inside ES-module repositories can still execute Git and GitHub CLI commands. Native runner launches carry the validated scoped paths
|
||
through both runnerd’s Rust sidecar filter and the ACPX JavaScript launch filter,
|
||
including the explicitly controller-projected GitHub broker environment. ACPX does not inherit ambient host GitHub credentials or
|
||
shell startup hooks. CLI configuration remains in its private runtime directories.
|
||
Native OpenCode preserves the same explicit GitHub binding through both its
|
||
runner proxy and provider process. It does not inherit repository credentials
|
||
or shell startup hooks from the host; provider diagnostics redact the projected
|
||
capabilities and credential configuration values.
|
||
Warm sandbox task bindings persist independently of the experimental isolated
|
||
workspace setting. Only the active host run can establish that binding; the
|
||
setting still controls user-configurable worktree operations.
|
||
|
||
```text
|
||
$HOME/
|
||
task/ current issue's working files
|
||
agent/ current agent's durable files
|
||
user/ responsible user's private Paperclip files
|
||
project/ current project's shared files
|
||
repos/ task-specific project repository checkouts
|
||
.codex/ CLI configuration and provider session state
|
||
.cache/ disposable caches
|
||
```
|
||
|
||
An absent task, user, or project produces an empty, unbound directory. The
|
||
`user/` collection never copies a person's computer home directory. Existing
|
||
managed agent workspace files are imported once, excluding CLI homes, caches,
|
||
Git metadata, and conventional credential directories. Task attachments become
|
||
editable working copies whose filenames include attachment IDs; original
|
||
uploads remain unchanged. Task plans and documents are not materialized.
|
||
|
||
The agent starts in `$HOME`. `PAPERCLIP_PRIMARY_REPO` and the workspace context
|
||
identify the repository for project commands. `AGENT_HOME` and
|
||
`PAPERCLIP_{TASK,AGENT,USER,PROJECT,REPOS}_DIR` expose the bound directories.
|
||
Legacy adapters use the host-bound sandbox home for both CLI launch and skill
|
||
discovery; a private per-run runtime directory must not override it. CLI-specific
|
||
configuration remains separately staged beneath that home. Local and SSH homes
|
||
are unchanged.
|
||
Sandbox Codex tool commands preserve the environment initialized by the adapter:
|
||
login-shell execution and shell snapshots are disabled so image profiles cannot
|
||
replace the managed Git PATH. This applies to CLI and ACP execution in both
|
||
runner generations; local execution keeps its existing settings.
|
||
Legacy and native Codex ACP select the provider's `agent-full-access` initial mode only
|
||
inside a validated external work-folder sandbox with an explicit `approve-all`
|
||
binding. This avoids an inner network namespace that prevents tools from reaching
|
||
the sandbox's loopback API and Git-credential callback bridge. Local
|
||
execution and the `approve-reads` / `deny-all` modes retain their existing policy.
|
||
CLI state is separate from the four shared collections. A change of task,
|
||
agent, responsible user, or project cannot reuse a sandbox with another binding.
|
||
|
||
The cache inspector shows “Waiting for first save” until an active run has a
|
||
completed checkpoint. Missing or unavailable sync status is never labeled saved.
|
||
|
||
## Storage and synchronization
|
||
|
||
Postgres stores company/owner bindings, paths, executable bits, current object
|
||
references, trash entries, retry receipts, and each sandbox's sync baseline.
|
||
Contents use the configured `StorageProvider`: S3 or self-hosted `local_disk`.
|
||
The sandbox disk is a working copy, not the durability authority. With local
|
||
disk object storage, operators must persist and back up that storage directory
|
||
alongside Postgres. S3 recovery needs the database and bucket; it does not need
|
||
the original sandbox or application workspace volume.
|
||
|
||
Startup hydrates only the four bound collections. A warm startup first saves
|
||
uncheckpointed local changes and then downloads changed incoming files. There
|
||
is no background incoming refresh while an agent edits. Explicit refresh is
|
||
queued until the run stops, after a successful final flush.
|
||
If another writer changes a shared file between listing and download, incoming
|
||
transfer uses the downloaded version's size, hash, and executable bit. Its sync
|
||
baseline records those same bytes, so an unchanged copy cannot overwrite a later
|
||
shared edit.
|
||
|
||
Providers with native file synchronization or explicit streaming-stdin support
|
||
hydrate files through bounded stdin batches instead of one remote command per
|
||
small chunk. A batch carries at most 4 MiB of file bytes and 256 operations;
|
||
each file still uses confined paths, SHA-256 validation, and atomic publication.
|
||
Before publishing an incoming batch or applying incoming deletions, the host
|
||
persists the intended versions in Postgres. After a failed or lost response,
|
||
the next run reconciles those intents against observed disk contents before
|
||
saving outgoing changes. Imported bytes therefore cannot overwrite a newer
|
||
shared version by being mistaken for an agent edit; actual subsequent edits
|
||
still synchronize normally. Failed explicit refreshes retain the same intent.
|
||
Other providers keep the small-argument transport. Incoming storage responses
|
||
are prefetched four at a time and closed if transfer fails. Repository restores
|
||
use the same path, then recreate confined repository links.
|
||
|
||
Read-only sandbox commands retry transient connection failures and HTTP
|
||
502/503/504 responses up to three attempts within one 120-second deadline.
|
||
Script failures, invalid responses, and authorization failures are not retried.
|
||
An incoming bulk batch has a unique ID and a signed receipt in its private
|
||
staging directory. If the provider loses the response, the host checks that
|
||
receipt: a completed batch is acknowledged without publishing its files again;
|
||
only a missing claim permits resubmitting the same batch ID. An atomic claim
|
||
prevents two concurrent submissions from applying the same batch twice. A
|
||
running or interrupted batch is never replayed. Its bounded outcome check
|
||
either observes completion or fails visibly and retains the working copy for
|
||
the next run's existing intent reconciliation. This does not make arbitrary
|
||
sandbox commands or repository mutations retryable.
|
||
|
||
Repository checkpoints transfer up to sixteen distinct batch-readable blobs
|
||
of at most 1 MiB concurrently, plus at most four larger streaming blobs.
|
||
Transports without batched reads retain the four-stream limit. Identical files
|
||
share one content-addressed upload. Small-file reads are grouped into at most
|
||
1 MiB and 64 files per remote command, with at most four read batches cached
|
||
per checkpoint and sixteen additional batches held by active readers.
|
||
Retries bypass that cache and reopen the actual file. All active transfers
|
||
must settle, and a second filesystem scan must match, before the
|
||
complete checkpoint reference can advance. Scoped-file retry receipts and
|
||
last-write-wins publication remain ordered.
|
||
|
||
Outgoing checkpoints run every **180 seconds**, with at most one in flight,
|
||
and a final flush when execution stops. File signatures include content and
|
||
executable state. Unchanged stale working copies do not overwrite newer shared
|
||
files. Changed files use server-accepted last-write-wins. Operation IDs persist
|
||
before transfer so a lost response retries the same operation rather than
|
||
overwriting a later writer. A failed save remains visible and prevents lease
|
||
cleanup from destroying the working copy. Providers with resume support can
|
||
recover a retained lease on the next run with the same identity/configuration,
|
||
including a lease originally configured as ephemeral.
|
||
|
||
S3 uploads observe source completion and cancel failed requests before reporting
|
||
success. Optional SDK streaming checksums are disabled to avoid an unhandled
|
||
digest rejection when a source file changes during transfer. Work-folder SHA-256
|
||
verification and complete-checkpoint publication remain required. A changing
|
||
source must fail its save without stopping the application or another run.
|
||
Scoped-file and repository-blob uploads retry transient network errors and
|
||
retryable HTTP responses up to three attempts, using the same object key. Each
|
||
attempt opens a fresh source and verifies its complete size and SHA-256; the
|
||
previous request and reader must settle before another attempt starts. Changed
|
||
content, ownership errors, and authentication failures are not retried. Exhausted
|
||
retries retain the previous complete checkpoint and the recoverable working copy.
|
||
|
||
Deletion moves files to recoverable trash. Restore rejects path collisions.
|
||
Explicit purge and permanent owner deletion schedule object cleanup through a
|
||
durable deletion journal. Overwritten scoped-file content is not versioned.
|
||
The current complete repository checkpoint remains retained while its task
|
||
binding exists. Superseded manifests and unused blobs enter a 24-hour deletion
|
||
queue; shared blobs in the current checkpoint stay protected. A save that takes
|
||
more than one hour fails visibly and must retry before its old references expire.
|
||
The scheduler retries object cleanup every three minutes; disabled heartbeat
|
||
scheduling also disables this cleanup sweep.
|
||
|
||
Every file API checks company and owner authorization. User collections are
|
||
available only to the current user and their bound, authorized agent run.
|
||
Generic company access does not grant user-file access. Background transfers
|
||
also recheck the responsible user's active membership before saving.
|
||
|
||
Paths reject traversal, control characters and reserved runtime segments.
|
||
Scoped files cannot be symbolic links or hard links. Linux transport pins
|
||
parent directory descriptors and uses no-follow opens. Repository symlinks
|
||
must stay inside their checkout, outside `.git`. Transfers stream in bounded
|
||
chunks; individual files are limited to 1 GiB, scans to 100,000 entries, and UI
|
||
previews to 8 MiB.
|
||
|
||
## Repositories
|
||
|
||
Each task owns independent clones of all project workspaces with a repository
|
||
URL. Names derive from repository names, with stable workspace-ID suffixes on
|
||
collisions. Initial clones use existing Git credentials and starting-ref policy;
|
||
the primary clone also honors the task's configured branch. Warm starts never
|
||
reset branches, clean edits, or rerun completed setup. Added repositories are
|
||
prepared at the next startup; removed bindings retain saved work. Replacement
|
||
sandboxes rerun project setup to restore excluded dependencies; an existing
|
||
warm checkout keeps both its completed setup and reusable caches.
|
||
|
||
A complete repository checkpoint includes Git objects, refs, HEAD and index,
|
||
tracked working files, and nonignored untracked files. It excludes dependencies
|
||
and generated ignored caches, Git credentials/configuration, hooks, and private
|
||
runtime state, including nested plugin repositories inside private runner caches.
|
||
Checkpoints reject in-progress Git locks and a tree that changes
|
||
during scanning. The database pointer advances only after all required objects
|
||
and the manifest have been saved. Restores verify ownership and hashes, then
|
||
publish the restored directory atomically. Git origin configuration is recreated
|
||
from the host binding. Linked worktrees and submodules using external `.git`
|
||
directories are not supported by this checkpoint format.
|
||
|
||
## API and UI
|
||
|
||
Cached-file inspection is an opt-in development tool. Enable **Allow viewing
|
||
cached task files** in Experimental settings under Paperclip Developer Mode.
|
||
Task properties then show **Files → View cached files** beneath Execution in
|
||
the Workspace section. The dialog previews and downloads the task, project,
|
||
assigned agent, and responsible user's saved collections. Missing bindings are
|
||
empty; private user files are available only to that user. Existing server
|
||
ownership checks apply independently of the visibility setting.
|
||
|
||
The inspector clearly identifies saved copies that can lag behind agent edits.
|
||
Checkboxes select files; the move-to-trash action appears only for a nonempty
|
||
selection. Each scope has Files and Trash tabs, with restoration from retained
|
||
trash. Upload, folder creation, sandbox refresh, and permanent purge controls
|
||
remain outside this inspector. It does not list repositories or the live sandbox disk.
|
||
Agent, project, and profile pages have no standalone stored-file entry points.
|
||
Live sandbox filesystem inspection remains a separate future feature. The
|
||
editable stored-file browser remains in Storybook for design reference.
|
||
|
||
All routes start at
|
||
`/api/companies/:companyId/work-folders/:scope/:ownerId`:
|
||
|
||
- `GET /`: paginated active files or trash (`trash=true`).
|
||
- `GET /content?path=...`: confined download/preview stream.
|
||
- `PUT /content?path=...`: raw `application/octet-stream` upload; supports
|
||
`Idempotency-Key`, `X-File-Content-Type`, and `X-File-Executable`.
|
||
- `POST /operations`: idempotent mkdir, delete, restore, or purge.
|
||
- `GET /sync`: save state, last successful save, errors, and refresh state.
|
||
- `POST /refresh`: request refresh at the active run's safe boundary.
|
||
|
||
## Acceptance gate
|
||
|
||
The Cloud app image includes a build-owned remote provider pack at
|
||
`/opt/paperclip-runner/provider-pack` and configures
|
||
`PAPERCLIP_RUNNER_REMOTE_PROVIDER_PACK_PATH` to that directory. Native OpenCode
|
||
and ACPX runs verify the sandbox's installed pack against this manifest. Reuse
|
||
requires a valid full manifest digest and matching content, including artifact
|
||
hashes, the distribution tree, dependency pins, platform, and Node requirements.
|
||
The source revision remains provenance; a revision-only difference does not
|
||
require retransferring identical contents. App and sandbox builds omit the
|
||
redundant `dist/bin/paperclip-runnerd` from the pack because that executable is
|
||
shipped and verified separately. If content differs, the host stages its
|
||
complete pack before launch. The pack is built
|
||
from the app revision, includes the production lockfile and artifact hashes,
|
||
and must pass its provider-launch checks during the image build. It belongs to
|
||
the app image, not the workspace volume or a scoped file collection. Ordinary
|
||
local execution is unchanged.
|
||
|
||
When diagnosing startup delays, distinguish scoped-file hydration from native
|
||
runtime preparation. `work_folder.prepared` records the intended layout before
|
||
hydration completes. `provider_pack.verify_preinstalled` reports installed-pack
|
||
verification; a fallback within `runner.artifact.prepare` can transfer gigabytes
|
||
independently of task files. Acceptance must prove that a matching image uses
|
||
its installed pack instead of silently relying on that fallback.
|
||
|
||
Both provider-pack build stages install from the immutable
|
||
`docker/daytona-runner/provider-dependencies.lock.yaml` using pnpm 9.15.4 and a
|
||
frozen install. This deployment input is separate from the CI-owned app
|
||
lockfile. Each stage checks its SHA-256 before installing, builds the provider
|
||
entrypoints under that graph, and records the installed lock in the pack.
|
||
Refresh this lock from a reviewed CI-resolved artifact when provider manifests
|
||
or patches change, update both expected hashes, and qualify a new sandbox image.
|
||
Matching source files alone does not prove matching dependencies: acceptance
|
||
also compares the actual app and sandbox production-lock hashes.
|
||
|
||
The qualification entry `build-provider-pack.mjs` (also exposed as
|
||
`pnpm --filter @paperclipai/paperclip-runner build:provider-pack`) requires
|
||
Docker with BuildKit and builds the canonical `linux/amd64` provider stage.
|
||
It uses the same digest-pinned Node interpreter, dedicated dependency graph,
|
||
and fresh TypeScript compilation as the sandbox image; host `node_modules`,
|
||
CI's root lock and previously compiled outputs are not assembly inputs.
|
||
Both Docker stages call the low-level assembler directly, avoiding recursion.
|
||
The entry verifies exported manifest and artifact bytes before atomically
|
||
replacing its output. Failed builds or validation preserve the previous pack.
|
||
This produces a local artifact and does not publish an image or release.
|
||
|
||
Native and legacy Git credential callbacks honor the same experimental duplex
|
||
setting and provider capability gates. When streaming is disabled or unavailable,
|
||
the file bridge remains supported. Credential acquisition allows 35 seconds per
|
||
request so the bridge can return its response within its 30-second window.
|
||
Transient transport failures receive at most three attempts within a 75-second
|
||
overall budget; authorization denials and invalid responses are not retried.
|
||
Only credential acquisition is retried, before starting Git or `gh`; repository
|
||
operations are never replayed. Acceptance must exercise both transport paths
|
||
and record which one was actually selected.
|
||
|
||
Automated tests do not qualify a deployed runner image. Before merging, use a
|
||
new pinned staging stack with the branch's Cloud image and matching migrator.
|
||
The deployed harness must target that tenant URL without launching a local
|
||
server. Enumerate every sandbox-capable adapter/engine and native profile
|
||
exposed by the stack; missing credentials or skipped required profiles block
|
||
acceptance. Verify that standalone stored-file entry points are absent from
|
||
the UI, then enable experimental cached-file inspection and test the task
|
||
dialog's four scopes, previews, downloads, checkbox selection, trash, restoration,
|
||
and save feedback in the deployed browser. Record API/runner operations, two actual 180-second intervals,
|
||
short-run flushes, independent task checkouts, identity/privacy boundaries,
|
||
interrupted saves, and recovery without the original sandbox or app volume.
|
||
|
||
Repository acceptance must also read the private repository through the managed
|
||
Git launcher, for example with `git ls-remote origin HEAD`. Do not count a model's
|
||
PATH changes or credential workarounds as a pass. Native Git credential callbacks
|
||
use the internal API origin, as legacy callbacks do; the public Cloud tenant
|
||
origin requires a browser session.
|
||
|
||
Native continuation validates the full control-plane journal with the same
|
||
64 MiB bound as the runner transport. Tool output in that journal can exceed
|
||
2 MiB without invalidating its identity. Oversized, malformed, foreign-session,
|
||
and unverifiable state still fail closed and remain recoverable in quarantine.
|
||
The cached-file routes initialize storage once per router after authorization;
|
||
every request still checks current owner access. If a run ends while its save is
|
||
starting or in progress, the inspector reports the interruption and keeps the
|
||
previous successful save time visible, including across sandbox replacement.
|
||
|
||
Passing acceptance does not authorize a merge or mainline release. Both require
|
||
the user's explicit sign-off.
|
||
|
||
Cloud's `deploy:stack` tool can deploy an unpublished branch commit to an
|
||
explicit staging tenant. Run it from the Cloud checkout with the staging tenant
|
||
URL and full commit SHA. Pin the target stack first. Require the tool's migration
|
||
and readiness gates plus an authenticated tenant health response with the exact
|
||
SHA. This does not authorize a mainline release or a fleet-default promotion.
|
||
|
||
Staging migrator artifacts use an immutable object-storage prefix. The Docker
|
||
workflow's optional `staging_artifact_base_url` input builds DB/shared tarballs
|
||
and an integrity manifest as a GitHub Actions artifact; it has no release-write
|
||
permission. Supply `staging_lock_sha256` with the reviewed SHA-256 of the
|
||
resolved pnpm 9.15.4 lockfile. The migrator and both app-image builds verify
|
||
that digest before installing dependencies, failing if registry resolution
|
||
has changed. Transfer those artifacts to the staging bucket using conditional
|
||
creates, publishing the manifest last. Do not create GitHub releases or publish
|
||
npm packages for this flow.
|
||
|
||
Cloud enables this lane only in staging through
|
||
`CLOUD_HARNESS_STAGING_ARTIFACT_BASE_URL`. Resolve `preview:<full SHA>` through
|
||
its authenticated deployment API, then target the dedicated pinned stack.
|
||
The configured origin, commit identity, artifact integrity, migration coverage,
|
||
dependency lockfile and tenant readiness are checked before acceptance. Preview
|
||
artifacts cannot become the fleet default. Record both the build workflow and
|
||
the resulting object identities with the acceptance evidence.
|
||
|
||
On graceful app shutdown, idle native sandbox sessions are parked and checkpointed
|
||
before application services and the database close. The drain runs with bounded
|
||
concurrency and a 30-second deadline; failed or timed-out checkpoints are logged
|
||
as incomplete. Active native turns keep their existing restart/reattach behavior.
|
||
Local and SSH session shutdown behavior is unchanged. A hard process kill cannot
|
||
guarantee a provider-session checkpoint; scoped files and repository durability
|
||
remain limited to the last successfully published work-folder checkpoint.
|
||
|
||
Deployments must allow enough graceful shutdown time for that drain and other
|
||
server cleanup. On Railway, configure at least 60 seconds of draining time for
|
||
the service before testing an app redeployment; the platform default is zero.
|
||
This is a deployment prerequisite, not a fleet-default promotion. When upgrading
|
||
from a release without the idle-session drain, park warm native sessions and
|
||
verify their completed harness checkpoints before stopping the old app.
|
||
|
||
Native OpenCode binds each validated completion result to the current provider
|
||
process and turn before the controller can interrupt it. This matches the
|
||
semantic-tool response path. A shutdown interruption must preserve that exact
|
||
completed-turn authority so the session can be suspended and checkpointed.
|
||
Invalid results and conflicting identities still fail validation.
|
||
|
||
Warm native Codex attachment drains bounded informational deprecation notices
|
||
that arrive after the prior turn and its readiness probe. Notices naming another
|
||
turn, new work, and provider requests still block attachment.
|
||
|
||
The app checks the runner’s passive-notice capability before reusing an image
|
||
binary. An older binary is replaced with the app’s compatible artifact through
|
||
a temporary file and atomic rename, preserving image symlink targets and the
|
||
previous launcher on interrupted uploads. The durable checkpoint contract stays
|
||
at version 2 so existing native session backups remain restorable.
|
||
|
||
Warm continuity qualification reads the full run record before selecting the
|
||
persistence contract; company run listings omit the scoped-folder manifest.
|
||
Each completed scoped turn must have its own successful final save before the
|
||
harness reads cached bytes. The sync API exposes `finalCheckpointAt` from the
|
||
run manifest, distinct from periodic `lastSavedAt`. Qualification requires a
|
||
successful terminal run and finalization/save timestamps within that run; a
|
||
periodic save cannot substitute for completed finalization. Legacy host-workspace fallback is used only when
|
||
the full run record has no scoped manifest.
|
||
|
||
The three-turn fixture supplies a shell script that compares exact bytes before
|
||
appending and after writing. Missing or changed prior content fails without
|
||
repair; repeating the first turn cannot truncate existing work. This avoids
|
||
model-generated byte-count arithmetic while retaining independent per-turn
|
||
persistence checks and the same-sandbox, same-provider continuity requirements.
|
||
|
||
Native continuation retains its sandbox, runner-instance identity, provider
|
||
conversation, and files across runs. The existing GitHub identity contract
|
||
rotates the provider process for each run-scoped capability. Qualification
|
||
requires an explicit controller rotation event for that exact transition before
|
||
accepting a changed PID/process fingerprint; other process changes still fail.
|
||
See [GitHub execution identity](execution-github-identity.md) and the
|
||
[run-log contract](run-log-events.md#native-process-rotation).
|
||
|
||
Process metadata updates apply only while their run is active and unfinished.
|
||
Late callbacks from warm-session inspection or maintenance cannot rewrite a
|
||
completed run's process identity. Remote native process timestamps come from
|
||
the validated remote marker; an unrelated host process with the same PID must
|
||
not replace them. Active local execution retains its host process lookup.
|