Repository navigation
feat(operator): the egress proxy of run pods, and their network deny by default (D15c) - #42
Merged
Merged
Conversation
… run pods The goldens were rendered at 0391809. adam-rs main is at 3f714d0, where the coder refuses to start with a GitHub App key file beside local commands (exit 78) unless ALLOW_COMMANDS_NEXT_TO_SECRETS is accepted, and the chart refuses to render github.auth=app with runPods.enabled=false unless runPods.allowCommandsNextToSecrets is true (adam-rs#131). The two App cases (coder, coder-split) are about the pods of the agent, so they keep their meaning and accept the risk by name; extract.py does not project the variable (an AgentConfig takes it in extraEnv). The token case needs nothing. The renders at the two revisions differ only by that variable: the env contract of a coder with run pods off did not move. extract.py now also projects each pod's service account and token, and, when the chart renders run pods, the pod template, the quota, the priority class, the network policy, the service account, the role and its binding and the admission policy with its binding, under runPods. The README says what was decided and why. Refs #36 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ZQyuMx8GXkV9VrUGeHMno
A coder that runs its commands in pods of their own (adam-rs#131) needs, beside its
workers, a pod template, a quota, a network policy, an account with a bound Role and
an admission policy. The coder chart renders them; the operator now does too, for an
AgentConfig that sets coder.runPods.sizeClass, and renders nothing and changes no
digest for one that does not.
- aap-api: Coder.runPods { sizeClass } (the CRDs regenerated and copied).
- aap-ports: RuntimeSpec.run_pods and Workload.service_account, absent from the JSON
when absent; EnvValue::PodNamespace; the invariants in check().
- aap-domain: the settings the deployment gives (RunPodSettings, read from a file the
chart renders and validated at start), the template as a file set of the workers,
the variables of adam's contract; what is only applied is left out of the digest. The only home of
the contract stays aap-domain.
- aap-runtime-kubernetes: the account, the quota, the network policy, the binding and
the ConfigMap of the policy's parameters; removed when the service stops asking or
goes, before anything the binding was holding up; no object of another owner touched.
- aap-controller, operator: RUN_PODS_FILE.
- deploy/operator: runPods and runPodClasses (off by default, and then not a trace in
any render: the four goldens are byte for byte what they were), a PriorityClass, the
Role of run pods, one admission policy for every agent with its parameters in a
ConfigMap per agent, and the operator's Role gaining serviceaccounts, resourcequotas,
rolebindings, pods/delete and the right to BIND that one Role by name (it holds no
right to make a pod or exec). A validator refuses what the operator would refuse at
start. render-check.sh holds all of it, and api --check reads the shape with run
pods on.
- Forms and the Platform API: coder.runPods.sizeClass as agent.runPodSizeClass, and
GET /v1/info offering the classes the chart writes into the API's file.
- Parity at adam-rs 3f714d0: four more cases of the coder chart with run pods on, the
template compared in aap-domain, the quota, network policy, binding, Role,
PriorityClass and the admission policy (with the parameters substituted) compared in
aap-runtime-kubernetes. tests/run-pods-admission.sh asks a real API server.
Refs #36
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ZQyuMx8GXkV9VrUGeHMno
…erator may The previous commit made `ensure` of a spec without run pods, and `delete`, list serviceaccounts, resourcequotas and rolebindings to clear what a service that stopped asking had left. The operator's Role has no right on those kinds unless the chart has runPods.enabled (the default render must stay what it was), so with run pods off every reconcile and every delete of every agent would have failed with Forbidden. The fake API server enforces no RBAC and the tests did not see it; a review of the diff against the default Role did. The look is now a choice of the composition root: KubernetesRuntime::clearing_run_pods, off by default, turned on by `operator run` when it has settings for run pods (the same condition under which the chart gives the rights). A spec that has run pods is applied whatever it says. The cost is stated in the READMEs and in section 60a: turning runPods.enabled off clears nothing, so remove coder.runPods from the configs first. Two tests hold it: a default provider makes not one request that names the three kinds nor deletes a pod, through an ensure, a second ensure, a status and a delete; and a provider that is not looking applies the objects of a spec that has run pods and leaves them when a later spec has none. Refs #36 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ZQyuMx8GXkV9VrUGeHMno
…nly with settings The composition root decides whether the Kubernetes provider looks for run pod objects to clear, and a mutation of that one line (never told to look) survived every test: no test reads the provider's flag, and the cluster tests skip without a cluster. The decision is now a function of its own, kubernetes_runtime, and the provider reports the flag (is_clearing_run_pods), so a unit test holds both ways. Refs #36 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ZQyuMx8GXkV9VrUGeHMno
Section 60a gains the "D15a built" note: what is built and where, how it is proven, the parity with the coder chart at adam-rs 3f714d0, what a bare kube-apiserver v1.35.8 was asked and answered, what a person must know (the operator can bind the one Role of run pods, the guard fails open for an account whose parameters are deleted, the agents of a namespace share the tighter quota, the account may exec in any pod of its namespace), eleven decisions for the owner to confirm, what only the kind end-to-end (D15b) can prove, and what is still unverified. P-020 proposes the split between the namespaced operator and the chart's cluster-scoped guard with its costs. The D15 row is split in two, the risks gain two rows, section 59a's field table the new row, the index its revision. The chart's README gains "Run pods" (a sequence and a state diagram, every value, who makes what, the five things to know, how to check a cluster) and the Rights table the rows the chart adds; the crates' READMEs and the goldens' README say what changed in each; CLAUDE.md and the root README carry the status. The chart's CI also lints and runs kubeconform over the render with run pods on. Refs #36 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ZQyuMx8GXkV9VrUGeHMno
The workspace denies expect_used outside tests by lint, and a unit test inside src/ is not a test target the allow of the integration tests covers; clippy -D warnings said so only after the test was written and the gates had been run once. Refs #36 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ZQyuMx8GXkV9VrUGeHMno
shellcheck 0.9.0, the version CI runs, reads `A && B || C` as SC2015; 0.11.0 does not. The check refuses the same parameters as before. Refs #36 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ZQyuMx8GXkV9VrUGeHMno
An OpenAI-compatible endpoint that plays a coder's model with no network and no key, for the kind end-to-end of run pods (D15b). It answers every task with the same three turns: a scratch project (or prepare_workspace with a repository), run_command with a probe written for the task, and the answer, which holds the probe's output. The probe prints one key=value line per fact (host, user, directory, what /work and /work/workspaces hold, the NAMES of the environment variables and never a value, whether a peer's path is visible, and with a repository whether the mirror can be written and what git says), writes the same lines to /tmp/probe-<name>.out, and can wait for a release file. The journal keeps what the model was asked (run, turn, tool results) and no header, so the key the coder sends is not in it. The coder's own image runs it from a ConfigMap, so nothing else is pulled. Seven node:test tests run the probe for real and cover both shapes of answer. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ZQyuMx8GXkV9VrUGeHMno
The two coders the end-to-end runs, both the real adam-rs coder image (ghcr.io/vymalo/another-adam-rs/coder:sha-3f714d0, adam-rs main at #131, which contains #117) pinned by tag and digest on two ref: lines of run-pods.yaml and nowhere else: e2e-plain, a token coder with no run pods, and e2e-coder, a GitHub App coder with coder.runPods. defaultAgentImage is not moved. The pin was read from ghcr.io anonymously on 2026-10-26 and again on 2026-10-27 (a Docker v2 image manifest whose digest is the one in the file; six of the workspace run image's seven layers are the coder's). run-pods.values.yaml is laid over the chart: run pods on, a quota of two pods so the script can fill it. run-pods-support.yaml is a throwaway Postgres with two databases and the scripted model as a Deployment of the coder's own image. bin/operator/tests/e2e_run_pods_fixture.rs needs no cluster: both coders pass aap_domain::resolve (the plain one the same on an operator with and without run pods; the other refused by one without and resolved by one with), the claim is work-e2e-coder-0, the pin is one pin, and the fixtures name the Secrets, Services and files the script makes. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ZQyuMx8GXkV9VrUGeHMno
run-pods-e2e.sh installs the operator chart in kind twice, first with runPods.enabled=false and then without the flag (the upgrade), and runs the real coder as a token coder with no run pods and a GitHub App coder with coder.runPods, on the scripted model. It shows, step by step: the cluster-scoped objects absent, then present, and the operator's can-i answers under both Roles; the plain agent unchanged by the upgrade (objects, pod uid, start and restarts, StatefulSet generation, config digest, asked again minutes later); the operator's objects for the coder and none for the plain one; run-pods-admission.sh run in the cluster as the agent's account; two runs, each in a run pod made from the operator's template and started by the kubelet, the command run in it, each pod mounting its own run only; the quota counting two pods, a third refused with exceeded quota, a run that waits for a slot with no pod; the network policy (DNS and a public address answer, a private address does not, while the coder's pod reaches it); a run in a repository with a read-only mirror and a worktree; the release; the plain agent asking for run pods and stopping; a deleted service leaving nothing, run pods included, only the claim; no secret and no panic in any answer or log. House style of api-e2e.sh and chain-e2e.sh: only a kind-* context, a namespace of its own that it labels and refuses without the label, undoes only what it made, printf for bodies, logs from a successful kubectl logs holding a line of their component, HELM_KUBECONTEXT, diagnostics without any line that holds a secret. Walked under dash against a bare kube-apiserver v1.35.8 (RBAC, quota, admission policy), the real operator running as its own service account, the real coder (adam-rs main, the code of 3f714d0) and a Postgres, with a stand-in for the kubelet: all steps pass (44 ok). Seven assertions were broken on purpose, each in a fresh world, and each failed in the step it should. A real kubelet, kindnet and the 3 GB pull are the job's to show. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ZQyuMx8GXkV9VrUGeHMno
The job run-pods-e2e of operator-image.yml: a kind cluster of its own (the node image of the other jobs, v1.35.8 by digest, kindnet enforces NetworkPolicy), the operator image that passed its smoke test loaded into it, and run-pods-e2e.sh. The coder, the run image and Postgres are pulled by the kubelet by digest. The scripts job also checks and runs the scripted model's tests with node. The job does not gate push, as chain-e2e does not: it pulls a 3 GB image of another repository pinned in this one, and run pods are off by default, so a red run is a reason to look and does not put a broken default into the release. To make it gate, add it to the needs of push (the header and the comment at push say so). An owner decision, recorded in section 60a. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ZQyuMx8GXkV9VrUGeHMno
Nine checks, each broken on purpose once in a copy of the tree and each failing as written: - its values render (an unknown registry setting makes them refuse); - the PriorityClass, the policy and its binding for the namespace aap-e2e-runpods, the Role of run pods and the settings that name them (run pods turned off in the values); - the quota is two pods, the class standard, the run image pinned by digest (a quota of three); - no registry, no Platform API, no Ingress, no ExternalSecret (a registry added); - the operator watches the release's namespace, runs the image of the job and never pulls it (a watched namespace set); - one flag apart: the same values render no trace of run pods with runPods.enabled=false and all of them without it (a template that renders without the flag); - the coder's pin is on two lines of run-pods.yaml that are one pin, and its digest is in no other file of deploy/ or .github/ (a tag changed; the digest copied into another file); - the support manifest runs the coder's pin and the pin of postgres.yaml by digest, every object carries the script's label, none names a namespace (a label removed); - the script's constants are the ones the values and the fixtures name (a name changed), and the model agrees with the script on the first line of the answer, the probe file, the release file and the keys (the first line changed). 388 checks in all (379 before). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ZQyuMx8GXkV9VrUGeHMno
The kind end-to-end of run pods is built: section 60a gains the "D15b built" note (what the script shows step by step, how it was walked where it was written and what that is not, what only the job can prove, seven decisions for the owner to confirm), the D15 row and the D15a notes are amended, the revision history and the status paragraphs of CLAUDE.md and the root README say it, the operator chart's README and tests/e2e/README.md describe the script, its fixtures and its limits. D15 stays open until the job has run green in CI. Verified 2026-10-27 and recorded as such: the coder's pin by an anonymous manifest request to ghcr.io (a Docker v2 image manifest; its digest is the one in run-pods.yaml); the script walked under dash against a bare kube-apiserver v1.35.8, the real operator running as its own service account (no Forbidden in its log) and the real coder, with a stand-in for the kubelet. Unverified, and said so: a real kubelet, kindnet enforcing the egress rule, the exec over a WebSocket on 1.35, the 3 GB pull, the timings. Assertions broken on purpose in fresh worlds, each failing in its step: a quota of three (step 4; with that check neutralised, step 6), run pods off in the second install (3), a run pod that mounts the whole workspaces directory (5), a run pod that reaches the Service address (7; passes with a warning under AAP_E2E_CNI_MAY_IGNORE_POLICIES=1), a quota named for the plain agent (2), a model that drops the command's output (8), a model whose journal keeps its key (11). Closes #36 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ZQyuMx8GXkV9VrUGeHMno
D15a's list of what stays unverified said a run pod runs with a read-only root. The template sets RuntimeDefault seccomp, no privilege escalation and drops every capability, but no readOnlyRootFilesystem, as the coder chart's does not either; Pod Security restricted still admits it. Refs #36 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ZQyuMx8GXkV9VrUGeHMno
In kind, the first run of run-pods-e2e refused both token cases, but by the volume rule: the ServiceAccount admission plugin adds the token as a projected volume before the policy runs, which a bare API server does not. The two cases now accept the token rule's message or the volume rule's; any other refusal still fails. §60a notes it. Refs #36 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ZQyuMx8GXkV9VrUGeHMno
…ing it run-pods-e2e's first full run (run 40432460348, step 9) gave e2e-plain coder.runPods and took it away again. The operator deleted the account before it applied the workloads, so the StatefulSet still named it: its next pod was refused (serviceaccount "e2e-plain" not found) and the service stayed Degraded at 0/1. The run pod template ConfigMap was still there after 120 s: it was left to remove_stale_config_maps, which runs only once the service is Ready, and it could not become Ready. Applying the workload without the account was not enough either. A real API server keeps the deprecated serviceAccount of a pod template through a server-side apply that leaves serviceAccountName out (one value on write; the apply releases the field it owned and conversion fills serviceAccountName from the other). Verified 2026-10-27 on a bare kube-apiserver v1.35.8, StatefulSet and Deployment: after an apply without the field both names still read the account; a merge patch that nulls only serviceAccountName changes nothing; one that nulls both removes them, and a later apply keeps them removed. Now ensure revokes the right and the run pods first, applies the workloads without the account, frees any workload whose answer still names an account its render does not with that merge patch (sent only then: a render without run pods sends nothing, and a workload an earlier operator left naming a deleted account is freed by the next pass), and only then removes the quota, the network policy, the parameters, the run pod template and, last, the account. Nothing that is not ours is deleted. The delete path is unchanged. The fake API server copies the two-name rule of the real one and records an account deleted while a workload names it; the new tests fail on the code before. tests/cluster.rs has one case against a real API server (run on the bare kube-apiserver above: it passes with the fix, fails without it, and fails on a patch of only serviceAccountName). Each new guarantee was broken once on purpose and a named test failed. Refs #36 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ZQyuMx8GXkV9VrUGeHMno
Lockfile only; no Cargo.toml is touched. Both moves are semver-compatible and built with the CI toolchain, 1.94.1 (rust-version 1.94). - RUSTSEC-2026-0386 (tokio, unsound: AsyncFd::new and related constructors violate I/O safety): 1.53.2 -> 1.53.3 - RUSTSEC-2026-0382 (rustls-webpki, certificate verification may accept distrusted intermediates): 0.103.15 -> 0.103.16 Left, none has a fix we can take: - RUSTSEC-2023-0071 rsa 0.9.10 (Marvin timing attack, no patched release) and RUSTSEC-2026-0387 crypto-bigint 0.5.5 (fixed only in 0.7.6, a different major than the elliptic-curve 0.13 and p256 0.13 stack that needs ^0.5): both come from jsonwebtoken 11.1.0 with its rust_crypto backend, which only the dev-dependencies of aap-auth-oidc switch on (tests/provider.rs links it beside aws-lc-rs). They are not in the normal or build graph of any target, so neither the operator nor the API binary links them, and the authenticator installs its own aws-lc-rs provider. - RUSTSEC-2024-0449 unsafe-libyaml 0.2.11 (unmaintained, no patched release): serde_yaml_ng 0.10.0, the newest release, still depends on it; it is used by aap-operator and aap-api-server to read YAML. Changing the YAML library is not a lockfile change. cargo-deny 0.18.9 `check advisories`: 5 findings -> 3 (the ones above). Verified with 1.94.1: `cargo fmt --all --check`, `cargo clippy --workspace --all-targets --locked -- -D warnings` and `cargo test --workspace --locked` pass (1266 passed, 0 failed, 1 ignored). The cluster suites (AAP_TEST_KUBECONFIG) skip without a cluster. The reqwest 0.12 pin of aap-auth-oidc is untouched. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ZQyuMx8GXkV9VrUGeHMno
…by default (D15c) adam-rs closed the network of a run pod (its ADR 0042, ff10395 and 7598255): the run pods' NetworkPolicy allows DNS, the pods of an HTTPS-only egress proxy on 3128 and extraEgress, and nothing else; the proxy (adam-egress, from the coder's own image) is a Deployment, Service, ServiceAccount, allow-list ConfigMap, PodDisruptionBudget and NetworkPolicy; the run-pod template carries HTTPS_PROXY, HTTP_PROXY and NO_PROXY. The operator now makes the same, per agent, so a run pod made by the platform is no more open than one made by the chart. * aap-ports: EgressProxy; RunPods::network is required (a spec with run pods and no policy for them is not one); except_cidrs moved into the proxy. Breaks every RunPods literal. * aap-domain: runPods.egressProxy in the settings (allow, extraAllow, extraNoProxy, replicas, ports, resources) with the coder chart's checks; networkPolicy.enabled is gone; the template's six proxy variables; the proxy described with adam's names kept here. * aap-runtime-kubernetes: the six objects, applied before the policy that names it and the binding, never taken for stale, cleared before the run pods' policy, which is the last policy to go; the proxy is no workload of the service and its pods carry no managed-by. * operator chart: runPods.egressProxy.* (the default list is the coder chart's), the render-time refusals, enabled: false refused; the operator's Role does not change. * parity: goldens regenerated at adam-rs 7598255 (extract.py projects the proxy's objects; two cases move the proxy off its defaults); the env contract of a coder with run pods off did not move; the proxy is compared object by object in four cases. * run-pods-e2e.sh: the proxy's objects, and a rewritten step 7 (controls, refusals of a direct connection, of the API server and of another pod, curl and npm through the proxy, example.com 403, the proxy's log, IPv6); the coder pin moves to sha-7598255 (verified 2026-10-27 at ghcr.io). Run where written: fmt, clippy -D warnings, cargo test --workspace (1280), render-check.sh (411 checks; the new ones broken on purpose in a copy of the tree, 24 mutations, each failing the check made for it), regen.sh --check, run-pods-admission.sh against a bare kube-apiserver v1.35.8 (every case as expected, with the new template), the run-pods case of the cluster tests against it (real server-side apply of the proxy's objects), shellcheck and dash -n on the script, and its jq programs against the operator's objects. Unverified until CI has run the job: that a kubelet starts the proxy, that kindnet drops the direct connections, and curl and npm through the proxy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ZQyuMx8GXkV9VrUGeHMno
§60a gains the D15c built note: what is built and how it is proven, what only the job can prove, and ten decisions for the owner (the allow-list is the deployment's; an image without adam-egress, which defaultAgentImage is, is documented and not refused; the proxy is no workload of the service; networkPolicy.enabled is gone; the proxy's pods carry no managed-by; the order; pods made before the upgrade lose their way out; no scheduling for the proxy; the checksum's value; the Role does not change). §59a's field row, the D15 rows and amendments, the Risks, P-021 and §93's egress question follow, and the revision history gets its row. D15b's job has run green (run 40435825771, verified 2026-10-27 with gh run view); CLAUDE.md and the root README say so and say what D15c proves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ZQyuMx8GXkV9VrUGeHMno
Contributor
✅ AI Governance check passedThis PR declares AI usage, references a source of truth, and provides verification evidence. Thank you. |
|
The coder pinned since D15c (adam-rs 7598255) carries adam-rs #133: a run pod mounts every slot of its run (a project, a worktree) as a mount of its own on top of the run's directory, so the pod cannot rename the slot's directory. The first run of run-pods-e2e on that pin (run 40482371992) failed on the assertion that A's pod mounts only its run's directory: the pod mounted that and A's project, iso-a. A and B now expect the run's directory and their project's slot (and A's annotation the two lines, the slot's with its generation), C its run's directory and its worktree's slot r1, and still nothing else of the volume. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ZQyuMx8GXkV9VrUGeHMno
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
1. Summary
This PR changes:
The operator renders an egress proxy for each coder that has run pods. The run pods' network becomes deny by default, and the run-pod template carries the proxy's variables. This matches what the adam-rs chart renders since feat(egress): an HTTPS-only egress proxy for run pods another-adam-rs#134 and feat(chart): run pods reach the internet only through the egress proxy another-adam-rs#135.
adam-egressfrom the agent's coder image: a Deployment, Service, ServiceAccount, allow-list ConfigMap, PodDisruptionBudget and its own NetworkPolicy.extraEgress.Parity moves from adam-rs
3f714d0to7598255. Two new parity cases move the proxy off its defaults, and the proxy's six objects are compared object by object.The operator chart gets
runPods.egressProxy.{allow, extraAllow, extraNoProxy, replicas, ports, resources}, carried inRUN_PODS_FILE. It refuses what the coder chart refuses at render, andnetworkPolicy.enabled: falseis refused.Apply and clear order. The proxy is applied before the policy that names it. On clear:
run-pods-e2e.shgains step 7. From a run pod the operator made:example.comgets403;The coder pin moves to
sha-7598255@sha256:6e74b292…, which hasadam-egress(verified 2026-10-27, anonymous manifest request).Docs:
CLAUDE.mdandREADME.md. They also record that D15b's job ran green (verified 2026-10-27, run 40435825771).It solves:
2. Intent
The intent of this PR is:
3. Scope
In Scope
render-check.sh.Out of Scope
These are the decisions in §60a, P-021:
AgentConfigthat extends the allow-list. The list is deployment-level only.adam-egress: it is documented, not refused. Note thatdefaultAgentImageis still such an image; moving it is a decision for the owner.4. Verification
I verified this change by:
Commands run:
Results:
Unverified until CI runs
run-pods-e2e:No cluster or container daemon exists where this was written. D15 stays open until that job is green.
5. Screenshots / Evidence
6. Risk Assessment
Risk level:
Potential risks:
adam-egressgets a proxy that cannot start, so its run pods have no internet.RunPods::networkis now required in the neutral spec, which breaks everyRunPodsliteral.Mitigation:
7. AI Usage Declaration
AI was used for:
Human verification:
8. Reviewer Focus
Please focus your review on:
Generated by Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need.