Skip to content

Sync upstream DNS enforcement and sandbox runtime updates - #78

Merged
EItanya merged 25 commits into
mainfrom
sync/upstream-main-20261014-2
Oct 15, 2026
Merged

EItanya merged 25 commits into
mainfrom
sync/upstream-main-20261014-2

Conversation

@EItanya

@EItanya EItanya commented Oct 14, 2026 •

Copy link
Copy Markdown

Merge upstream through 2c9a05f48 into the fork, bringing in 24 commits covering DNS egress enforcement, runtime-compatible snapshot restore, worker availability, API authorization, and store-aware readiness.

The atelet conflict keeps the fork's transport setup and adds upstream's Control client to its existing credential service. The Helm chart now passes the required DNS enforcement flag and includes the updated CSI TLS validation. Agentgateway remains the chart's dataplane.

The credential test uses the service's fully qualified hostname so its DNS query matches the egress policy. The passthrough refresh test keeps DNS allowed while revoking TLS access, so its denial assertion reaches the gateway.

Breaking change

  • Change ActorTemplate sandboxConfig.sandboxClass values from SANDBOX_CLASS_GVISOR / SANDBOX_CLASS_MICROVM to gvisor / microvm, and update clients for the new protos, including removal of ExternalVolume.status.
  • Allow required DNS hostnames in each actor's egress policy. Queries without a matching policy rule now return REFUSED.
  • Update JWT consumers to read actor claims from actor.ate.dev instead of ate.dev. Ateom tokens use the separate ateom-for-actor.ate.dev claim.
  • Set spec.tls.serverName on CSIDriverConfigs with TLS enabled, and check access-policy grants for the newly authorized RPCs and tag operations.

Please apply the breaking-change label.

Validation: hack/verify-all.sh, Helm lint, manifest rendering, CRD parity, and the API-tool race tests passed. The root go test -race ./... run passed 156 packages; its only failure was the unchanged internal/e2e color-formatting tests under this shell's NO_COLOR=1. That package passed with env -u NO_COLOR go test -race ./internal/e2e. Runtime E2E passed in pr-workflow and helm-e2e. The standard suite ran 141 tests per runtime, with 6 skipped on gVisor and 2 on microVM. Helm's credential-injection and trust-rotation lanes also passed. All PR checks, including unit tests and verifiers, passed on 8945c4f92.

The branch contains one merge commit, cd1fb3958, with both histories preserved. Its tree is identical to the revision that passed CI; GitHub is rerunning checks after consolidation.

Merge this PR with a merge commit to preserve upstream ancestry for the next sync.

JeffLuoo and others added 25 commits October 14, 2026 14:06
…cs (agent-substrate#2576)

`otelgrpc` sets `server.address` on `rpc.client.call.duration` from the
remote address of the client connection: `atelet` dials each `ateom`
over a Unix socket path containing the worker pod UID, and TCP clients
record the IP address of an individual server pod. This produced one
`server.address` value per pod, causing metric series cardinality to
grow with worker pod count and churn.

* Add a metric `View` in `serverboot.newMeterProvider` that strips
`server.address` from all instruments in the `otelgrpc` scope while
keeping `server.port` (one value per service).
* Remove `server.address` from `docs/metrics/registry/metrics.yaml` and
`docs/metrics/substrate.yaml`, and document the view on
`rpc.client.call.duration`.

Fixes agent-substrate#2556

- [x] Tests pass
- [x] Appropriate changes to documentation are included in the PR
…2055)

This prevents actors being scheduled on a worker that is restarting or
temporarily unreachable.

- [ x ] Tests pass
- [ x ] Appropriate changes to documentation are included in the PR
…compatible workers (agent-substrate#2536)

Restoring a sandboxed actor's memory checkpoint (`MEMORY` snapshot
fidelity) requires compatibility between the worker that captured the
checkpoint and the worker restoring it — across the sandbox class
(`gvisor`, `microvm`), the host hardware (`architecture`, CPU features),
and the sandbox runtime assets (`gvisor_asset_hash`, microVM
kernel/rootfs digests).

Fixes agent-substrate#2249 agent-substrate#2250

Following agent-substrate#2381 (where workers began self-reporting their
`default_runtime` and `restorable_runtimes` during registration), this
PR closes the control-plane loop across checkpointing, scheduling, and
restore:

1. **Assignment-Pinned Runtime Stamping**:
When an actor is placed on a worker, the control plane records the
specific `SandboxRuntime` that will serve that actor on
`WorkerAssignment.sandbox_runtime`. Subsequent checkpoints taken during
that assignment (`Pause`, `Suspend`, `Tag`) stamp
`Snapshot.sandbox_runtime` from the assignment rather than re-reading
the worker's live `default_runtime`. This guarantees that if a worker
updates its `default_runtime` while an actor is still running under an
older runtime version, checkpoints taken from that actor are still
stamped with the runtime that actually produced the snapshot.
2. **Runtime-Aware Scheduling with Graceful Fallback**:
When resuming an actor from a `MEMORY` snapshot under its current
template, the scheduler prefers workers whose `default_runtime` or
`restorable_runtimes` match the snapshot's `sandbox_runtime` (with
`hardware.Matches` returning `false` if either runtime is `nil`). If no
runtime-compatible worker has capacity (and the actor is not pinned to a
local pause checkpoint), the scheduler relaxes the runtime constraint so
the actor can still make progress on any otherwise-eligible worker.
3. **Consistent Restore Fidelity Downgrade (`MEMORY` -> `VOLUMES`)**:
At restore time, compatibility is evaluated directly against the
persisted `WorkerAssignment.sandbox_runtime` — the same field that will
stamp the next checkpoint — keeping the restore decision and future
snapshot stamps in lockstep across retries and re-entries. When the
assigned runtime does not match the snapshot's required runtime, all
durable snapshots (actor-owned, explicit `source_tag` clones, and
borrowed golden snapshots) uniformly downgrade the `RestoreRequest`
fidelity to `SNAPSHOT_FIDELITY_VOLUMES` and log a warning.

---

### Scenarios

#### 1. Cold Boot (No Snapshot) or Non-Memory (`VOLUMES` / `ROOTFS`) /
Repointed Snapshot
- **Scheduling**: No memory checkpoint is being restored
(`RequiredRuntime == nil`), so any active worker matching the template's
`SandboxClass`, selectors, and resource limits is eligible.
- **Assignment**: `WorkerAssignment.sandbox_runtime` is set to the bound
worker's `default_runtime`.
- **Restore & Next Checkpoint**: The actor cold-boots via `Run` (or
restores `VOLUMES`/`ROOTFS`), and any subsequent pause/suspend stamps
the new snapshot with `WorkerAssignment.sandbox_runtime`.

#### 2. Warm Restore on a Matching Worker (`default_runtime` or
`restorable_runtimes`)
- **Scheduling**: The actor has a stamped `MEMORY` snapshot (taken under
the current template). The scheduler filters for workers where either
`default_runtime` or an entry in `restorable_runtimes` matches the
snapshot's `sandbox_runtime`.
- **Assignment**: Under the worker's row lock,
`WorkerAssignment.sandbox_runtime` records the exact runtime entry on
that worker that matched the snapshot (`default_runtime` on a
steady-state worker, or the matched `restorable_runtimes` entry during a
runtime rollout).
- **Restore & Next Checkpoint**: Because
`WorkerAssignment.sandbox_runtime` matches the snapshot's required
runtime, `atelet.Restore` runs at full `SNAPSHOT_FIDELITY_MEMORY`. If
the actor was restored via a `restorable_runtimes` entry on a worker
whose `default_runtime` has rolled forward, its next pause or suspend is
still stamped with the matched `restorable_runtimes` entry.

#### 3. Durable Snapshot Fallback When Compatible Workers Are Full
(Actor-Owned, `source_tag` Clone, or Borrowed Golden Snapshot)
- **Scheduling**: The initial `Schedule` pass with `RequiredRuntime`
finds no capacity (`ErrNoCapacity`). Because the snapshot is durable
(not a node-local pause checkpoint), the workflow relaxes
`RequiredRuntime = nil` and re-runs `Schedule`, picking an eligible
worker running a different runtime.
- **Assignment**: `WorkerAssignment.sandbox_runtime` records the
fallback worker's `default_runtime` (or, if a compatible worker freed up
between the two `Schedule` calls, the matched runtime from the unrelaxed
snapshot requirement).
- **Restore & Next Checkpoint**: `ensureAteletRestored` detects that
`WorkerAssignment.sandbox_runtime` does not match the snapshot's
required runtime, logs a warning, and downgrades the `RestoreRequest`
fidelity to `SNAPSHOT_FIDELITY_VOLUMES` so the actor boots a fresh
sandbox under the new runtime while preserving its volume state.
Subsequent checkpoints are stamped with the new worker's runtime.

#### 4. Local Pause Checkpoint on an Incompatible Node
- **Scheduling**: A `PAUSED` actor holds a node-local checkpoint on
`AssignedNode`. Because nothing releases the node-local checkpoint and
claim on fallback yet, `canRelaxRuntime` returns `false`. If no worker
on `AssignedNode` supports the snapshot's `sandbox_runtime`, scheduling
returns `ResourceExhausted` rather than silently stranding the local
checkpoint.

#### 5. Concurrent Worker Re-registration & Workflow Re-entry Races
- **Stale cache vs. row-locked worker**: If `atelet` re-registers a
worker and drops an older runtime from `restorable_runtimes` before the
scheduler's watch cache catches up, `BindActorToWorker` evaluates
admission under the worker's DB row lock and `assignWorkerAttempt`
computes `WorkerAssignment.sandbox_runtime` from that fresh row-locked
worker rather than the cached copy.
- **Re-entry in `RESUMING`**: If `ResumeActor` fails after persisting
`RESUMING` (e.g., during volume attachment) and re-enters later,
`validateAssignedWorker` accepts a worker assigned via runtime
relaxation, and `ensureAteletRestored` derives `runtimeIncompatible`
from the persisted `WorkerAssignment.sandbox_runtime` so the restore
fidelity and the next checkpoint's stamp cannot drift even if the
worker's advertised runtimes changed in the interim.

### Testing & Docs
- **Functional test**
(`TestResumeActor_SandboxRuntimePlacementAndFallback`): Exercises
stamping, compatible worker placement, full-worker fallback to
`SNAPSHOT_FIDELITY_VOLUMES` (for both actor-owned and borrowed golden
snapshots), re-stamping on subsequent suspend, and `restorable_runtimes`
warm restore + `PauseActor` -> `SuspendActor`-from-`PAUSED`.
- **Cluster E2E test** (`internal/e2e/suites/sandboxruntime` —
`TestSnapshotRuntimeStamp`): Verifies live worker `DefaultRuntime`
stamping across `ResumeActor`, `PauseActor` (local checkpoint + warm
restore), `SuspendActor`-from-`PAUSED`, `SuspendActor`-from-`RUNNING`,
and `SourceTag` actor clones.
- **API Guide** (`docs/api-guide.md`): Documents
`Snapshot.sandbox_runtime` and `WorkerAssignment.sandbox_runtime`
placement and fallback semantics.

- [x] Tests pass
- [x] Appropriate changes to documentation are included in the PR
…ubstrate#2330)

The `runsc create`, `start`, and `restore` commands for the pause
container previously wrote their stdout/stderr to the shared worker pod
log without actor attribution. `RunWorkload` and `RestoreWorkload` now
forward that CLI output and the pause container's stdio through the
existing actor log envelope, labeled with the actor and
`ate.actor.container.name="_pause"`. This covers cold startup and
VOLUMES/MEMORY restore. JSON fields are preserved, and reserved `ate.*`
fields cannot override platform attribution.

`kubectl ate logs actors` excludes `_pause` by default, keeping the
pause container's runsc startup banners and host paths out of the
default application log view. Application container logs and actor
lifecycle records remain visible. Pass `-c _pause` to inspect the pause
container's runsc diagnostics. The attributed records remain in the
worker pod log and centralized logging backends.

Restore requests reject unsupported snapshot fidelity values before
acquiring actor locks or setting up networking and log pipes. Both
workload paths call `StartJSONLogPipe` directly, consistent with
application containers. The RPC closes its pipe writer when it returns;
the forwarding goroutine does not retain the RPC context.

This change attributes runsc CLI/pause stdio output. With runsc debug
logging disabled, sentry and gofer stdio is directed to `/dev/null`;
this change does not enable that logging. `--alsologtostderr` uses glog
text by default. Tests cover that text format and runsc JSON diagnostics
using `msg`, `level`, and `time`.

Addresses the pause-container invocation portion of agent-substrate#2271 and the
default log display concern raised in review. One-shot runsc helpers,
sentry panic capture, and the micro-VM implementation remain separate
work. No new registry attributes are introduced.

Validation after merging main `f03327bd`:

- Linux/amd64, Go 1.27.2: full `go test -race -count=1 ./...` passed for
152 packages, with PostgreSQL/envtest integration enabled by `CI=true`.
- Pause attribution, fs checkpoint/restore arguments, fidelity
rejection, and CLI source/container filtering regressions passed 20
repetitions with the race detector.
- All 15 repository verification scripts passed. The pinned
golangci-lint v2.13.2 used Go 1.27.1 and a 15-minute timeout after its
Go 1.27.2 export-data incompatibility was confirmed. The earlier
package-loading timeouts are retained in the validation record.

Both VOLUMES and MEMORY restore paths retain main's fs restore arguments
while forwarding pause output through the actor log pipe. The checkout
remained clean.

Verification ran as an unprivileged user. Root-gated mount tests and
real gVisor E2E were not run for this head.

AI assistance was used to resolve the merge conflicts and run
verification.
Fixes agent-substrate#2075 
- [x] Tests pass
- [x] Appropriate changes to documentation are included in the PR
With serverName empty, VerifyConnection skipped the hostname check and
accepted any certificate from pod-identity CA. Require it via CEL rule
on CSIDriverTLSConfig and fail closed in resolveTLSConfig

#### Breaking change
- CRD: CEL rule on `CSIDriverTLSConfig` requires a non-empty
`serverName`
  when `enabled` is true. Manifest regenerated.
- Runtime: `resolveTLSConfig` returns an error when `serverName` is
empty,
so configs stored before the rule fail closed instead of connecting
unverified.
- Docs: `serverName` is documented as required when TLS is enabled.

Fixes agent-substrate#2079

- [x] Tests pass
- [x] Appropriate changes to documentation are included in the PR
…-system's default-deny ingress (agent-substrate#2637)

Since agent-substrate#2522, ate-system denies ingress by default and admits API clients
only from namespaces labeled `ate.dev/api-access=true`. The
`benchmarking` namespace that the automation's runner Job templates
create had no labels, so on a fresh cluster the runner could not dial
ate-api-server. The boomer worker died ensuring its atespace
(`CreateAtespace ... dial tcp ...:443: i/o timeout`), and the locust
master waited for a worker that never connected until the Job timed out.

This labels the namespace in both runner Job templates
(`runner-job.yaml.tmpl` and `nighthawk-ingress-runner-job.yaml.tmpl`),
as `benchmarking/locust/deploy.sh` already does for manual runs.

`RunnerNamespaceTest` reads the label that the `ate-api-server`
NetworkPolicy selects on and checks both templates against it, so the
two can't drift apart. It fails on both templates without the change.

- [x] Tests pass
- [x] Appropriate changes to documentation are included in the PR (none
needed)
…; actor JWT claims move from ate.dev to actor.ate.dev (agent-substrate#2523)

Part of agent-substrate#777

The object-store plugin's control sidecar will exchange an actor JWT for
a downscoped object-store credential scoped to that actor. atelet needs
a way to obtain that JWT over the channel it already authenticates on,
which is `WorkerService`, where it already mints the certificate
counterpart with `MintAteomActorCertificate`. This adds
`MintAteomActorJwt` beside it. Nothing calls it yet. atelet starts to in
a later PR, together with the node plugin exchange.

Two commits:

1. **Proto.** `MintAteomActorJwtRequest` mirrors the certificate request
(actor, UID guard against a deleted-and-recreated actor) plus
`audiences` from `Control.MintActorJwt`, each non-empty. The lifetime is
fixed at one hour, so there is no expiration field. Response is the
token and its expiry. Generated Go and validation code, and the
validation wrapper.
2. **Handler.** The caller must be atelet and the actor must exist. The
mint is then refused unless the caller's node hosts the actor, as
`RequestActorSuspend` already requires. The actor's state decides which
status field is trusted, since both can outlive the hosting they record:
the node of the assigned worker while the actor is resuming, running or
suspending, and `assignedNode` while it is suspended or suspending with
no worker and its latest generation is a local checkpoint with no
durable copy, which is how a paused actor looks now that `PAUSED` is no
longer a state. The whole rule lives in a new `controlapi.HostingNode`,
in `hosting.go`, so the suspend and resume workflows' own notion of a
paused actor is what it reads, and so the certificate RPC can reuse it
in its follow-up. Anything else resolves to no node and is refused: a
worker gone from the store, a reverting actor whose assignment outlives
its released worker, a crashed or deleting actor whose `assignedNode` is
only a record of its last node, and a suspended actor with a stale
`assignedNode`. The node check runs before the UID check and every
refusal is `NotFound`, so a node learns nothing about actors it does not
host, not even that one exists. A UID mismatch is then answered
`Aborted`, as `MintAteomActorCertificate` answers it. Each handler
composes its own `actoridjwt.Claims` and signs with the actor-id JWT
pool, as `Control.MintActorJwt` does. The two tokens differ only in
kind: a token minted here speaks for an ateom acting on the actor's
behalf, with subject `ateom-for-actor/<atespace>/<name>` and its claims
under `ateom-for-actor.ate.dev`, while `Control.MintActorJwt` mints the
actor's own token with subject `actor/<atespace>/<name>` and claims
under `actor.ate.dev`. The zero kind is rejected, so a token cannot be
minted without choosing. A relying party can therefore tell the actor
from a Substrate component acting for it, and the snapshot credential
exchange will accept only the `ateom-for-actor` kind, so actor code
cannot reach its own snapshots.

Follow-up, in its own PR: `MintAteomActorCertificate` gets the same host
check. It is an existing RPC on the actor boot and resume path, so the
change deserves its own review and e2e run.

#### Breaking change

Actor JWTs no longer carry the `ate.dev` claim. The actor's own token
now carries the same fields under `actor.ate.dev`, and the new
ateom-for-actor token carries them under `ateom-for-actor.ate.dev`. The
OIDC discovery document's `claims_supported` lists the two new names.
Any relying party that reads `ate.dev` from an actor JWT, agentgateway
included, has to read `actor.ate.dev` instead, and must not accept an
`ateom-for-actor` token as the actor. Relying parties should accept both
`ate.dev` and `actor.ate.dev` before the API server is upgraded, and
drop `ate.dev` an hour after it, once every token minted before the
upgrade has expired. Please add the `breaking-change` label.

- [x] Tests pass with `go test -race ./...`. New unit tests cover the
handler and the claims package: claim contents per kind, `exp` equal to
`expire_time`, the host check for a foreign node, a foreign node with a
stale UID, a paused actor on each node, a paused actor being suspended,
a crashed actor on its last node, an unhosted actor, a lost worker, and
each validation and lookup error.
- [x] Appropriate changes to documentation are included in the PR. The
RPC, messages and both tokens' claims are documented in the proto, and
no guide lists WorkerService RPCs.
…1989)

Fixes agent-substrate#1395

`/readyz` now fails while the store is unreachable, so the replica
leaves the Service. `/healthz` is unchanged.

- [x] Tests pass
- [x] Appropriate changes to documentation are included in the PR
…bstrate#2656)

This avoids needing network ingress policy overrides.

- [x] Tests pass
- [x] Appropriate changes to documentation are included in the PR

Signed-off-by: Yan Avlasov <yavlasov@google.com>
…gent-substrate#2618)

- Follow-up to agent-substrate#2531: --upstream-connect now defaults to true, so the
Envoy router reaches atunnel through mTLS CONNECT tunnels on 8443.
--upstream-connect=false goes back to HTTPS on 443.
- An empty --upstream-credential-bundle now needs
--upstream-connect=false, and the startup error says so. The check only
applies to the Envoy ingress router; the egress gateway and agentgateway
don't use the flag (agentgateway always uses CONNECT).
- Split xDS server setup out of startEnvoyDataplane so a new test can
parse real flags and check the built snapshot for both settings.
- The e2e gRPC downgrade test now expects only gRPC Unavailable, so it
fails if the router falls back to the HTTPS path.
- Updated architecture.md, threat-model.md and the router manifest
comment.

Related: agent-substrate#1208 
> It's a good idea to open an issue first for discussion.

- [x] Tests pass
- [x] Appropriate changes to documentation are included in the PR
Rewrites the upgrade runbook for an `in-place` roll by K8s deployment.

Each component is upgraded where it runs, in order:
podcertificate-controller, atelet, the workers, ate-api-server, the CRDs
and ate-controller, atenet, then the default SandboxConfig.
ate-controller follows the API server because it is a client of it, and
new workers start from the pod template the old ate-controller renders.
The old runbook ran the old and new dataplane side by side instead.

This deliberately gives up two things the old runbook had.

The first is zero loss. Workers are replaced through the eviction path:
`SIGTERM`, then 30 minutes for the actor to be suspended before
`SIGKILL`. Actors already need to handle this for node upgrades and
preemption. An actor that is suspended in time loses nothing. One that
isn't crashes and loses the work since its last snapshot. The old
runbook avoided this by having the operator suspend every actor on a
node before moving it.

The second is instant rollback. Rollback is the same roll in reverse,
from the old release, instead of flipping a node label back. Keeping
both versions running needs spare capacity and the version label.

## TODOs

 Some places are marked TODO because they depend on unfinished work:

1. [DONE] The install record `ate-setup` writes (agent-substrate#1887, implemented in
agent-substrate#2223)
2. The compatibility policy and the version skew policy. The runbook
links both, while both poclies are still in design.
3. Per-release SandboxConfig names

> Part of agent-substrate#1686

Merges together with agent-substrate#1812
…te#2454)

The `apitool` tool used to guess the resource model from names. It kept
a hard-coded list of resource names and assigned each RPC to whichever
one appeared longest in the method name.

That list is stale and subresource methods were therefore attributed to
their parent. For example, `GetActorEgressPolicy` counted as an Actor
method and `ListWorkerActorAssignments` as a `Worker` method. Because of
that, subresources and singletons were linted against the wrong resource
or not at all.

This change records the resource model in the proto itself and makes
apitool read it from there.

Changes

The PR has three commits, which can be reviewed one at a time:

1. Add proto options to annotate API resources and methods.
2. Annotate the Control API's resources and methods.
3. Simplify the apitool CLI to read resources from the proto
annotations.

Lint rule changes:

New:
- `standard-method-naming`: the verb, then for a subresource one of its
parents (or Global), then the resource. List uses the plural.
- `subresource-naming`: a subresource's message is named without its
parent type.
- `singleton-no-list`: a singleton has no List method.
- `custom-response-name-matches-method`: a custom method returns
{MethodName}Response, not the resource.

Changed:
- `get-request-single-objectref` is now get-request-shape, and it covers
subresources: the parent's ObjectRef plus name.
- The List, Create, Update and Delete shape rules understand
subresources, singletons, Global parents and scope. For example, a List
request has an atespace field only for an atespace-scoped top-level
resource.
…t-substrate#2586)

#### Breaking change

An ActorTemplate's `sandboxConfig.sandboxClass` is now a string,
`gvisor` or `microvm`, the same values the WorkerPool and SandboxConfig
CRDs use. The `SANDBOX_CLASS_GVISOR` and `SANDBOX_CLASS_MICROVM`
spellings are rejected.

To upgrade:

- In ActorTemplate manifests and API requests, replace
`SANDBOX_CLASS_GVISOR` with `gvisor` and `SANDBOX_CLASS_MICROVM` with
`microvm`.
- Go clients of `pkg/proto/ateapipb`: the `SandboxClass` enum is removed
and `SandboxConfig.SandboxClass` is a `string`.
- Existing ActorTemplates read back with an empty `sandboxClass`,
because the stored enum value does not decode into the new string field.
Templates are immutable, so delete them and their actors before
upgrading and recreate them afterwards.
- The `SANDBOX CLASS` column of `kubectl ate get actortemplates` now
prints `gvisor` or `microvm`.


Fixes agent-substrate#2406
…ive_actors (agent-substrate#2221)

Part of agent-substrate#1590

Benchmark runs now record:

- Per-operation RPS and latency over time (Locust `--csv-full-history`),
not just the aggregate.
- Restore and checkpoint latency from the
ate_actor_{restore,checkpoint}_duration histograms (total phase, every
snapshot kind)
- `active_actors` (p50/p95/avg + timeseries) in `server_summary.json`.

- [x] Tests pass
- [x] Appropriate changes to documentation are included in the PR
…Internal (agent-substrate#2614)

Fixes agent-substrate#2613

On GKE with a GCS backend, the object-store plugin's Workload Identity
token gets `412 PreconditionFailed` ("The operation requires that
Uniform Bucket Level Access be enabled.") from the public `gs://gvisor`.
`toStatus` maps that, like any error it doesn't recognize, to
`Internal`, and atelet didn't fall back on `Internal`, so sandbox
creation failed after agent-substrate#2439.

**Changes**
- atelet falls back to an anonymous read on `Internal`. In practice
that's any unexpected plugin failure. That's acceptable because
anonymous reads only reach public objects, and atelet still checks the
sha256.
- So a broken plugin isn't hidden: the plugin logs a scrubbed warning
when it returns `Internal`, and atelet warns when it falls back.
- Updated the `AssetProvider` contract, `api-guide.md` and threat model
T-08.

**Testing**
- Unit tests: `go test ./cmd/atelet/... ./internal/objectstoreplugin/...
./pkg/objectstorage/...`
- GKE 1.37 (Envoy dataplane): reproduced the 412 on `main`. With this
PR, gVisor prewarm falls back and succeeds, and the counter demo passes
create, persistence across requests, suspend, and resume from snapshot.

A follow-up issue will discuss the long-term design for plugin fallback.

---------

Co-authored-by: Anna Pendleton <13793293+annapendleton@users.noreply.github.com>
Part of agent-substrate#2069 actor lifecycle v2

If a local snapshot exists for the requested uuid, restore from it
instead of downloading from object storage.

- [ ] Tests pass
- [ ] Appropriate changes to documentation are included in the PR
Move the platform-neutral DNS relay test helper out of the Linux-only
file so Darwin tests compile.
fixes agent-substrate#2180

1) ateom now runs under a minimal init (internal/ateominit) when it
would be PID 1. the init reaps orphans like the runsc sandbox, so ateom
no longer calls wait4(-1) and no longer races os/exec for subprocess
exit statuses. this drops internal/childreap and the RunCommand gating,
which is what kept the checkpointed sentry a zombie while runsc delete
ran.

2) after a MEMORY checkpoint ateom-gvisor waits for the sandbox to exit
and be reaped (pidfd opened before the checkpoint, so pid reuse can't
fool it) before running cleanup.

This PR was AI assisted. I have reviewed the changes.
Fixes agent-substrate#2619

atunnel waited for the gateway's CONNECT answer for as long as the actor
stayed active. A gateway at its connection cap queues the CONNECT; the
actor's client gives up in seconds, but the queued CONNECT kept its slot
and later opened a tunnel nobody wanted.

The tunnel open now has a 10s deadline on the Egress side. Since the
HTTP/2 multiplexing change, ending the dial context already resets the
CONNECT stream, which withdraws it from the gateway's queue; this PR
adds the test that pins that behavior and the deadline that triggers it
for an actor that has gone away. A tunnel already returned is
unaffected. The HTTP/1 fallback path is left as it was.

Tested with `go test -race -count=20 ./internal/atunnel/...` and the
`hack/verify` scripts. New test: an Egress with a shortened timeout,
dialing through the real client to a gateway that never answers, closes
the actor's connection at the deadline while the gateway sees the stream
reset.

Prepared with help from an AI coding agent; reviewed and verified by the
author.
…gent-substrate#2562)

Working on agent-substrate#1563. 

Fixes agent-substrate#1696

### Summary
- **Fail-closed interceptor for unregistered RPCs:**
`UnaryServerInterceptor` now returns `PermissionDenied` when an RPC is
not in `defaultRPCPermissions` while enforcement is enabled.
`WorkerService` RPCs, which authenticate `atelet` in their handlers via
`ateletauth`, are explicitly registered with `inHandler(...)`.
- **Exhaustive coverage test
(`TestDefaultRPCPermissions_CoversEveryServedRPC`):** Walks every method
of every service in `ateapi.proto` and fails if any RPC has neither a
permission rule nor an `inHandler` reason, if the registry names an RPC
that isn't in `ateapi.proto`, or if a service gains a streaming RPC
(which the unary interceptor would not check).
- **Tags (`Tag` type and rules):** Models tags as atespace children
(`can_get`: `viewer`, `can_update`/`can_delete`: `editor` on the parent
atespace; `can_create_tag`: `editor` on the source actor's atespace;
`can_list_tags`: `viewer` on the atespace or `global:root`). Calling
`CreateActor` with an explicit non-golden `source_tag` requires
`can_use_tag` (`editor` on the tag's atespace), regardless of whether
the tag's scope is `TAG_SCOPE_ATESPACE` or `TAG_SCOPE_PUBLISHED` (the
handler continues to enforce that `TAG_SCOPE_ATESPACE` tags are only
used within the same atespace).
- **Actor identity credentials:** Restricts `MintActorJwt` and
`MintActorCertificate` to `egress_gateway` so an atespace `editor`
cannot impersonate an actor.
- **Model cleanup:** Removes `global.can_get` and
`actor_template.can_update`, which no RPC checks.
…at Worker holds the actor (agent-substrate#2668)

Follow-up to agent-substrate#2523. Suspend (and pause), crash, and a resume that finds
its Worker ineligible release the actor's Worker before they rewrite its
status. In that window, or for good if the status write fails, the
status still names the Worker, so its node could mint a one-hour
ateom-for-actor JWT, even after it took another actor.

- `controlapi.HostingNode` returns a Worker's node only while the
store's worker assignment binds that Worker to the actor's UID. A failed
lookup refuses the mint.
- A paused actor, which has no Worker, still resolves to
`assigned_node`; that branch is unchanged.
- Real-PostgreSQL tests drive each workflow into that window and through
a rebook.

- [x] Tests pass
- [x] Appropriate changes to documentation are included in the PR (none
needed)
#### Breaking change

`ExternalVolume.status` is gone. Use `storage_volume_id` (empty until
the volume is created) and the actor state instead. Please add the
`breaking-change` label.

Fixes agent-substrate#2414

- [x] Tests pass
- [x] Appropriate changes to documentation are included in the PR
Bring the fork up to date with upstream while retaining agentgateway and Helm integration. Wire the DNS policy control client into atelet's existing transport setup and synchronize controller flags and CSI validation in the charts.

Align egress test hostnames and policy refresh fixtures with DNS enforcement so requests reach the gateway assertions.

Signed-off-by: Eitan Yarmush <eitan.yarmush@solo.io>
@EItanya
EItanya force-pushed the sync/upstream-main-20261014-2 branch from 8945c4f to cd1fb39 Compare October 15, 2026 12:53
@EItanya
EItanya merged commit 8852660 into main Oct 15, 2026
4 checks passed
@EItanya
EItanya deleted the sync/upstream-main-20261014-2 branch October 15, 2026 13:13
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.