diff --git a/.github/workflows/govulncheck.yaml b/.github/workflows/govulncheck.yaml index 6422d54dc1..4b5cff069b 100644 --- a/.github/workflows/govulncheck.yaml +++ b/.github/workflows/govulncheck.yaml @@ -16,7 +16,8 @@ name: govulncheck on: push: branches: - - main + - giantswarm + pull_request: schedule: - cron: "37 4 * * 1" diff --git a/.github/workflows/helm-e2e.yaml b/.github/workflows/helm-e2e.yaml index dfc5df2757..ec7646477c 100644 --- a/.github/workflows/helm-e2e.yaml +++ b/.github/workflows/helm-e2e.yaml @@ -16,7 +16,7 @@ name: helm-e2e on: pull_request: push: - branches: [main] + branches: [giantswarm] permissions: contents: read jobs: diff --git a/.github/workflows/pr-workflow.yaml b/.github/workflows/pr-workflow.yaml index b0a1e30690..687b32d63b 100644 --- a/.github/workflows/pr-workflow.yaml +++ b/.github/workflows/pr-workflow.yaml @@ -16,7 +16,7 @@ name: pr-workflow on: pull_request: push: - branches: [main] + branches: [giantswarm] schedule: # Weekly run on the default branch keeps the micro-VM asset cache warm (GitHub # evicts caches idle for 7 days). The push-to-main run populates the cache that diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml new file mode 100644 index 0000000000..0cb0f1fdeb --- /dev/null +++ b/.github/workflows/publish.yaml @@ -0,0 +1,251 @@ +# Copyright 2026 The Agent Substrate Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Fork (giantswarm/substrate): publishes the Giant Swarm line of Agent +# Substrate to ghcr.io/giantswarm/substrate — every push to the `giantswarm` +# branch as a dev build, every `v*` tag as a release (FORK.md, "Publishing"). +# What is published: the six images the agent platform runs (ateapi, +# atecontroller, atelet, atenet, podcertcontroller, ateom-gvisor; multi-arch, +# built with ko like upstream's release.yaml), a digest-true mirror of the +# agentgateway image the chart deploys, and the charts substrate-crds and +# substrate with their image defaults stamped to this registry. Every own +# image is scanned before the charts that reference it are pushed. +name: publish + +on: + push: + branches: + - giantswarm + tags: + - "v*" + workflow_dispatch: + inputs: + version: + description: 'Version to publish (default: the tag without "v", or a dev version derived from the commit)' + required: false + +permissions: + contents: read + packages: write + +env: + REGISTRY: ghcr.io/${{ github.repository }} + UPSTREAM_REPO: kagent-dev/substrate + COMPONENTS: ateapi atecontroller atelet atenet podcertcontroller ateom-gvisor + +jobs: + version: + runs-on: ubuntu-latest + outputs: + version: ${{ steps.version.outputs.version }} + steps: + - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0 + with: + fetch-depth: 0 + fetch-tags: true + - name: Resolve the version + id: version + env: + INPUT_VERSION: ${{ inputs.version }} + run: | + set -o errexit -o nounset -o pipefail + if [ -n "${INPUT_VERSION}" ]; then + version="${INPUT_VERSION}" + elif [ "${GITHUB_REF_TYPE}" = tag ]; then + version="${GITHUB_REF_NAME#v}" + else + # Dev version in the schema giantswarm's gitsemver emits today: + # -dev....h + # base = the next patch of the pin, the nearest upstream release tag + # reachable from the branch (the line's own tags carry a + # pre-release suffix and are excluded). Consumers select the channel + # with a Flux semverFilter on the branch part + # (`.*-dev\.giantswarm\..*`); the timestamp keeps semver order across + # rebuilds. When gitsemver moves to the RFC schema + # `-btc`, change this + # block and the consumers' filter together. + git fetch --quiet "https://github.com/${UPSTREAM_REPO}.git" 'refs/tags/v*:refs/tags/v*' + pin=$(git describe --tags --abbrev=0 --match 'v[0-9]*' --exclude '*-*' HEAD) + IFS=. read -r major minor patch <<<"${pin#v}" + base="${major}.${minor}.$((patch + 1))" + branch=$(printf '%s' "${GITHUB_REF_NAME}" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9-]/-/g') + stamp=$(TZ=UTC git show -s --format=%cd --date=format-local:%Y-%m-%d.%H-%M-%S "${GITHUB_SHA}") + version="${base}-dev.${branch}.${stamp}.h${GITHUB_SHA:0:7}" + echo "pin ${pin} -> dev base ${base}" + fi + if ! [[ "${version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then + echo "::error::'${version}' is not a semver version" + exit 1 + fi + echo "version=${version}" | tee -a "$GITHUB_OUTPUT" + + images: + needs: version + runs-on: ubuntu-latest + env: + VERSION: ${{ needs.version.outputs.version }} + steps: + - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0 + - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 + with: + go-version-file: go.mod + - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - uses: imjasonh/setup-crane@feee3b6bb0d4c68370f256a4502498c9227e5c6b # v0.7 + - name: Build and push the component images + # ko cross-compiles both platforms (no QEMU) onto the distroless base + # .ko.yaml pins; --image-refs records the pushed reference with its + # digest for the scan and the ledger. + run: | + set -o errexit -o nounset -o pipefail + : > image-refs.txt + for component in ${COMPONENTS}; do + KO_DOCKER_REPO="${REGISTRY}/${component}" ./hack/run-tool.sh ko build \ + --bare --platform linux/amd64,linux/arm64 \ + --tags "${VERSION}" --image-refs "ref-${component}.txt" \ + "./cmd/${component}" + cat "ref-${component}.txt" >> image-refs.txt + done + - name: Mirror the agentgateway image the chart deploys + # atenet-router and atenet-egress run upstream's agentgateway build + # (charts/substrate/values.yaml images.agentgateway), an image this + # repository does not build. Copied by digest, under its upstream tag, + # so the chart published below deploys only images of this registry. + run: | + set -o errexit -o nounset -o pipefail + src=$(sed -n 's/^ agentgateway: //p' charts/substrate/values.yaml) + test -n "${src}" + dst="${REGISTRY}/agentgateway:${src##*:}" + crane copy "${src}" "${dst}" + echo "${dst}@$(crane digest "${dst}")" >> image-refs.txt + - name: Record the digests + run: | + { + echo "## Images ${VERSION}" + echo + echo '| Image | Digest |' + echo '|---|---|' + sed -E 's/^([^@]+)@(sha256:[0-9a-f]+)$/| `\1` | `\2` |/' image-refs.txt + echo + } >> "$GITHUB_STEP_SUMMARY" + cat image-refs.txt + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: image-refs + path: image-refs.txt + if-no-files-found: error + + scan: + needs: + - version + - images + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + image: + - ateapi + - atecontroller + - atelet + - atenet + - podcertcontroller + - ateom-gvisor + - agentgateway + env: + # The trivy database mirrors, tried in order (ghcr rate-limits anonymous pulls). + TRIVY_DB_REPOSITORY: public.ecr.aws/aquasecurity/trivy-db:2,ghcr.io/aquasecurity/trivy-db:2 + steps: + - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0 + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: image-refs + - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Pick the image by digest + id: ref + run: | + set -o errexit -o nounset -o pipefail + ref=$(grep -E "^${REGISTRY}/${{ matrix.image }}[:@]" image-refs.txt | tail -1) + test -n "${ref}" + echo "ref=${ref}" | tee -a "$GITHUB_OUTPUT" + - name: Scan + # Own images gate the charts: a fixable HIGH or CRITICAL finding fails + # the publish (fix it — bump the module, or time-box it in .trivyignore + # with an expiry when upstream has no fix). The mirrored agentgateway + # image is upstream's build and reports only: findings there go to + # kagent-dev/substrate. + uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 + with: + image-ref: ${{ steps.ref.outputs.ref }} + format: table + severity: CRITICAL,HIGH + ignore-unfixed: true + trivyignores: .trivyignore + exit-code: ${{ matrix.image == 'agentgateway' && '0' || '1' }} + + charts: + needs: + - version + - scan + runs-on: ubuntu-latest + env: + VERSION: ${{ needs.version.outputs.version }} + steps: + - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0 + - uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4.3.1 + - name: Package and push the charts + # The chart's image defaults are stamped to this registry and version + # (image.tag explicitly, although the helper falls back to + # .Chart.AppVersion: `helm show values` is documentation); the checks + # make a missed anchor fail the job instead of shipping upstream's + # defaults. The chart version is the artifact version, as upstream does. + run: | + set -o errexit -o nounset -o pipefail + values=charts/substrate/values.yaml + sed -i \ + -e "s|^ registry: ghcr.io/kagent-dev/substrate$| registry: ${REGISTRY}|" \ + -e "s|^ tag: \"\"$| tag: \"${VERSION}\"|" \ + -e "s|^ agentgateway: ghcr.io/kagent-dev/substrate/agentgateway:| agentgateway: ${REGISTRY}/agentgateway:|" \ + "${values}" + grep -q "^ registry: ${REGISTRY}$" "${values}" + grep -q "^ tag: \"${VERSION}\"$" "${values}" + grep -q "^ agentgateway: ${REGISTRY}/agentgateway:" "${values}" + if grep -n "kagent-dev/substrate" "${values}"; then + echo "::error::${values} still references upstream's registry after stamping" + exit 1 + fi + + mkdir -p dist + for chart in substrate-crds substrate; do + helm package "charts/${chart}" --destination dist --version "${VERSION}" --app-version "${VERSION}" + done + echo "${{ secrets.GITHUB_TOKEN }}" | helm registry login ghcr.io --username "${{ github.actor }}" --password-stdin + { + echo "## Charts ${VERSION}" + echo + echo '| Chart | Digest |' + echo '|---|---|' + } >> "$GITHUB_STEP_SUMMARY" + for chart in substrate-crds substrate; do + out=$(helm push "dist/${chart}-${VERSION}.tgz" "oci://${REGISTRY}/helm" 2>&1) + echo "${out}" + digest=$(sed -n 's/^Digest: //p' <<<"${out}") + echo "| \`oci://${REGISTRY}/helm/${chart}:${VERSION}\` | \`${digest}\` |" >> "$GITHUB_STEP_SUMMARY" + done diff --git a/.github/workflows/sync-upstream.yaml b/.github/workflows/sync-upstream.yaml new file mode 100644 index 0000000000..121f9675f7 --- /dev/null +++ b/.github/workflows/sync-upstream.yaml @@ -0,0 +1,193 @@ +# Copyright 2026 The Agent Substrate Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Fork (giantswarm/substrate): keeps the Giant Swarm line of Agent Substrate +# in step with upstream (FORK.md, "Re-pin"). Weekly and on demand it +# 1. mirrors upstream `main` into this fork's `main` — kagent-dev/substrate +# rewrites its main when it rebases onto agent-substrate/substrate, so the +# mirror is a forced update of the one branch nobody edits here; +# 2. probes whether the carried patches of the `giantswarm` branch still +# rebase onto upstream `main` and names the first one that would conflict; +# 3. on `workflow_dispatch` with a `pin`, re-pins the branch: rebases the +# carried patches onto the new upstream tag (a patch upstream has merged +# falls away), builds and tests, and pushes the rebased branch — or, when +# a patch conflicts, pushes what applied before it and opens a pull +# request that names the conflicting patch and the ones behind it. +# Pushes use the taylorbot token: a push with GITHUB_TOKEN triggers no other +# workflow, and the push to `giantswarm` is what publishes the dev build. +name: sync-upstream + +on: + schedule: + - cron: "23 5 * * 1" + workflow_dispatch: + inputs: + pin: + description: "Upstream release tag (or commit) to re-pin the giantswarm branch onto, e.g. v0.0.27. Empty: mirror main and probe the rebase only." + required: false + dry_run: + description: "Do everything except push" + type: boolean + default: false + +permissions: + contents: read + +concurrency: + group: sync-upstream + cancel-in-progress: false + +env: + LINE_BRANCH: giantswarm + UPSTREAM_REPO: kagent-dev/substrate + +jobs: + sync: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0 + with: + ref: giantswarm + fetch-depth: 0 + fetch-tags: true + token: ${{ secrets.TAYLORBOT_GITHUB_ACTION }} + - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 + with: + go-version-file: go.mod + - name: Fetch upstream + run: | + set -o errexit -o nounset -o pipefail + git remote add upstream "https://github.com/${UPSTREAM_REPO}.git" + git fetch --quiet upstream main 'refs/tags/v*:refs/tags/v*' + git config user.name taylorbot + git config user.email dev@giantswarm.io + + - name: Mirror upstream main + env: + DRY_RUN: ${{ inputs.dry_run }} + run: | + set -o errexit -o nounset -o pipefail + fork=$(git rev-parse origin/main) + up=$(git rev-parse upstream/main) + if [ "${fork}" = "${up}" ]; then + echo "- main: at upstream \`${up:0:12}\`" >> "$GITHUB_STEP_SUMMARY" + exit 0 + fi + kind=fast-forward + git merge-base --is-ancestor "${fork}" "${up}" || kind="rewrite — upstream rebased its main" + echo "- main: \`${fork:0:12}\` → \`${up:0:12}\` (${kind})" >> "$GITHUB_STEP_SUMMARY" + if [ "${DRY_RUN}" != true ]; then + git push --force-with-lease="refs/heads/main:${fork}" origin "${up}:refs/heads/main" + fi + + - name: Resolve the pin and the carried patches + id: line + env: + INPUT_PIN: ${{ inputs.pin }} + run: | + set -o errexit -o nounset -o pipefail + # The pin is the nearest upstream release tag reachable from the + # branch; the line's own tags carry a pre-release suffix and are excluded. + current=$(git describe --tags --abbrev=0 --match 'v[0-9]*' --exclude '*-*' HEAD) + new="${INPUT_PIN:-${current}}" + if ! git rev-parse --verify --quiet "${new}^{commit}" >/dev/null; then + echo "::error::pin '${new}' is not a known upstream tag or commit" + exit 1 + fi + { + echo "current=${current}" + echo "new=${new}" + echo "head=$(git rev-parse HEAD)" + } >> "$GITHUB_OUTPUT" + { + echo "- pin: \`${current}\` → \`${new}\`" + echo "- carried patches on \`${LINE_BRANCH}\`:" + git log --reverse --format=' - `%h` %s' "${current}..HEAD" + } >> "$GITHUB_STEP_SUMMARY" + + - name: Probe the rebase onto upstream main + if: steps.line.outputs.new == steps.line.outputs.current + env: + CURRENT: ${{ steps.line.outputs.current }} + HEAD_SHA: ${{ steps.line.outputs.head }} + run: | + set -o errexit -o nounset -o pipefail + up=$(git rev-parse --short upstream/main) + if git rebase --quiet --onto upstream/main "${CURRENT}" >/dev/null 2>&1; then + echo "- probe: the carried patches rebase cleanly onto upstream main \`${up}\`" >> "$GITHUB_STEP_SUMMARY" + else + conflict=$(git rev-parse --short REBASE_HEAD 2>/dev/null || echo unknown) + git rebase --abort + subject=$(git log -1 --format=%s "${conflict}" 2>/dev/null || true) + echo "- probe: rebasing onto upstream main \`${up}\` conflicts at \`${conflict}\` ${subject}" >> "$GITHUB_STEP_SUMMARY" + echo "::warning::carried patch ${conflict} (${subject}) conflicts with upstream main; the next re-pin needs a hand" + fi + git checkout --quiet "${HEAD_SHA}" + + - name: Re-pin + if: steps.line.outputs.new != steps.line.outputs.current + env: + CURRENT: ${{ steps.line.outputs.current }} + NEW: ${{ steps.line.outputs.new }} + HEAD_SHA: ${{ steps.line.outputs.head }} + DRY_RUN: ${{ inputs.dry_run }} + GH_TOKEN: ${{ secrets.TAYLORBOT_GITHUB_ACTION }} + run: | + set -o errexit -o nounset -o pipefail + if git rebase --onto "${NEW}" "${CURRENT}"; then + echo "- re-pin: the carried patches rebased onto \`${NEW}\`" >> "$GITHUB_STEP_SUMMARY" + git log --reverse --format=' - `%h` %s' "${NEW}..HEAD" >> "$GITHUB_STEP_SUMMARY" + go build ./... + go test ./... + if [ "${DRY_RUN}" != true ]; then + git push --force-with-lease="refs/heads/${LINE_BRANCH}:${HEAD_SHA}" origin "HEAD:refs/heads/${LINE_BRANCH}" + echo "- pushed \`${LINE_BRANCH}\` (\`${HEAD_SHA:0:12}\` → \`$(git rev-parse --short HEAD)\`); the publish workflow builds the dev build" >> "$GITHUB_STEP_SUMMARY" + fi + exit 0 + fi + + # A carried patch conflicts: replay the patches before it onto the new + # pin, push that as a hand-over branch and open a pull request that + # names the rest. The pull request is a hand-over, not a merge — the + # line is a rebased branch (FORK.md, "Re-pin"). + conflict=$(git rev-parse REBASE_HEAD) + git rebase --abort + branch="sync/$(date -u +%Y%m%d)-${NEW}" + git branch --force "${branch}" "${conflict}^" + git checkout --quiet "${branch}" + git rebase --quiet --onto "${NEW}" "${CURRENT}" + { + echo "Re-pin of \`${LINE_BRANCH}\` from \`${CURRENT}\` onto \`${NEW}\` stopped at a conflicting carried patch." + echo + echo "Replayed on this branch:" + git log --reverse --format='- `%h` %s' "${NEW}..HEAD" + echo + echo "Conflicting: \`$(git rev-parse --short "${conflict}")\` $(git log -1 --format=%s "${conflict}")" + echo + echo "Not replayed yet (in order):" + git log --reverse --format='- `%h` %s' "${conflict}^..${HEAD_SHA}" + echo + echo "To finish (FORK.md, \"Re-pin\"): check this branch out, \`git cherry-pick -x\` the conflicting patch and the ones behind it, resolve, run \`go build ./... && go test ./...\`, then update the line with \`git push --force-with-lease origin HEAD:${LINE_BRANCH}\` and close this pull request. Do not merge it: the line is a rebased branch, a merge would fold the old pin back in." + } > "${RUNNER_TEMP}/body.md" + { + echo "- re-pin: conflict at \`$(git rev-parse --short "${conflict}")\` $(git log -1 --format=%s "${conflict}")" + } >> "$GITHUB_STEP_SUMMARY" + if [ "${DRY_RUN}" != true ]; then + git push --force origin "HEAD:refs/heads/${branch}" + gh pr create --base "${LINE_BRANCH}" --head "${branch}" \ + --title "sync: re-pin ${LINE_BRANCH} onto ${NEW} (hand-over: a carried patch conflicts)" \ + --body-file "${RUNNER_TEMP}/body.md" >> "$GITHUB_STEP_SUMMARY" + fi + echo "::error::carried patch $(git rev-parse --short "${conflict}") conflicts with ${NEW}; hand-over branch ${branch}" + exit 1 diff --git a/.trivyignore b/.trivyignore new file mode 100644 index 0000000000..7139a3e2d6 --- /dev/null +++ b/.trivyignore @@ -0,0 +1,21 @@ +# Copyright 2026 The Agent Substrate Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Fork (giantswarm/substrate): time-boxed acceptances of image-scan findings +# the line cannot fix itself (publish.yaml scans every own image; a FIXABLE +# HIGH/CRITICAL finding is never listed here — bump the module, upstream first). +# One finding per line, with an expiry and a reason; an expired entry fails the +# scan again and is re-triaged, never just extended: +# +# CVE-2026-NNNNN exp:2026-12-31 # : why no fix exists upstream, what to track, tracked in /#NN diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000000..316dd43b2b --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,2 @@ +# generated by giantswarm/github actions - changes will be overwritten +* @giantswarm/team-bumblebee diff --git a/FORK.md b/FORK.md new file mode 100644 index 0000000000..19cfbf143a --- /dev/null +++ b/FORK.md @@ -0,0 +1,167 @@ +# The Giant Swarm line of Agent Substrate + +This repository is Team Bumblebee's fork of [kagent-dev/substrate](https://github.com/kagent-dev/substrate) +(itself a fork of [agent-substrate/substrate](https://github.com/agent-substrate/substrate)). The Giant Swarm +Agent Platform's kagent (API v2) runs every agent as a Substrate actor; this fork is where the platform's +Substrate is pinned, built, scanned and published. It answers one question from one place: **which Substrate +are we running, and why does it differ from upstream?** + +Tracking (both upstream repositories and this fork have issues disabled): the line +[giantswarm/giantswarm#37757](https://github.com/giantswarm/giantswarm/issues/37757), upstream engagement +[giantswarm/giantswarm#37742](https://github.com/giantswarm/giantswarm/issues/37742) (Substrate rows), the +kagent line built the same way [giantswarm/giantswarm#37010](https://github.com/giantswarm/giantswarm/issues/37010) +(`giantswarm/kagent-upstream`), the epic [giantswarm/giantswarm#37705](https://github.com/giantswarm/giantswarm/issues/37705). + +## Branches + +| Branch | What it is | Who moves it | +|---|---|---| +| `main` | A pure mirror of upstream `main`. Upstream rebases its `main` onto agent-substrate (no release tag is an ancestor of it), so the mirror is a **forced** update. Never edited, never the target of a pull request. | the sync workflow (taylorbot) only — the `protect-main` ruleset admits the `bots` team and nobody else | +| `giantswarm` (default) | **The line**: the upstream release tag the platform's kagent pins ("the pin") + cherry-picked upstream fixes + this fork's own files. Every change of the fork's own files is a pull request against it. | pull requests (`run-tests` and `govulncheck` required); the sync workflow and repository admins may force-push it for a re-pin | +| `fork/` | pull-request branches against `giantswarm` | anyone in the team | +| `sync/-` | hand-over branches the sync workflow opens when a re-pin conflicts | the sync workflow; a human finishes them | + +## Pin + +| | | +|---|---| +| Upstream tag | **v0.0.26** (2026-09-06; tag commit `0cb6a535`) | +| Why this one | `giantswarm/kagent-upstream` pins `github.com/kagent-dev/substrate v0.0.26` in `go/go.mod` (the `replace` of `github.com/agent-substrate/substrate`): the ate-api gRPC contract between kagent's client and Substrate's server must match. | +| When it moves | only together with kagent's pin, proven in agentlab first (`agentlab configure --defaults --chart-branch poc/kagent-main && agentlab up` and the proofs) — see "Re-pin". Not on a schedule. | +| Derived how | `git describe --tags --abbrev=0 --match 'v[0-9]*' --exclude '*-*' giantswarm` with upstream's tags fetched; the line's own tags carry a pre-release suffix and are excluded. The workflows compute it, nothing records it twice. | + +## Carried patches + +Everything on `giantswarm` that is not in the pin (`git log v0.0.26..giantswarm`): + +| Patch | Purpose | Fork commit | Upstream | +|---|---|---|---| +| Grant atelet cluster-wide read access to sandbox configs | atelet's sandbox-asset prewarm degraded on the second test cluster without the RBAC ([#37742](https://github.com/giantswarm/giantswarm/issues/37742) row 10) | `74b45f9e` (`git cherry-pick -x a7505e9c`) | [kagent-dev/substrate#33](https://github.com/kagent-dev/substrate/pull/33), merged 2026-09-08, not in v0.0.26 — falls away at the re-pin onto the first tag that contains it | +| Fork infrastructure: this file, the README pointer, `CODEOWNERS`, `.github/workflows/publish.yaml`, `.github/workflows/sync-upstream.yaml`, `.trivyignore`, and the branch triggers of `pr-workflow.yaml`, `helm-e2e.yaml`, `govulncheck.yaml` (`main` → `giantswarm`, govulncheck also on pull requests) | the line's CI, publishing and sync | the `giantswarm` branch history | not for upstream | + +Nothing in the line changes Substrate's behaviour beyond what upstream has already merged. Giant Swarm +specific wiring lives elsewhere: the CA/JWT pool bootstrap (`kubectl-ate admin make-ca-pool`/`make-jwt-pool` +and the `ate-api-authentication` ConfigMap) is created by [agentlab](https://github.com/giantswarm/agentlab) +and by meta chart 4.0; the `WorkerPool` the platform's Harnesses run on comes with the kagent chart +(`kagent.substrateWorkerPool`); feature gates, Kyverno exceptions and network policies are cluster +configuration. + +Dropped at the bootstrap of the line (2026-09-10): the three June commits of the old fork `main` — `6564754e` +enable websockets (upstream has it: `cmd/atenet/internal/router/xds.go` `UpgradeConfigs`), `5169cdd4` +ActorTemplate env refs (upstream kagent-dev/substrate#20, merged), `63ea2b0e` a dispatch-only release workflow +that never ran (upstream ships `release.yaml`) — and the eight stale June branches whose patches upstream has +merged. + +## Re-pin + +The re-pin moves the line onto a new upstream release tag and replays the carried patches; a patch upstream has +merged falls away by itself (`git rebase` drops already-applied patches). It is the one sanctioned rewrite of +`giantswarm`. + +1. kagent first: the new pin is whatever `giantswarm/kagent-upstream`'s `go/go.mod` `replace` names after its own + re-pin. Do not move Substrate ahead of kagent — the ate-api contract is versioned by that pin. +2. Run **Actions → sync-upstream → Run workflow** with `pin` = the tag (for example `v0.0.27`). The workflow + mirrors `main`, rebases the carried patches onto the tag, runs `go build ./... && go test ./...`, and + force-pushes `giantswarm`. The push runs upstream's suites (`pr-workflow`, `helm-e2e`, `govulncheck`) and + `publish` builds the dev build. + - On a conflict it pushes `sync/-` (the new tag + the patches that applied before the conflict) and + opens a pull request that names the conflicting patch and the ones behind it. Finish it by hand: check the + branch out, `git cherry-pick -x` the rest, resolve, test, `git push --force-with-lease origin HEAD:giantswarm`, + close the pull request. **Do not merge it** — the line is a rebased branch; a merge would fold the old pin back in. + - `dry_run: true` does everything except the pushes; the run summary shows the outcome. +3. Update this file (pin, carried patches) in a pull request, and the Substrate rows of #37742. +4. Move the consumers to the new dev version (see "Consumers"), prove it in agentlab, then let the meta chart's + pin and kagent-upstream follow. + +The weekly run (Mondays 05:23 UTC) does not re-pin: it mirrors `main` and **probes** whether the carried patches +still rebase onto upstream `main`, naming the first patch that would conflict in the run summary, so the next +re-pin is never a surprise. + +Manual equivalent (a workstation, upstream as a remote): + +```sh +git fetch upstream main --tags +git checkout giantswarm +git rebase --onto v0.0.27 v0.0.26 # new pin, old pin +go build ./... && go test ./... +git push --force-with-lease origin giantswarm +``` + +## Publishing + +`publish.yaml` publishes to `ghcr.io/giantswarm/substrate` on every push to `giantswarm` and on every `v*` tag; +nothing is ever pushed by hand. + +| Artifact | Name | +|---|---| +| Control plane and node images | `ghcr.io/giantswarm/substrate/{ateapi,atecontroller,atelet,atenet,podcertcontroller}:` — linux/amd64 + linux/arm64, built with ko from `./cmd/` on the distroless base `.ko.yaml` pins | +| Worker image | `ghcr.io/giantswarm/substrate/ateom-gvisor:` — the `WorkerPool.spec.workerImage` of the platform's pool | +| agentgateway | `ghcr.io/giantswarm/substrate/agentgateway:` — a digest-true `crane copy` of upstream's `images.agentgateway` (atenet-router and atenet-egress run it; this repository does not build it) | +| Charts | `oci://ghcr.io/giantswarm/substrate/helm/substrate-crds:`, `oci://ghcr.io/giantswarm/substrate/helm/substrate:` — `image.registry`, `image.tag` and `images.agentgateway` stamped to this registry; `version` = `appVersion` = the image tag | + +Not published from here: `ateom-microvm` and the demo images (the platform runs gVisor workers only), +`kubectl-ate` binaries (use upstream's release), PyPI packages. Third-party images stay as upstream pins them +(`postgres`, `rustfs`, `amazon/aws-cli`, `coredns`, `busybox`). + +**Versions.** + +- Dev build, on every push to `giantswarm`: `-dev.giantswarm...h` + (for the pin v0.0.26: `0.0.27-dev.giantswarm.…`), the schema the kagent line uses — base = the pin's patch + 1, + branch lowercased to `[a-z0-9-]`, committer date in UTC, so a rebuild of the same commit yields the same version + and versions sort chronologically within the branch. Consumers that follow the channel use a Flux + `OCIRepository` with `semver: ">=0.0.27-0 <0.1.0-0"` and `semverFilter: ".*-dev\.giantswarm\..*"`; exact pins + name the full string. +- Release, on a tag `vX.Y.Z-gs.N` where `X.Y.Z` is upstream's **next** version (the dev base) and `N` counts the + line's releases of that pin: `v0.0.27-gs.1`. Ordering by semver: `0.0.27-dev.… < 0.0.27-gs.1 < 0.0.27`, so a dev + build never outranks a release, a fork release never outranks the upstream version it anticipates, and the + switch to an upstream tag one day is a range change, not a rename. A fleet consumer follows + `semverFilter: ".*-gs\..*"`. +- `workflow_dispatch` with a `version` input publishes that string (for a one-off). + +**Digests.** Every run writes an `Images`/`Charts` table with the digest of each pushed artifact to its summary +and uploads them as the `image-refs` artifact; the platform pins by tag and verifies by digest from there. Release +digests are recorded here: + +| Release | Pin | Images and charts | +|---|---|---| +| none yet | | | + +**Scans.** Every own image is scanned with Trivy (HIGH and CRITICAL, fixable only) after the push and before the +charts that reference it are published. A fixable finding fails the publish: bump the module (upstream first) or, +when upstream has no fix, add a time-boxed entry to `.trivyignore` (`CVE-… exp:YYYY-MM-DD # reason, tracking +issue`) — an expired entry fails again and is re-triaged, not extended. The mirrored agentgateway image is +scanned report-only; its findings belong upstream. `govulncheck` covers the Go module graph on every push, pull +request and weekly. + +## Consumers + +| Consumer | Where the pin lives | Selects | +|---|---|---| +| [agentlab](https://github.com/giantswarm/agentlab) | `internal/lab/substrate.go` (`substrateChartsRepo`, `substrateImageRegistry`, `substrateVersion`) | an exact dev version or release; installs `substrate-crds` + `substrate` and preloads the worker image | +| agent-platform meta chart, branch `poc/kagent-main` | `helm/agent-platform/values.yaml` `kagent.substrateWorkerPool.workerImage` | the `ateom-gvisor` image at an exact version (the `WorkerPool` the kagent chart renders) | +| [giantswarm/kagent-upstream](https://github.com/giantswarm/kagent-upstream) | `Makefile` `SUBSTRATE_REPO ?= oci://ghcr.io/giantswarm/substrate/helm`, `SUBSTRATE_VERSION` | the `substrate`/`substrate-crds` chart dependencies of the kagent charts (off in the platform, which installs Substrate as cluster infrastructure) | + +## Assets that are not images + +- **gVisor `runsc`**: the chart's `SandboxConfig gvisor-default` (`charts/substrate/templates/sandboxconfig-gvisor.yaml`) + names `gs://gvisor/releases/release/20260803/{x86_64,aarch64}/gvisor.tar.bz2`; atelet downloads the release + tarball at prewarm. Unchanged from upstream and not mirrored; a cluster needs egress to + `storage.googleapis.com` from the atelet pods (or an override of `spec.assets` on the SandboxConfig). +- **micro-VM assets** (kata, cloud-hypervisor, virtiofsd): assembled by `hack/microvm-assets/assemble.sh` for the + e2e suites; the platform does not run the micro-VM sandbox class. + +## Contributing + +- **Upstream first.** Every behavioural change is a pull request to + [kagent-dev/substrate](https://github.com/kagent-dev/substrate) (issues are disabled there; discussion goes + through the pull request or the kagent community channels) with DCO sign-off (`git commit -s`); pull-request + workflows from forks wait for a maintainer's approval. The line carries the same change as a + `git cherry-pick -x` of the upstream commit (or, before merge, of your pull-request branch) until an upstream + release contains it, with a row in #37742. +- **Fork-only changes** (workflows, this file): a pull request from `fork/` against `giantswarm`. +- **Experiments**: your own personal fork. Branches here exist to become pull requests. +- **What CI runs on a pull request**: upstream's `pr-workflow` (unit, root-gated and e2e suites on kind, gVisor and + micro-VM lanes), `helm-e2e` (the charts on kind) and `govulncheck`; `run-tests` and `govulncheck` are required. + `publish` runs only on the branch and on tags. +- **Do not** dispatch upstream's `release.yaml` here (it is the mirror's file; it would push to this registry under + an arbitrary tag and try to push charts to upstream's), and do not push tags other than `vX.Y.Z-gs.N` releases. diff --git a/README.md b/README.md index dfb13f7fe9..0ae046289e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,10 @@ +> **Giant Swarm line.** This repository is Team Bumblebee's fork of +> [kagent-dev/substrate](https://github.com/kagent-dev/substrate) for the Giant +> Swarm Agent Platform: `main` mirrors upstream, the `giantswarm` branch is what +> the platform runs and publishes (`ghcr.io/giantswarm/substrate`). Pin, +> carried patches, published artifacts, the re-pin procedure and how to +> contribute: [FORK.md](FORK.md). + # Agent Substrate [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)