Skip to content

ci: track core releases, run E2E against published gateways, and tag lockstep releases - #6

Merged
rhuss merged 29 commits into
mainfrom
ci/automation
Nov 6, 2026
Merged

rhuss merged 29 commits into
mainfrom
ci/automation

Conversation

@rhuss

@rhuss rhuss commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Release automation, the nightly drift check, and the E2E lane for sdk-go. Stacked on the governance PR; review after it is merged. Draft until the E2E lane is green here and the ai-openshell-sdk-bot App exists (#2).

Core releases and SDK releases

  • core-watch.yml (hourly, or dispatched with a tag): core-tags.sh lists core tags newer than the pin, and open-bump-pr.sh opens one proposal per tag on branch core/<tag>. Each proposal moves the pin in buf.gen.yaml, regenerates the bindings, refreshes the vendored conformance fixtures, and shows the proto diff between the two core tags. Labels: core-bump plus stable or prerelease. Only the newest pre-release of a train gets a proposal, a newer one closes older pre-release proposals, and stable proposals are never closed automatically.
  • release-tag.yml (push to main, or dispatched for a commit): when a commit moves the pin to stable core vX.Y.Z, tag-release.sh tags vX.Y.Z, publishes the GitHub release with the merged proposal's body, and asks the Go module proxy for the version. Pre-release pins and other commits create nothing; a tag at another commit fails the run.

Drift

  • core-drift.yml (nightly): drift-check.sh runs buf breaking from core main against the pin for the files the SDK generates, regenerates from main and builds, and lists RPCs the SDK doesn't call yet (informational). update-drift-issue.sh keeps one Core main drift issue current and closes it when the check passes. Failed automation runs add a dated line to that issue through report-failure.sh.

E2E

  • e2e/: the Go E2E suite from test: E2E tests for Go SDK NVIDIA/OpenShell#3338 by @jiripetrlik, with his commit, plus one commit adapting it to CreateOption.
  • e2e.yml: start-gateway.sh runs the published gateway with the Docker driver from core's own deploy/docker/docker-compose.yml and gateway.toml at the core tag, with the supervisor and sandbox images pinned to that version. ci.yml calls it for pull requests that change the pin, the bindings, the SDK code, the suite, or the gateway scripts.

The workflows that write to GitHub use the App token, scoped to the permissions each job needs, so their pushes and PRs trigger CI. They need the SDK_BOT_CLIENT_ID and SDK_BOT_PRIVATE_KEY secrets. workflows:check also runs shellcheck over hack/.

Testing

  • mise run ci and mise run workflows:check pass locally.
  • core-tags.sh against real core tags, and against a synthetic list for ordering (pre.10 after pre.2, v0.1.10 after v0.1.9).
  • open-bump-pr.sh v0.1.7 --dry-run against a base pinned to v0.1.6: one commit, regenerated bindings, fixture report, and the proto diff in the body.
  • tag-release.sh --dry-run: tags v0.1.7 at the pin commit; no-op for commits without a pin change and for pre-release pins.
  • drift-check.sh against core main: pass. The breaking check reports a deliberately deleted field.
  • Drift issue scripts against a stubbed gh: create, append a failure, keep pending failures across a passing report, close.
  • start-gateway.sh up to the compose call (image check, files at v0.1.7, image pinning). The E2E lane itself runs for the first time on this PR.

Part of NVIDIA/OpenShell#5031.

@SDAChess

SDAChess commented Nov 4, 2026

Copy link
Copy Markdown

Setting up the App now, with a few changes from #2:

  • Name: ai-openshell-go-sdk, so the bot is ai-openshell-go-sdk[bot]. hack/automation/README.md still says ai-openshell-sdk-bot.
  • Permissions: contents, pull requests and issues (read & write), plus metadata (read). No workflows: write: nothing in this PR touches .github/workflows/, and I'd rather the adaptation agent can't push workflow changes. We can revisit if adapt.yml really needs it.
  • Client ID instead of App ID: app-id is deprecated in create-github-app-token v3.3.0. The secrets are SDK_BOT_CLIENT_ID and SDK_BOT_PRIVATE_KEY, used as client-id: ${{ secrets.SDK_BOT_CLIENT_ID }}.
  • sdk-bot environment: the secrets go in an sdk-bot environment that only main can deploy to, not at repository level. As repository secrets, anyone who can push a branch could mint a bot token, push to a PR as the bot and then approve it, which gets around the last-push approval in main-integrity. Please add environment: sdk-bot to the jobs in core-watch.yml, release-tag.yml and core-drift.yml; they all run from main already. adapt.yml will need some thought there, since a label trigger runs in PR context.

I'll post here once the secrets are in.

rhuss added a commit that referenced this pull request Nov 4, 2026
Per the App setup on #6: the App is ai-openshell-go-sdk, its secrets are
SDK_BOT_CLIENT_ID and SDK_BOT_PRIVATE_KEY (app-id is deprecated in
create-github-app-token v3.3.0), and they live in the sdk-bot
environment, which only main can deploy to. As repository secrets, any
pushed branch could mint a bot token, push to a PR as the bot, and then
approve it. core-watch, release-tag, and core-drift run from main and
now use environment: sdk-bot.

Signed-off-by: Roland Huß <rhuss@redhat.com>
@rhuss

rhuss commented Nov 4, 2026

Copy link
Copy Markdown
Contributor Author

Thanks, all four make sense. Done in 8cda85b: client-id: ${{ secrets.SDK_BOT_CLIENT_ID }} in all three workflows, environment: sdk-bot on the core-watch, release-tag, and core-drift jobs, and the README names ai-openshell-go-sdk. The bot's git identity comes from the token's app slug, so the rename needs nothing else. I also rebased onto the current #5, which drops the old dco.yml. Agreed on adapt.yml: it comes later and gets its own design for the PR-context trigger.

Base automatically changed from ci/governance to main November 5, 2026 14:54
rhuss and others added 17 commits November 5, 2026 15:56
conformance:check reads hack/conformance-fixtures.txt, so the core bump
automation can refresh the same fixtures it checks.

Signed-off-by: Roland Huß <rhuss@redhat.com>
core-tags.sh prints core release tags newer than the pin: every newer
stable tag and the newest pre-release if it is ahead of them (--all for
every tag). open-bump-pr.sh builds branch core/<tag> in a temporary
worktree, moves the pin, regenerates the bindings, refreshes the
vendored fixtures, and opens a PR labeled core-bump plus stable or
prerelease with the proto diff between the two core tags. It skips tags
that already have a branch or a merged proposal, closes older
pre-release proposals, and closes proposals whose core tag was deleted.
--dry-run changes nothing on GitHub.

Signed-off-by: Roland Huß <rhuss@redhat.com>
tag-release.sh creates the SDK tag vX.Y.Z when a commit on main moves
the pin to stable core vX.Y.Z, publishes the GitHub release with the
merged proposal's body, and asks the Go module proxy for the version.
Pre-release pins and commits that leave the pin alone create nothing,
and an existing tag at another commit fails the run.

Signed-off-by: Roland Huß <rhuss@redhat.com>
core-watch runs hourly and on dispatch, and opens a proposal per new
core tag. release-tag runs on pushes to main and on dispatch for a given
commit. Both use the ai-openshell-sdk-bot App token, scoped to the
permissions they need, so their pushes and PRs trigger CI. Failures are
reported through report-failure.sh, a job-summary stub until the drift
issue exists. Adds the core:tags, core:bump, and release:tag tasks.

Signed-off-by: Roland Huß <rhuss@redhat.com>
drift-check.sh runs buf breaking (FILE rules) from core main against the
pinned tag for the proto files the SDK generates, regenerates the
bindings from main in a temporary worktree and builds and vets against
them, and lists RPCs the SDK does not call yet. It writes a markdown
report with a 'Pin | Core main | Result' header and exits 1 on breaking
changes or a failed build. Uncovered RPCs are informational.

Signed-off-by: Roland Huß <rhuss@redhat.com>
update-drift-issue.sh writes the drift report into the single open
'Core main drift' issue (label core-drift), creating it on drift and
closing it once the check passes and no failures are pending.
report-failure.sh appends a dated line to the issue's
'Automation failures' section, which the report updates carry over.
Both take --dry-run.

Signed-off-by: Roland Huß <rhuss@redhat.com>
core-drift runs drift-check.sh and update-drift-issue.sh with an App
token scoped to issues. A run that produces no report is recorded as an
automation failure.

Signed-off-by: Roland Huß <rhuss@redhat.com>
workflows:check also runs shellcheck over hack/automation and
hack/migration. Fixes the one warning it found in port-pr.sh (an
unquoted command substitution passed to git add).

Signed-off-by: Roland Huß <rhuss@redhat.com>
Signed-off-by: Jiri Petrlik <jpetrlik@redhat.com>
The suite from NVIDIA/OpenShell#3338 predates the switch of
Sandboxes().Create from a labels map to CreateOption values. Pass
labels through v1.WithLabels and drop the nil maps.

Signed-off-by: Roland Huß <rhuss@redhat.com>
start-gateway.sh fetches deploy/docker/docker-compose.yml and
gateway.toml from core at the given tag, pins the supervisor and sandbox
images to the same version, starts the gateway with the Docker driver,
waits for /readyz, and registers it as gateway 'e2e' for the SDK. It
first waits up to 15 minutes for the gateway image of a fresh tag.
stop-gateway.sh saves the gateway log and removes the gateway and the
sandbox containers it created.

Signed-off-by: Roland Huß <rhuss@redhat.com>
e2e.yml starts the gateway for a core version (default the pin), runs
mise run e2e, and uploads the gateway log on failure. ci.yml calls it
for pull requests that change the pin, the bindings, the SDK code, the
suite, or the gateway scripts.

Signed-off-by: Roland Huß <rhuss@redhat.com>
Core's gateway.toml binds the health server to 127.0.0.1 inside the
container, so the published port 8081 never answers and the readiness
wait times out. Bind it to all container interfaces; the compose file
publishes it on the host's loopback only.

Signed-off-by: Roland Huß <rhuss@redhat.com>
Since NVIDIA/OpenShell#3383 the gateway ships no provider profiles, so
creating a claude-code or nvidia provider fails until the profile is
imported. TestMain now imports both into the default workspace, based on
core's example profiles, replacing earlier copies so reruns start from
the same definitions.

Signed-off-by: Roland Huß <rhuss@redhat.com>
The gateway rejects an endpoint protocol without access or rules, and
the SDK's NetworkEndpoint can't express those yet. The tests only need
the credentials, so the fixtures use host and port only.

Signed-off-by: Roland Huß <rhuss@redhat.com>
A credentialed L4 endpoint also needs allow_uninspected_credentials,
which NetworkEndpoint can't express either. The tests only exercise
credentials, so the fixtures declare no endpoints.

Signed-off-by: Roland Huß <rhuss@redhat.com>
A provider without a profile workspace resolves its type against global
profiles, so profiles imported into the default workspace were not
found. Import and delete the fixtures in the global scope, as core's
E2E harness does with --global.

Signed-off-by: Roland Huß <rhuss@redhat.com>
rhuss added 5 commits November 5, 2026 15:56
The fixture profiles declare no endpoints, and the gateway withholds
credentials of endpointless profiles unless the sandbox policy binds
them to an endpoint. The credential tests now create their sandboxes
with such a binding, as core's E2E does. Also drops the leftover nil
CreateOption arguments.

Signed-off-by: Roland Huß <rhuss@redhat.com>
A sandbox whose policy binds a provider's credentials can't detach it,
so deleting the provider right after the sandbox fails with a conflict
until the asynchronous sandbox deletion ends. Retry on conflict. Skip the
attach/detach test, which needs a profile with endpoints that the SDK
can't import yet.

Signed-off-by: Roland Huß <rhuss@redhat.com>
Signed-off-by: Roland Huß <rhuss@redhat.com>
Per the App setup on #6: the App is ai-openshell-go-sdk, its secrets are
SDK_BOT_CLIENT_ID and SDK_BOT_PRIVATE_KEY (app-id is deprecated in
create-github-app-token v3.3.0), and they live in the sdk-bot
environment, which only main can deploy to. As repository secrets, any
pushed branch could mint a bot token, push to a PR as the bot, and then
approve it. core-watch, release-tag, and core-drift run from main and
now use environment: sdk-bot.

Signed-off-by: Roland Huß <rhuss@redhat.com>
adapt.yml, adapt-context.sh, and the agent:adapt label are not part of
this PR: a label trigger runs in PR context, which the sdk-bot
environment restricted to main rules out, so the adaptation workflow
gets its own design later.

Signed-off-by: Roland Huß <rhuss@redhat.com>
@rhuss
rhuss marked this pull request as ready for review November 5, 2026 15:22
@rhuss
rhuss requested review from a team as code owners November 5, 2026 15:22
Comment thread e2e/harness_test.go Resolved
Comment thread e2e/harness_test.go
)

var (
sharedClient *v1.Client

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm assuming that although tests are run in parallel, the TestMain is sequential meaning that this will be valid at the point where the tests start running?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes. TestMain runs before any test starts, and tests (parallel ones included) only start inside m.Run(), so sharedClient and gatewayName are set by then and never written again.

Comment thread e2e/harness_test.go Resolved

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

As a general question: What would it take to be able to run the same "conformance" tests using each of the SDKs? Is this something that we think we can strive towards?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think it's worth aiming for. Today the SDKs share only data: the JSON fixtures in core's sdk/conformance/, which sdk-go vendors and checks against the pinned tag. Sharing behavior would need scenarios in a language-neutral form (steps plus expected results) and a small runner per SDK, or one runner that drives each SDK through a thin CLI shim. Core's openshell-conformance crate already has driver-agnostic scenarios for the CLI, which might be a starting point. It's a cross-SDK design question, so I'd take it to an issue in NVIDIA/OpenShell rather than this PR.

@elezar elezar left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The design is careful: SHA-pinned actions, bot secrets scoped to an environment that only main can use, and --dry-run modes on the scripts. I read the workflows and hack/automation scripts in full. I only skimmed the Go E2E code, and I haven't run anything.

Should fix

  1. open-bump-pr.sh reopens proposals that someone closed on purpose. It skips a tag only if the branch still exists on origin or a PR from it was merged. If a maintainer closes a proposal and deletes the branch, core-watch recreates it on the next hourly run. Add a --state closed check or a marker.
  2. A transient API error looks like a deleted core tag. if ! gh api repos/$core_repo/git/ref/tags/$tag --silent 2>/dev/null treats any failure (rate limit, 5xx) as "tag gone" and runs gh pr close --delete-branch. Only treat a 404 as gone.
  3. tag-release.sh can't recover from a half-finished release. It creates the tag, then the release. If the release step fails, a re-run sees "tag already points at $sha" and exits 0, so the release is never created. Check for the release separately.
  4. Secret name mismatch. The description says SDK_BOT_APP_ID, but all three bot workflows read secrets.SDK_BOT_CLIENT_ID.

Worth considering

  1. tag-release.sh only diffs $sha^..$sha. A rebase-merge, or several commits in one push, can miss a pin change that isn't the tip. Fine if squash-only is enforced; otherwise worth documenting.
  2. The E2E path filter in ci.yml omits .github/workflows/e2e.yml, mise.toml and go.mod.
  3. start-gateway.sh / stop-gateway.sh assume a throwaway CI host:
    • The header says stop "undoes everything", but it never removes gateways/e2e or active_gateway.
    • It docker rm -fs every container labelled openshell.sandbox_namespace=openshell.
    • Only the supervisor image pin is verified; the sandbox sed has no check.
    • The readiness wait only checks the gateway manifest, not the supervisor and sandbox images.
  4. report-failure.sh and update-drift-issue.sh read-modify-write the issue body from different concurrency groups, so a failure line can be lost. Low probability.
  5. The uncovered-RPC grep (\.${method}\() matches generic names such as .List( and .Delete(, so it under-reports. It's informational only.
  6. Several open stable proposals branch from the same main; merging an older one after a newer one moves the pin backwards.
  7. persist-credentials: true in core-watch leaves the write-scoped App token in .git/config for the rest of the job. Acceptable, but worth knowing.

Looks good

core-tags.sh version ordering, the port-pr.sh word-splitting fix, the shared fixture list in mise.toml, and inputs passed via env: rather than interpolated into run:.

@rhuss

rhuss commented Nov 5, 2026

Copy link
Copy Markdown
Contributor Author

@SDAChess #6 is ready for review. I rebased it onto main after #5 was squash-merged. The code is identical to the version you saw yesterday, including the sdk-bot environment changes from 8cda85b.

CODEOWNERS sends .github/ and hack/automation/ to @ai-openshell/infra, so this needs an infra approval.

After it merges, I'll do two one-off dispatches:

  1. release-tag.yml at 7cc0b94f (the feat!: make the SDK a standalone module at github.com/ai-openshell/sdk-go #4 merge commit), to tag SDK v0.1.7 to match core v0.1.7.
  2. core-watch.yml with the newest v0.1.8 pre-release, which should open the first update PR. That is also the first run that uses the App token, so it shows whether the App is installed on sdk-go.

If both runs pass, I'll close #2.

rhuss added 7 commits November 5, 2026 17:59
TestMain now delegates to run(m), so the shared client is closed by one
deferred call instead of before every os.Exit.

Signed-off-by: Roland Huß <rhuss@redhat.com>
The helper skips the test when no gateway is configured; it doesn't
assert. The require prefix stays with the helpers that fail the test.

Signed-off-by: Roland Huß <rhuss@redhat.com>
open-bump-pr.sh skipped a tag only when its branch existed or a PR from
it was merged. A proposal closed by a maintainer, or closed as superseded
by a newer pre-release (which deletes the branch), came back on the next
hourly run. Skip the tag when any PR from the branch exists.

The core tag lookup treated every failure as a deleted tag and closed the
open proposal. Only a 404 means the tag is gone now; other errors fail the
run, which reports to the drift issue.

Signed-off-by: Roland Huß <rhuss@redhat.com>
tag-release.sh created the tag and then the release. When the release
step failed, a re-run saw the tag at the commit and stopped, so the
release was never created. Check the tag and the release separately and
create whichever is missing; API errors other than 404 fail the run.

It also decided by diffing only the pushed commit against its parent, so
a push of several commits or a rebase merge could miss a pin change that
was not the tip. The push workflow now passes the commit before the push
as --since, and the script compares the pins at both commits.

Signed-off-by: Roland Huß <rhuss@redhat.com>
The path filter now also matches .github/workflows/e2e.yml, mise.toml,
go.mod, and go.sum.

Signed-off-by: Roland Huß <rhuss@redhat.com>
start-gateway.sh verified only the supervisor image pin; it now checks
the sandbox image pin too. stop-gateway.sh removes the "e2e" gateway
registration and the active-gateway pointer when it still names it. The
header no longer claims stop undoes everything and says the scripts are
for throwaway CI hosts, since the sandbox cleanup matches core's default
namespace label.

Signed-off-by: Roland Huß <rhuss@redhat.com>
The App was registered as ai-openshell-sdk-go, following the repository
naming convention.

Signed-off-by: Roland Huß <rhuss@redhat.com>
@rhuss

rhuss commented Nov 5, 2026

Copy link
Copy Markdown
Contributor Author

@elezar thanks for the careful pass. Changes are in 0617220..de002ec.

Should fix

  1. 261776b: a tag is skipped when any PR from its branch exists, in any state. This also covers a case the review didn't name: a pre-release proposal closed as superseded (branch deleted) would have come back on the next hourly run.
  2. 261776b: only an HTTP 404 counts as a deleted tag. Any other error fails the run and lands in the drift issue.
  3. 2386235: the tag and the release are checked separately and the missing one is created, so a re-run finishes a half-done release.
  4. Fixed in the description: the secrets are SDK_BOT_CLIENT_ID and SDK_BOT_PRIVATE_KEY.

Worth considering
5. Fixed rather than documented, since the repo allows rebase and merge commits (2386235): the push workflow passes github.event.before as --since, and the script compares the pins at both commits.
6. 5ac6985.
7. 7695dc3: the header says the scripts are for throwaway CI hosts and why; stop removes the e2e registration and the active-gateway pointer when it still names e2e; the sandbox image pin is checked like the supervisor's. The label-based cleanup and the gateway-only image wait stay. If a fresh tag's supervisor or sandbox image lags, the E2E run fails and can be re-run.
8. Leaving as is. A lost line needs two writers within seconds, and the run stays in the Actions history.
9. Agreed, it under-reports. A planned follow-up adds a filter list for RPCs an SDK never calls; I'll fix the matching there.
10. Two proposals change the same tag: line in buf.gen.yaml and the same generated files, so merging an older one after a newer one stops at a merge conflict instead of moving the pin silently.
11. Agreed. open-bump-pr.sh pushes with plain git push, so git needs the token. The job runs only on main through the sdk-bot environment and doesn't check out PR code.

Separately, de002ec renames the App in the automation README to ai-openshell-sdk-go, the name it was registered under.

The fixes came from an agent-assisted pass. Checked locally: mise run ci, workflows:check, dry runs of tag-release.sh (including the planned v0.1.7 dispatch) and open-bump-pr.sh v0.1.8-pre.2, and the non-404 error paths with an invalid token. CI, including E2E, is green on de002ec.

@elezar elezar left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Checked the updates at de002ec against my earlier review: the four must-fix items (closed proposals reopening, non-404 errors treated as a deleted tag, half-finished releases, secret name) are fixed, and the optional items are fixed or reasonably declined. Thanks for the quick turnaround.

@rhuss
rhuss merged commit 73ef0c2 into main Nov 6, 2026
9 checks passed
@rhuss
rhuss deleted the ci/automation branch November 6, 2026 12:18
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.

4 participants