Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
315 changes: 186 additions & 129 deletions .github/workflows/ci.yml

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion .github/workflows/eng-docs-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# deploy.ts, SEA-1765). This is a STANDALONE CD workflow — it is deliberately
# NOT part of `moon ci :ci` (the CI gate in ci.yml), so a deploy failure never
# gates a merge and a green CI never waits on Cloudflare.
name: eng-docs-deploy
name: eng-docs

on:
push:
Expand All @@ -22,6 +22,7 @@ concurrency:

jobs:
deploy:
name: deploy
runs-on: ubuntu-latest
# Fork guard: a push always runs; a PR only runs when it comes from a branch
# in THIS repo. Fork PRs get no secrets (CLOUDFLARE_*/GH_TOKEN), so a deploy
Expand Down
121 changes: 121 additions & 0 deletions .github/workflows/pr-base-repoint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# Base-re-point re-trigger for stacked PRs.
#
# ci.yml deliberately does NOT listen to `pull_request.edited`: that event
# fires on every title/body edit (jj-vine edits every PR's body ~1s after
# opening it), and the phantom run it produced self-skipped and double-listed
# every check on the PR — a cosmetic mess with a latent
# skipped-required-as-passing false green. But `edited` is ALSO the only event
# fired when a stacked PR's BASE branch is re-pointed (the PR below it
# merges/closes and GitHub re-points this PR at the new base). That case needs
# a fresh CI run against the recomputed merge commit; a plain `gh run rerun`
# only replays the frozen stale merge SHA (actions/checkout#919), which is the
# exact phantom red `edited` was once added to fix.
#
# So `edited` lives HERE, narrowly: this workflow guards on `changes.base` so
# it is a no-op for every title/body edit, and on a real base re-point it
# re-triggers ci.yml out of band. The re-trigger is a `workflow_dispatch`
# carrying the PR number, NOT a label bounce or a close/reopen: an event
# created with the default `GITHUB_TOKEN` triggers no new workflow run (the
# sole exceptions are `workflow_dispatch` and `repository_dispatch`), so
# `workflow_dispatch` is the one re-trigger that needs no minted App token.
# ci.yml's dispatch arm checks out `refs/pull/<pr>/merge`, which GitHub
# recomputes against the new base for a fresh run, and runs the full battery
# against it.

name: pr-base-repoint

on:
pull_request:
types: [edited]

# Least privilege: the one thing this workflow does is dispatch ci.yml, which
# needs `actions: write`. Nothing else.
permissions:
actions: write

# One re-trigger in flight per PR. A second base re-point supersedes the first;
# the older dispatch is answering a question about a base that no longer holds.
concurrency:
group: pr-base-repoint-${{ github.event.pull_request.number }}
cancel-in-progress: true

jobs:
repoint:
name: guard
runs-on: ubuntu-latest
# Fire ONLY on a real base re-point, and ONLY for a same-repo PR. `edited`
# sets `github.event.changes.base` only when the base actually moved, so a
# title/body edit is `null` and skips here — the no-op the phantom-removal
# depends on. The fork guard is a hard boundary: a fork PR's token is
# read-only (it could not dispatch anyway) and must never be handed
# `actions: write`; a fork's own CI already runs from its fork, and a fork
# base re-point is vanishingly rare, so skipping it costs nothing.
if: >-
github.event.changes.base != null &&
github.event.pull_request.head.repo.full_name == github.repository
timeout-minutes: 5
steps:
- name: Re-trigger ci.yml against the recomputed merge ref
id: dispatch
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number }}
HEAD_REF: ${{ github.event.pull_request.head.ref }}
# Dispatch ci.yml with `pr=<number>`; ci.yml's gate jobs recognise a
# non-empty `pr` as a base-re-point re-trigger (an empty `pr` is the
# separate operator-only fixture-regen lane) and check out
# `refs/pull/<pr>/merge`. `--ref` is the PR's HEAD branch: a dispatch
# needs a ref that carries the workflow, and the head branch is where a
# stacked PR lives; ci.yml's concurrency key keys the dispatch run to
# that head ref, distinct from the PR's own `pull_request` runs.
#
# Record a UTC timestamp taken JUST BEFORE the dispatch. The verify
# step below anchors its poll to runs created after this instant, so it
# confirms THIS dispatch's run — never a pre-existing queued run on the
# same head branch (a rapid double re-point), which would otherwise let
# the verify pass even if this dispatch silently produced nothing.
run: |
echo "since=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >>"$GITHUB_OUTPUT"
gh workflow run ci.yml --ref "$HEAD_REF" -f pr="$PR_NUMBER"

- name: Verify a fresh ci.yml run started
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
HEAD_REF: ${{ github.event.pull_request.head.ref }}
PR_NUMBER: ${{ github.event.pull_request.number }}
SINCE: ${{ steps.dispatch.outputs.since }}
# "Loud to a log" is not loud to the gate: if the dispatch silently
# produced no run, the PR would keep showing its green stale checks and
# nobody would know the re-point went untested. So poll the runs API for
# a `workflow_dispatch` ci.yml run on this head branch created at or
# after the dispatch instant, and fail this guard RED if none appears
# within the window. The guard being red is itself the signal the
# operator sees on the PR.
run: |
# Match a workflow_dispatch ci.yml run on our head branch whose
# createdAt is >= the instant we dispatched (SINCE), so a pre-existing
# run from an earlier re-point can never satisfy this check — only a
# run this step actually caused. Poll up to ~2min for run-creation lag.
deadline=$(( SECONDS + 120 ))
found=0
while [ "$SECONDS" -lt "$deadline" ]; do
n=$(gh run list \
--workflow ci.yml \
--branch "$HEAD_REF" \
--event workflow_dispatch \
--limit 10 \
--json status,createdAt \
--jq '[.[] | select(.createdAt >= env.SINCE)] | length')
if [ "${n:-0}" -gt 0 ]; then
found=1
break
fi
sleep 10
done
if [ "$found" != 1 ]; then
echo "::error::dispatched ci.yml for PR #$PR_NUMBER on $HEAD_REF but no fresh workflow_dispatch run created at/after $SINCE appeared within the window — the base re-point was NOT re-tested; investigate before trusting this PR's checks"
exit 1
fi
echo "fresh ci.yml workflow_dispatch run confirmed for $HEAD_REF (created >= $SINCE)"
4 changes: 2 additions & 2 deletions .github/workflows/publish-agent-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
# the pull side. See docs/architecture/build-and-ci.md and the design record
# docs/designs/infra/ci/compass-agent-image-publish/design.md.

name: Publish agent image
name: agent-image

on:
push:
Expand Down Expand Up @@ -79,7 +79,7 @@ concurrency:

jobs:
publish:
name: Publish
name: publish
runs-on: ubuntu-latest
# workflow_dispatch runs on any branch; guard so a dispatch from a feature
# branch can never mint a `:git-<sha>` for unmerged code nor move `:latest`
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/renovate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
# secret surface. The App's Workflows permission is LOAD-BEARING — the
# github-actions manager pushes commits under .github/workflows/, which a GitHub
# App may only do with that permission (see the T8 human-action runbook).
name: Renovate
name: renovate

on:
schedule:
Expand Down Expand Up @@ -40,6 +40,7 @@ concurrency:

jobs:
renovate:
name: run
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand Down
5 changes: 0 additions & 5 deletions .moon/workspace.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,6 @@ projects:
# The toolchain version-parity gate: asserts CI's PATH holds the dev shell's
# toolchain, and carries the unit tests for its own comparison logic.
toolchain-parity: 'tools/toolchain'
# The devenv-CLI source tool: resolves the devenv CLI source (flakeref or a
# single-binary PATH shim) from a named devenv.lock, so renovate.yml and
# ci.yml share one lock-tracking resolver instead of hand-pinning a rev
# (RIG-2546). Carries its own unit tests for the pure resolution half.
devenv-cli: 'tools/toolchain/devenv-cli'
# The generator-stamp gate: asserts the checked-in gen trees' `@generated by`
# headers agree with each other and with the nixpkgs protoc-gen-es on PATH
# (SEA-1405). Separate from compass-proto because its subject is the plugin
Expand Down
4 changes: 2 additions & 2 deletions app-bundle/SMOKE.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ board → one agent session to a real container — end to end.

## What the automated gates cover

- **`dogfood-e2e`** stands up a real headless stack (`compass-stack` /
- **`ci / e2e`** stands up a real headless stack (`compass-stack` /
`compass-postgres` / a podman-run agent container) and drives it end to end on
every PR. That is the stack-side regression check; a green PR has already
proven the headless bring-up.
Expand Down Expand Up @@ -168,7 +168,7 @@ rm -rf "$STATE" "$RT"
## Manual checklist

What a human confirms on the dev box (the headless bring-up itself is the
`dogfood-e2e` gate's job — not on this list):
`ci / e2e` gate's job — not on this list):

- [ ] client `app.toml` is client-only (`mode = "client"`, https `server_url`,
`ca_cert` = the stack's `tls.crt`); no token in it (§2)
Expand Down
16 changes: 6 additions & 10 deletions go/internal/stack/postgres_image.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,10 @@ package stack
// major/minor-version-sensitive, and a mutable tag would ship an unreviewed
// database under the installed stack.
//
// Bump procedure: a DIGEST bump within postgres:18 (an upstream security rebuild,
// same major) is automated — Renovate surfaces this const as a docker dep via a
// customManager in tools/renovate/config.json5 (depName `postgres-stack`) and
// opens a reviewable PR to advance the digest. A MAJOR bump (18 -> 19) is frozen
// by DL-260 for on-disk-format stability and stays manual: the customManager's
// packageRule pins allowedVersions to /^18$/, so 18 -> 19 is never auto-proposed.
// When you do move the major deliberately, re-run the T8 container integration
// test (up -> probe DSN -> fresh-process down -> container gone) against the new
// digest before landing, and keep the major aligned with pgtest.go's pin
// discipline so a dev-box stack and an installed stack never skew on-disk format.
// Bump procedure: advance the digest below when the postgres minor/major moves,
// then re-run the T8 container integration test (up -> probe DSN -> fresh-process
// down -> container gone) against the new digest before landing. Keep the major
// aligned with pgtest.go's pin discipline so a dev-box stack and an installed
// stack never skew on-disk format. This is a Go const Renovate cannot see (like
// pgtest.go's pgImage), so it moves only via a reviewed manual PR.
const DefaultPostgresImage = "docker.io/library/postgres:18@sha256:1957b2ff3137e4ef7f3bc813e74fff50b1e1ffddc85c8b9d6f14ade972be8687"
48 changes: 0 additions & 48 deletions tools/renovate/config.json5
Original file line number Diff line number Diff line change
Expand Up @@ -251,37 +251,6 @@
depNameTemplate: "renovate",
datasourceTemplate: "npm",
},
{
// ── Containerized postgres image (RIG-2774, DL-260) ──
// go/internal/stack/postgres_image.go pins the S4 container-backed postgres
// image (DefaultPostgresImage) as a Go const the native managers can't see.
// Surface it as a docker dep so upstream postgres:18 security REBUILDS (same
// major, new digest) flow through a reviewable PR instead of the pin silently
// rotting. This is a STANDALONE default the T8 adapter runs, with NO parity
// coupling — unlike pgtest.go's pgImage, disabled below because it is locked
// to ci.yml's service image. DL-260 freezes the MAJOR at 18 for on-disk-
// format stability; the packageRule below (allowedVersions /^18$/) moves only
// the digest, so an 18->19 major is a deliberate design action (re-run the T8
// podman integration test), never an auto-PR.
//
// depName is `postgres-stack`, NOT `postgres`: the CI-service fence below is
// `matchDepNames: ["postgres"], enabled: false` with no manager/file scope,
// so a `postgres` depName here would inherit that disable and open zero PRs.
// The distinct name keeps the two postgres pins independently governed.
//
// versioningTemplate is explicit `docker`: a custom.regex manager defaults to
// `semver-coerced` regardless of datasource (see the catalog manager note
// above), which mishandles a `<tag>@<digest>` docker reference.
customType: "regex",
managerFilePatterns: ["/^go/internal/stack/postgres_image\\.go$/"],
matchStrings: [
"postgres:(?<currentValue>\\d+)@(?<currentDigest>sha256:[a-f0-9]{64})",
],
depNameTemplate: "postgres-stack",
packageNameTemplate: "docker.io/library/postgres",
datasourceTemplate: "docker",
versioningTemplate: "docker",
},
],

// ── The github-actions manager: SHA-pin maintenance (RIG-2432) ──
Expand Down Expand Up @@ -411,23 +380,6 @@
matchDepNames: ["postgres"],
enabled: false,
},
{
// ── Containerized postgres image: digest-only within major 18 (RIG-2774) ──
// DL-260 freezes the postgres MAJOR at 18 (on-disk-format stability). Pin the
// allowed version to major 18 so only the digest moves; an 18->19 major bump
// is a deliberate design action (re-run the T8 podman integration test to
// verify on-disk-format compatibility), never an auto-PR. Scoped by the
// distinct `postgres-stack` depName (see the customManager above) so it never
// touches the `postgres` CI-service fence above. No matchUpdateTypes: the
// rule must apply to ALL update types so allowedVersions filters a major
// candidate too — scoping it to `digest` would leave a `19` unfiltered. The
// repo-wide 5-day minimumReleaseAge soak is KEPT: Docker Hub carries a
// tag_last_pushed timestamp for the digest, so a rebuild clears the window
// and opens a PR (no permanent-pending stall the git-refs channel dep has).
matchManagers: ["custom.regex"],
matchDepNames: ["postgres-stack"],
allowedVersions: "/^18$/",
},
{
// ── gomod `go` directive: manual floor policy ──
// The `go` directive in go/go.mod tracks the tools/toolchain/versions/go.nix
Expand Down
Loading
Loading