Skip to content

Upgrade Runbook GA - #1844

Merged
Dmitry Berkovich (dberkov) merged 15 commits into
agent-substrate:mainfrom
HavenXia:runbook-v2
Oct 14, 2026
Merged

Dmitry Berkovich (dberkov) merged 15 commits into
agent-substrate:mainfrom
HavenXia:runbook-v2

Conversation

@HavenXia

@HavenXia Haven Xia (HavenXia) commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

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 (Need an install settings from a config file to persist configurations #1887, implemented in ate-setup: save used settings locally after installation #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 #1686

Merges together with #1812

@HavenXia Haven Xia (HavenXia) added the DO NOT MERGE This PR must not be merged yet. label Sep 24, 2026
@bowei Bowei Du (bowei) added kind/docs Improvements or additions to documentation kind/feature An enhancement / feature request or implementation area/api-machinery area/dev-infra labels Sep 24, 2026
@Raji14

Copy link
Copy Markdown
Collaborator

Overall this is much simpler to operate, but it gives up zero-loss and instant rollback. That tradeoff is the right one if it's deliberate, but the PR description ("Rewrite the runbook to GA solution") doesn't say so. Haven Xia (@HavenXia) can you please clarify what the rationale is?

@Raji14

Copy link
Copy Markdown
Collaborator

Haven Xia (@HavenXia) Some recommendations -

  • Too much cognitive load for a runbook. "Before you start" is longer than the upgrade itself: shell setup, 9-item checklist, paused actors, settings capture, OTLP, state snapshot.
    Suggestions:
  1. Split out "What actors must do" into a separate Actor-owner guide. It has a different audience and is sent at a different time (before step 2). Add a copy-paste notification template for operators to send.
  2. Collapse edge cases (custom OTLP, .ate-dev-env.sh, Cloud SQL) into
    Details blocks or an appendix.
  • "Check your progress" comes first, but depends on files made later. A first-time reader hits commands that diff against ~/ate-upgrade/before-images.txt, which doesn't exist yet. Add a one-liner at the top ("First time? Start at Before you start") or move this section after it.

  • The progress table is ambiguous. Rows like "Step 3 done, or step 4 running" and "the last matching row" make the operator reason about state. Read > markers from a diff is an expert-level interaction. At least make each row's condition mutually exclusive.

  • Terminology.

  1. "Release window" is new jargon for a semver concept. Say "patch upgrade within the same minor version (v0.2.x → v0.2.y)".
  2. The glossary is a dense paragraph with eight bold terms. Use a two-column table or a definition list.
  3. The settings table uses numbers 1–8 that point back to echo output. Use names (proxy, sdsmint, database…) in both places.
  • Too many warnings. There are about 10 [!WARNING] callouts and many "Do not…" sentences. When everything is a warning, nothing stands out. Keep WARNING for irreversible or data-loss actions (e.g. suspending a paused actor whose node is gone, DB schema). Rephrase the rest as positive preconditions ("Start step 4 only when…").

  • Placeholders that fail badly when pasted. OLD_RELEASE=<installed release, for example v0.2.0> inside a bash block is parsed as a redirect and gives a confusing error. Use OLD_RELEASE=v0.2.0 # ← change me, or put placeholders in a "fill these in" block with clear markers.

  • Loops done by hand. "For each line, set ATESPACE/ACTOR, then…" doesn't scale past a handful of actors and invites copy-paste mistakes. Give a loop, or the bulk commands above.

  • Expected time. The table gives per-step ranges, but "5 to 8 hours for larger pools" is buried in step 4. Put a total-time estimate or rule of thumb in the summary so operators can plan a maintenance window.

  • TODOs visible to users. Remove them from the published doc (or move them to a "Known limitations" section with issue links) before GA.

  • Error-message quality. Symptoms like "HTTP 503 actor … unavailable, with no detail" and "HTTP 500 error resuming actor" show that the product's messages don't say the cause is an upgrade. Consider tracking a follow-up for messages that say "atelet restarting; retry".

@HavenXia
Haven Xia (HavenXia) force-pushed the runbook-v2 branch 2 times, most recently from eb63eb6 to 0cb116b Compare September 29, 2026 16:53
@HavenXia
Haven Xia (HavenXia) marked this pull request as ready for review September 29, 2026 17:03
@HavenXia

Copy link
Copy Markdown
Member Author

Overall this is much simpler to operate, but it gives up zero-loss and instant rollback. That tradeoff is the right one if it's deliberate, but the PR description ("Rewrite the runbook to GA solution") doesn't say so. Haven Xia (Haven Xia (@HavenXia)) can you please clarify what the rationale is?

Yes, it's deliberate. Thanks for calling it out; I'll add it to the description.

Zero loss:

The in-place roll replaces workers through the same eviction path as a node upgrade or a spotVM preemption. A worker gets SIGTERM, and its actor has 30 minutes to be suspended before SIGKILL. Actors have to handle that anyway even without upgrade, so the upgrade doesn't add a separate mechanism.

An actor that is suspended in time loses nothing. One that isn't got suspend will crash and lose the work since its last snapshot. The old runbook got zero loss by having the operator suspend every actor on a node before moving that node, and that was most of what made it so heavy to run.

Instant rollback:

That needs the old and new dataplane running side by side, which means spare capacity and the version-label mechanism that #1812 removes. Rollback is now the same roll in reverse, from the old release.

@HavenXia
Haven Xia (HavenXia) force-pushed the runbook-v2 branch 3 times, most recently from ab62606 to d308711 Compare October 7, 2026 17:01
@HavenXia Haven Xia (HavenXia) changed the title [WIP] Upgrade Runbook GA Upgrade Runbook GA Oct 7, 2026
@HavenXia
Haven Xia (HavenXia) force-pushed the runbook-v2 branch 4 times, most recently from 2b045e6 to 4e33851 Compare October 12, 2026 18:14
@HavenXia

Copy link
Copy Markdown
Member Author

Raji (@Raji14)
This runbook is good for review now, with a few TODO for other unfinished work stream.

@HavenXia Haven Xia (HavenXia) removed the DO NOT MERGE This PR must not be merged yet. label Oct 12, 2026

@Raji14 Raji (Raji14) left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of big issues:

  1. U1. No way to see where the upgrade stands
    "Check your progress" was removed instead of fixed. Step 3 can take hours (see U5), which means breaks and handoffs.
    The only record of "the installed release" is a local file that every deploy overwrites. After step 1 it already shows the new version.
    Fix: add a short "Where am I?" section that reads only from the cluster.

  2. Placeholders still break when pasted
    OLD_RELEASE=<installed release, for example v0.2.0> gives syntax error near unexpected token 'newline' in bash and parse error near '\n' in zsh.
    The next line then runs with an empty variable: git clone --branch https://… old. Git reports fatal: repository 'old' does not exist.
    The same pattern appears at L34-35, L45, L51-52, L124-125, and L261-263.
    Fix: put one "fill these in" block at the top (export OLD_RELEASE=v0.2.0 # ← change me). Work out everything else with commands.

  3. The doc never says where the install record is
    "The record directory record.dir names" (L48): that setting is unset by default. The default is the OS cache directory (record.go L53-62):
    Linux: ~/.cache/ate-setup/installs/.yaml
    macOS: ~/Library/Caches/ate-setup/installs/.yaml. operator-install.md only shows the Linux path.
    Records only exist on the machine that ran the install. Anyone upgrading from another laptop, or a cluster installed before #2223, has to write the file by hand. There is no template and no way to check it.
    Fix: give the path for each OS (ls -t "${ATE_RECORD_DIR:-$HOME/.cache/ate-setup}/installs/") and a minimal template. Recommend keeping records in a shared repo by setting ATE_RECORD_DIR.

  4. Step 3 contradicts itself, and every pool is done by hand
    Sub-step 4 ends with kubectl rollout status (L143), which blocks until the whole pool has rolled. Sub-step 5 then says to patch the other pools "without waiting for the roll to finish" (L146). The Ctrl-C tip is far away, at L156.
    The doc doesn't say how to pick the first pool, even though "a bad image still removes the pool's first old worker" (L137).
    Fix: split the step into:
    3a canary: one small, non-critical pool per CLASS
    3b the rest: a loop over the remaining pools
    3c wait for all: one check that nothing was missed

  5. You still can't plan the total time
    The summary now says "Seconds to hours per pool". The accurate rule of thumb was removed. It follows directly from the code (workerpool_apply.go L39-56):
    Workers roll with maxSurge 0 and maxUnavailable 10%, rounded down to at least 1. Pools under 20 workers therefore replace one worker per batch.
    Without spare capacity, each batch can wait up to 30 minutes, the suspend window. The pod grace period is 60 minutes.
    Rule of thumb: pools under 20 workers take up to 30 minutes × workers. Larger pools take roughly 5–7 hours.
    atelet rolls one node at a time with a 330 s grace period (atelet.yaml L138), so step 2 takes up to about 6 minutes × nodes.
    The --rollout-timeout 1h in step 2 (L86) can expire on a healthy roll once a cluster has more than about 10 slow nodes. ate-setup then prints "retry with … failed/….yaml". Say that a timeout here does not mean the roll failed.

  6. Actor owners are never told
    Substrate doesn't suspend actors. Each one needs a SIGTERM handler that gets it suspended within 30 minutes. Yet no step says "tell actor owners before step 3", and there's nothing to send them.

  7. Step 4 understates the impact on actors and covers too much
    Egress isn't mentioned. atenet-egress connections still open after its ~15 s drain are cut (atenet-egress.yaml L1327-1336). For agents streaming model calls, this is the most visible effect of step 4. Only the router's >10 s cut is listed (L212).
    The step 4 row of the summary table (L23) leaves out the possible second round of SIGTERM when a release changes the worker pod template.
    Four separate deploys and a possible multi-hour roll share one Done/Stuck block. "An old pod keeps serving" can't be checked as written.
    Fix: split the step into 4a–4d, each with its own Done/Stuck. Give a command that checks images. Link "Roll back step 4" (L210); it's the only Stuck bullet without a link.

  8. Rolling back step 3 means copying values out of a table
    before-workerpools.txt is a column-aligned table for people, and rollback asks you to copy "the exact string" for each pool (L259).

@Raji14

Copy link
Copy Markdown
Collaborator

Some nits:

  • The cmd/atenet/README.md text this PR edits (L43) still calls the router drain "lossless". upgrade.md and the router manifest both say turns longer than the 10 s drain are cut. Make them agree.
  • Rollback step 3, L273: the two sentences say the same thing.
  • The 6-sentence paragraph explaining the step order (L25) would read better as 4 bullets.
  • Double blank lines at L16-17 and L274-275. There's no blank line before the code fence at L259-260.
  • EnsureCRDs runs inside deploy atelet and deploy apiserver (deploy.go L467-485). If a release adds a new CRD kind, all the new CRDs and RBAC get applied in step 2, so "CRDs follow the API server" doesn't hold.
  • Mention once, in the doc intro, that this runbook replaces the zero-loss model. Operators who used the old runbook will expect it.
  • The comment changes in workerpool_apply.go and its test look good.

@Raji14

Copy link
Copy Markdown
Collaborator

Blockers Jetski called out:

B1. The runbook needs #1812, which is still open
On main and on this PR's head, deploy atelet names the DaemonSet atelet- and pins it to nodes labeled ate.dev/substrate-version (version.go L67-73, atelet.yaml L110-132).
So if someone follows step 2 today:
ate-setup adds a second DaemonSet. Its node selector matches no node, so it runs 0 pods, and the command still exits 0. That looks like "Done".
kubectl rollout status ds/atelet (L92) fails with NotFound.
Step 3 then puts new workers next to old atelets. Rollback step 2 says that combination is not supported.
Fix: add #1812 to the PR's dependency list, and merge this PR with it or after it.
B2. The first upgrade across #1812 has no path
#1812 renames the DaemonSet to atelet. As written, it has no migration for:
existing atelet- DaemonSets. The new atelet pods would sit Pending behind the old pods' host ports.
WorkerPools pinned by spec.template.nodeSelector (the old runbook required that pin). Nodes added later no longer get the label, so pinned pools can't use them.
Fix: add a row to the support table for the oldest release you can upgrade from, for example "Installs from before vX: reinstall". Or add a one-time migration section.

@HavenXia

Copy link
Copy Markdown
Member Author

Blockers Jetski called out:

B1. The runbook needs #1812, which is still open On main and on this PR's head, deploy atelet names the DaemonSet atelet- and pins it to nodes labeled ate.dev/substrate-version (version.go L67-73, atelet.yaml L110-132). So if someone follows step 2 today: ate-setup adds a second DaemonSet. Its node selector matches no node, so it runs 0 pods, and the command still exits 0. That looks like "Done". kubectl rollout status ds/atelet (L92) fails with NotFound. Step 3 then puts new workers next to old atelets. Rollback step 2 says that combination is not supported. Fix: add #1812 to the PR's dependency list, and merge this PR with it or after it. B2. The first upgrade across #1812 has no path #1812 renames the DaemonSet to atelet. As written, it has no migration for: existing atelet- DaemonSets. The new atelet pods would sit Pending behind the old pods' host ports. WorkerPools pinned by spec.template.nodeSelector (the old runbook required that pin). Nodes added later no longer get the label, so pinned pools can't use them. Fix: add a row to the support table for the oldest release you can upgrade from, for example "Installs from before vX: reinstall". Or add a one-time migration section.

B1. #1812 I plan to submit at the same second with this pr so it's ok.
B2. We are having breakage for each pre-GA release now (0.2->0.3->0.4) so such one-time breakage is allowed.

@HavenXia

Copy link
Copy Markdown
Member Author

Thanks for the thorough pass. I pushed a commit that addresses most of it. I'm keeping the runbook short on purpose, so a few suggestions went in smaller than you proposed.

Nits

  • atenet README: no longer says lossless. It now says the drain lets requests finish, except a turn still running after the 15-second budget.
  • Rollback step 3: merged the two sentences.
  • Step order: now four bullets.
  • EnsureCRDs: agreed, a release that adds a CRD kind would update all the CRDs in step 2. I'll fix that in ate-setup in a separate PR, so a component deploy only creates a missing CRD, rather than document around it here.
  • Replacing the zero-loss model: I left this out of the runbook, which describes how to upgrade now. It fits the release notes better.

Big issues

  1. Where am I: added a short section near the top that reads only from the cluster. It lists each component's image, and in a prebuilt install the tag is the release.

  2. Placeholders: Before you start now has one block with the three values to fill in (example values marked # change me), and it reads OLD_RELEASE from the install record. The pool and rollback commands use example values too.

  3. Install record: the doc now gives the default directory on Linux and macOS and mentions ATE_RECORD_DIR. If the machine has no record, it says to write one from the settings in operator-install.md and set OLD_RELEASE by hand. operator-install.md already covers keeping records in a shared repo.

  4. Step 3: the per-pool steps no longer run the blocking rollout status, which is now only under Progress. The step says to start with a small pool of each CLASS that you can afford to disrupt. I didn't split it into 3a/3b/3c or add a loop, to keep the step short.

  5. Timing: Pace now says a batch is 10% of the pool, rounded down, at least one, and that each batch waits up to 30 minutes without spare capacity. The total follows from that, so I didn't add a separate rule of thumb. Step 2 says a deploy atelet timeout doesn't stop the roll.

  6. Actor owners: step 3's note now says to tell them before you start, and the Actors bullet links the API guide's Eviction section.

  7. Step 4: split into 4a to 4d, each with its own Done and Stuck, and every Stuck links to Roll back step 4. 4c mentions outbound connections cut after about 15 seconds. The summary table says workers may get SIGTERM again. "An old pod keeps serving" is now "rollout status does not finish, or a new pod keeps restarting".

  8. Rollback step 3: the old image is looked up in before-workerpools.txt with awk instead of copied by hand.

@Raji14 Raji (Raji14) left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks!

@dberkov
Dmitry Berkovich (dberkov) added this pull request to the merge queue Oct 13, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Oct 13, 2026
Vicente Ferrara (vicentefb) pushed a commit to vicentefb/substrate that referenced this pull request Oct 13, 2026
…d one (agent-substrate#2588)

`ate-setup` doesn't remember an external PostgreSQL's connection string
between runs.

The install record leaves it out intentionally because it can carry a
password. If a run forgets
`ATE_API_POSTGRES_READ_WRITE_CONNECTION_STRING`, `ate-setup` assumes the
bundled database and writes its address into the API server's Secret:

- `deploy apiserver` fails waiting for a bundled PostgreSQL that doesn't
exist, and the next API server restart can't reach the database.
- `deploy ate-system` starts an empty bundled PostgreSQL and moves the
API server onto it, so every actor looks gone.

------
Now, if the run names no database and the Secret already holds an
address that isn't the bundled one, `ate-setup` stops before it changes
the database settings and names the missing variable.

A fresh installs, bundled installs and Cloud SQL work as before, and so
does leaving Cloud SQL with an empty
`ATE_API_POSTGRES_CLOUDSQL_INSTANCE`.

A second commit does the same for
`ATE_API_POSTGRES_OWNER_CONNECTION_STRING`. If the install uses a
separate owner login and a run gives only the read-write string,
ate-setup stops instead of replacing the owner login.


Found while reviewing the upgrade runbook (agent-substrate#1844).
Part of agent-substrate#1686

- [ ] Tests pass
- [ ] Appropriate changes to documentation are included in the PR
Vicente Ferrara (vicentefb) pushed a commit to vicentefb/substrate that referenced this pull request Oct 13, 2026
…1812)

`ate-setup` no longer labels nodes with `ate.dev/substrate-version`. The
atelet DaemonSet is now named `atelet` and runs on every node, instead
of `atelet-<version suffix>` pinned to nodes carrying its version.

The label let two Substrate versions run side by side on disjoint sets
of nodes, which the clone-WorkerPool upgrade runbook relied on. The GA
upgrade doesn't split nodes by version: atelet rolls in place and worker
pools roll as ordinary Deployment updates. With the label, deploying a
new atelet creates a second DaemonSet pinned to a version no node
carries, so it schedules nowhere.

This merges together with agent-substrate#1844, which replaces the label-based runbook
in `docs/upgrade.md`.

#### Breaking change

Due to the removal of version label system. An install from v0.4 or
earlier can't be upgraded in place. Reinstall it, as with every pre-GA
minor release.

Part of agent-substrate#1686

- [ ] Tests pass
- [x] Appropriate changes to documentation are included in the PR
Upgrading ate-controller first did not save a worker roll. When a
release changes the worker pod template, the controller step rolls
every pool on its own. It also had a new ate-controller call an older
ate-api-server, which rejects fields it does not know, and had an older
ateom start with flags from a newer ate-controller.

Move the CRDs and ate-controller into step 4, after ate-api-server. New
workers then start with the flags the old ate-controller renders, and
ate-controller only calls an ate-api-server at least as new as itself.
Rollback runs the same order in reverse.

The atunnel broker identity comments no longer refer to the runbook
order.
ate-setup now records the settings of each deploy run in its cache
directory, in the format --config reads. Before you start says where to
find that record, and the TODO that waited for it is gone.

A later deploy run replaces the record, and it holds no database
connection strings. So the step asks the operator to check the copy and
to export the connection strings when the database is external. It also
asks them to point kubectl at the cluster first, because every command
in the runbook uses the current context.
Releases do not publish prebuilt images for now, so the runbook no
longer waits on where they would live or which tag they would carry.

The three TODOs left say what each waits for and what changes in its
step once decided: the two policy docs to link, whether a compatible
release may change the worker pod template, and per-release
SandboxConfig names.
A full upgrade on a GKE cluster showed places where the runbook does not
match what the commands print or what happens to actors.

ate-setup prints nothing while it waits for the atelet roll, so the
kubectl rollout status that followed it only ever printed the final
line. Run it in a second terminal instead.

kubectl rollout status prints "X of N updated replicas are available"
only after all N replicas are updated, so it cannot tell the operator
that a pool's first new worker is up. Check the newest ReplicaSet's
READY count instead.

publish worker-images now prints the configuration report before the
refs, so read NEW_IMAGE from its output with sed rather than by hand.

Nothing suspends actors during the worker roll. A running demo actor
that exited on SIGTERM crashed seconds into the window. Say so in step
3, and leave a TODO for the eviction section of the API guide.

The cluster had no record of which release was installed. The install
record now carries it as cluster.substrateVersion, so Before you start
points there for OLD_RELEASE.

WorkerPool now names its sandbox class in the first entry of
spec.sandboxClasses. Step 3 reads CLASS from there, in the saved pool
list and for each pool.
Operators need to know whether their upgrade path is supported before
they start. The runbook used to say only that it works within one v0.x
window. It now lists the cases: patch releases can be skipped, minor
releases go one at a time, a new major needs a reinstall, and before
1.0 only patch releases of the same v0.x upgrade in place. It also says
how far a rollback goes.

The policies these rules come from are not in the repository yet, so
the TODO now only waits on linking them.
Each release will ship its default SandboxConfig under its own name,
with the release version as a suffix, and leave the old ones in place.
A WorkerPool without a configRef moves to the newest default. One with
a configRef stays where it is. Every actor start resolves the sandbox
from its pool's current config, so an actor suspended under binaries
its config no longer offers cold boots and keeps only its files. A
separate sandbox upgrade guide will cover pinning a pool and what an
actor keeps.

None of this is in a release yet: deploy sandboxconfig still overwrites
gvisor-default. Keep the TODO and say what the step becomes. Say now
that the step does not roll workers. That holds either way, because
worker pods take nothing from a SandboxConfig.
The API guide now has an Eviction section that says what an actor
needs to keep its state through a worker roll. Link it from step 3 and
drop the TODO that waited for it.
A review walked through the runbook the way an operator would.

The commands had <...> placeholders that break when pasted into a
shell. Put the three values an operator has to supply in one block with
example values, read the installed release from the install record, and
look up a pool's old image in the saved list instead of copying it out
of a table. Say where the record is by default.

Step 3 told the operator to watch rollout status, which blocks until
the whole pool has rolled, and then to move on before the roll
finishes. Drop the blocking watch from the per-pool steps and start
with a small pool of each class. Say that a batch is 10% of the pool
rounded down, and that a deploy atelet timeout does not stop the roll.

Step 4 ran four deploys under one Done and Stuck. Give each its own,
add the egress connections the atenet roll cuts, and say in the summary
that workers can roll again.

Add a Where am I section that lists each component's image, whose tag
is its release in a prebuilt install, and remind the operator to tell
actor owners before step 3. The atenet README no longer calls the
router drain lossless, since a request still open about 15 seconds into
the drain is cut.
The setup block now keeps an OLD_RELEASE set by hand, prints both
releases so an empty one is caught before cloning, and sets images.tag
in new.yaml for prebuilt installs. That edit used to be a prose step
after the block, and missing it left new workers on old atelets.

Also:
- Before 1.0, only patch releases of the same minor upgrade in place.
- Back up the database before step 4a, since rollback keeps the new
  schema, and check that an external database's connection string is
  set in the shell.
- Where am I? tells what has moved from ReplicaSet and ControllerRevision
  ages rather than image tags, which source builds do not carry. It
  covers podcertificate-controller and each pool's roll progress and
  paused state, and gives the upgrade a final check.
- The prebuilt NEW_IMAGE is a command, the needed tools are listed, and
  the revert command is spelled out.
- A Git or CI sync of WorkerPools can put the old worker image back.
- Actor owners are told before the upgrade starts, not at step 3.
- The TODOs are HTML comments, so operators do not read them.
- "Why this order" is gone. It explains the version skew policy, which
  is where it belongs once that policy is in the repository.
The component steps leave out what only the system bundle carries, such
as the ate-system NetworkPolicies and the bundled PostgreSQL, so an
upgrade never applied them. Step 4e runs deploy ate-system once every
component is on the new release, with --setup-csi=none so the CSI
drivers stay as they are. Rollback does not undo it.
@dberkov
Dmitry Berkovich (dberkov) added this pull request to the merge queue Oct 14, 2026
Merged via the queue into agent-substrate:main with commit 8188a11 Oct 14, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/api-machinery area/dev-infra kind/docs Improvements or additions to documentation kind/feature An enhancement / feature request or implementation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants