Skip to content

fix(ctxd): Worker-Deprioritisierung wirkt jetzt prozessweit — per-Thr… #184

fix(ctxd): Worker-Deprioritisierung wirkt jetzt prozessweit — per-Thr…

fix(ctxd): Worker-Deprioritisierung wirkt jetzt prozessweit — per-Thr… #184

Workflow file for this run

# ctx CI/CD Pipeline
# github.com/GottZ/ctx — The memory your LLM pretends to have.
# GottZ 4-Way RRF | GottZ Scope Model | GottZ Guard
#
# Branch-Strategie: root (Haupt-Branch), keine main/master-Konvention.
# Alle Stages laufen auf: push to root, PR to root, workflow_dispatch.
name: ctx CI/CD
on:
push:
branches: [root]
pull_request:
branches: [root]
workflow_dispatch:
# Nightly cadence for the PV10 live tier (design 06 §4.7/§9.5, decision E3).
# The whole workflow re-runs at 03:00 UTC, but only web-live is scheduled-
# gated to actually do live work; docker is gated OFF schedule so a nightly
# run stays a verification, not a redundant image re-push.
schedule:
- cron: "0 3 * * *"
# Deny-all default — jeder Job deklariert eigene Permissions.
permissions: {}
# Schedule runs get their OWN concurrency group (wave C-W3): the nightly
# tiers (web-live release gate, web-lhci timing trend) must survive a root
# push around 03:00 UTC — in the shared group the push would cancel the
# schedule run and the tag rule (green nightly before a version tag) would
# block the morning release until a manual re-dispatch. Two parallel runs on
# the same ref cannot conflict: docker (image push) is gated OFF schedule.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}${{ github.event_name == 'schedule' && '-nightly' || '' }}
cancel-in-progress: ${{ github.event_name != 'workflow_dispatch' }}
env:
GO_VERSION: "1.26"
REGISTRY: ghcr.io
IMAGE_NAME: ghcr.io/gottz/ctx
jobs:
# ─── Stage 1: Lint ────────────────────────────────────────────────────────────
lint:
name: Lint
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Go ${{ env.GO_VERSION }}
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: ${{ env.GO_VERSION }}
cache: true
cache-dependency-path: go/go.sum
- name: golangci-lint
uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9.2.0
with:
version: latest
working-directory: go
args: --timeout=5m
only-new-issues: ${{ github.event_name == 'pull_request' }}
# ─── Stage 1b: Web (frontend gate, parallel to Lint) ─────────────────────────
# Wave PV8 (design 06 §7): PRs check the frontend for the first time.
# Every bun step runs inside the digest-pinned toolchain container built
# from e2e/toolchain.lock — the SINGLE digest source (decision E4), parsed
# by go/web/e2e-container.sh (the same implementation e2e-visual.sh uses;
# no second copy of the lock mechanics in this YAML). That container is the
# canonical render environment, so @visual pixel asserts run here
# (CTX_E2E_CONTAINER=1 is set by the runner script).
#
# Command parity with the release build (go/Dockerfile stage 0):
# `bun install --frozen-lockfile` and `bun run check` are byte-identical;
# `bun run build` runs inside the e2e step via the Playwright webServer
# (with VITE_E2E=1 — the plain production build stays covered by the
# Docker stage). Known, deliberate divergence: the release stage uses
# oven/bun alpine (musl), the toolchain image the glibc bun binary on the
# Playwright noble base (design 06 §4.4/S4).
#
# ── Sharding preparation (design 06 §6.3, wave PV11 — PREPARED, NOT ACTIVE) ──
# The reporter foundation (blob) is already laid (playwright.config.ts), so
# activating sharding is a YAML DIFF, not a rebuild. It stays dormant because
# the calibrated budget (.github/e2e-budget.json: e2e ~77s, budget 160s) sits
# far below the 270s e2e part budget — sharding activates only after the §6.3
# trigger (three consecutive runs over the e2e part budget) OR by flipping the
# repo variable `vars.E2E_SHARD == '1'`. The activation diff, verbatim:
#
# web:
# strategy:
# fail-fast: false
# matrix: { shard: [1, 2, 3, 4] }
# steps:
# ...
# - name: e2e (Playwright in pinned container)
# run: bash go/web/e2e-container.sh bun run test:e2e -- \
# --shard=${{ matrix.shard }}/4 # blob reporter is per-shard
# # upload each shard's e2e/.results/blob as blob-${{ matrix.shard }}
# web-merge: # new fan-in job
# needs: [web]
# steps:
# - download all blob-* artifacts
# - bun x playwright merge-reports --reporter html ./blobs
#
# Activation-day gate (design 06 §7-PV11 (c)): a 4×blob → merge-reports probe
# must yield the SAME total test count as a single-run report. Not run here —
# sharding is dormant, and a real 4-shard probe means four full e2e runs
# (deliberately deferred until the trigger fires; not a rebuild).
web:
name: Web
runs-on: ubuntu-latest
# Hard deck above the 10-min budget fail (design 06 §6.3).
timeout-minutes: 12
permissions:
contents: read
steps:
- name: Record job start (budget measurement, design 06 §6.3)
run: echo "CTX_JOB_STARTED_AT=$(date +%s)" >> "$GITHUB_ENV"
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
# Full history: the baseline marker gate diffs the whole PR/push
# range and reads per-commit messages (design 06 §5.5 layer 2).
fetch-depth: 0
# Enforcement that survives --no-verify and dead local hooks: baseline
# changes (__screenshots__, a11y debt growth) require the [baseline]
# marker on every touching commit, or the PR label `baseline-update`.
# Runs where a range exists: PRs (base...head) and pushes with a real
# `before` SHA. Skipped for branch-creation pushes (before = zero SHA)
# and workflow_dispatch — there is no range to check there.
- name: Baseline marker gate (design 06 §5.5 layer 2)
if: github.event_name == 'pull_request' || (github.event_name == 'push' && github.event.before != '0000000000000000000000000000000000000000')
env:
RANGE_BASE: ${{ github.event_name == 'pull_request' && format('origin/{0}', github.event.pull_request.base.ref) || github.event.before }}
PR_LABELS: ${{ github.event_name == 'pull_request' && join(github.event.pull_request.labels.*.name, ' ') || '' }}
run: bash .github/scripts/baseline-marker-gate.sh
- name: Build pinned toolchain container (e2e/toolchain.lock)
run: bash go/web/e2e-container.sh bun --version
# Byte-identical to go/Dockerfile stage 0 — the v4.1.0 lesson: bun.lock
# drift was CI-invisible until the Docker build; this catches it on PR.
- name: Install (bun, frozen lockfile)
run: bash go/web/e2e-container.sh bun install --frozen-lockfile
# K9 steps (design 05 → this job): 05 contributes lint:css and the
# contrast matrix as named job components. Reading of 05 (§1 DoD +
# masterplan K9): no separate MECHANISM is demanded — the contrast
# matrix is a vitest test (tokens/contrast-matrix.test.ts) and would be
# covered by `bun run test` below; `bun run check` re-runs lint:css.
# Both get named steps anyway for failure attribution (the K9 contract
# stays visible in the job UI); each re-run costs single-digit seconds.
- name: Lint CSS (token gate + inline gate — K9)
run: bash go/web/e2e-container.sh bun run lint:css
- name: Contrast matrix (WCAG token gate — K9)
run: bash go/web/e2e-container.sh bun run test tokens/contrast-matrix.test.ts
- name: Check (lint:css + svelte-check + tsc)
run: bash go/web/e2e-container.sh bun run check
- name: Unit tests (vitest)
run: bash go/web/e2e-container.sh bun run test
# Full suite incl. @visual: this is the canonical pinned container, the
# committed baselines are compared here (design 06 §5.5: CI has NO
# update path — --update-snapshots exists only in e2e-visual.sh).
- name: e2e (Playwright in pinned container)
# Nightly (schedule) + on-demand (dispatch) INCLUDE the @quarantine
# specs — quarantine means observed, not forgotten (design 06 §5.4,
# wave PV11). PRs/pushes leave CTX_E2E_QUARANTINE empty, so the config
# grepInvert excludes them from the per-PR gate. e2e-container.sh passes
# the var through into the pinned container.
env:
CTX_E2E_QUARANTINE: ${{ (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && '1' || '' }}
run: bash go/web/e2e-container.sh bun run test:e2e
# ── Bundle byte budget gate (PLc, wave C-W2) ──────────────────────────
# Byte truth = the RELEASE encoding: on-disk .br siblings from a build
# WITHOUT VITE_E2E (the bytes go/web/web.go actually serves). At this
# point dist/ holds the VITE_E2E build from the Playwright webServer
# (test hooks included — wrong bytes for a budget verdict), so the gate
# step pair rebuilds fresh and measures immediately after; nothing runs
# in between. Placement AFTER e2e (masterplan C-W2): the webServer build
# can never overwrite the measured artifacts. `if: !cancelled()` keeps
# byte-bloat attribution visible even when e2e is red; the gate itself
# is guarded on the release build's outcome so it can never measure a
# stale VITE_E2E dist/. PLc is the PR byte authority — the Lighthouse
# layer (C-W3) is nightly, warn-only, and never judges bytes.
- name: Release build (byte truth — no VITE_E2E)
id: release_build
if: ${{ !cancelled() }}
run: bash go/web/e2e-container.sh bun run build
- name: Bundle byte budget gate (PLc)
if: ${{ !cancelled() && steps.release_build.outcome == 'success' }}
run: bash go/web/e2e-container.sh bun run test:budget
- name: Flake annotation (report.json retry statistics)
if: ${{ !cancelled() }}
run: bash .github/scripts/flake-annotations.sh
- name: Budget check (design 06 §6.3)
if: ${{ !cancelled() }}
run: bash .github/scripts/e2e-budget.sh
# HTML report from the blob reporter output — git hosting renders no
# PNG diffs inline; without this artifact baseline review is blind
# (design 06 §4.8). Runs in the same container; `bun x` (not `bunx` —
# the toolchain image ships only the bun binary, no bunx symlink).
- name: Render HTML report from blob
if: ${{ !cancelled() }}
run: |
if [ -d go/web/e2e/.results/blob ]; then
bash go/web/e2e-container.sh bun x playwright merge-reports --reporter html ./e2e/.results/blob
else
echo "no blob output (e2e step did not run) — skipping HTML render"
fi
- name: Upload e2e report + diffs
if: ${{ !cancelled() }}
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: web-e2e-report
path: |
go/web/playwright-report/
go/web/e2e/.results/report.json
# Mock-tier retention 14 days (design 06 §4.8); the live tier
# (wave PV10) gets 3 — real keys flow only there.
retention-days: 14
if-no-files-found: ignore
# ─── Stage 1c: Web Live Tier (PV10, nightly + on-demand) ─────────────────────
# Wave PV10 (design 06 §4.7/§9.5): the ≤15 @live specs run against a REAL,
# throwaway ctxd + Postgres (docker-compose.e2e.yml), seeded ONLY through
# production write paths (fail-closed target gate, §3.6). It proves classes
# the mock `web` job cannot — server enforcement (tenant isolation), fixture-
# shape truth (W10), real SSE transport — so it is intentionally OFF the
# per-PR path: nightly (schedule) + on-demand (dispatch) + PR opt-in via the
# `e2e-live` label (decision E3). run-live.sh needs only docker on the host;
# bun + Playwright run INSIDE the pinned toolchain image (--network host).
#
# RELEASE-GATE RULE (design 06 §9.5, README): a version tag is pushed only
# after a GREEN nightly run of THIS job — the live-tier extension of the
# repo's "CI is truth, not local" rule.
web-live:
name: Web Live Tier
runs-on: ubuntu-latest
timeout-minutes: 30
if: >-
github.event_name == 'schedule' ||
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'e2e-live'))
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Live tier (throwaway compose stack + seed + @live specs)
run: bash go/web/e2e/live/run-live.sh
# Live traces retention 3 (design 06 §4.8): they record Bearer headers +
# the reveal-once owner_key, but every key is valid ONLY against the
# job-local instance that just died — impact null (§3.6 invariant 3).
- name: Upload live-tier report + traces
if: ${{ !cancelled() }}
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: web-live-report
path: go/web/e2e/live/.results/
retention-days: 3
if-no-files-found: ignore
# ─── Stage 1d: Web Trend + History Budget (PV11, nightly) ────────────────────
# Wave PV11 (design 06 §6.3): two nightly bookkeeping measurements that dock
# onto the nightly run without touching the release-gating web-live job.
# - e2e DURATION + flaky trend line, from the mock-tier `web` job's
# report.json (one JSON line per run, retention 90d — §4.7/§6.3);
# - screenshot HISTORY blob-volume budget (git rev-list over the full
# history, needs fetch-depth: 0 — §3.1/§6.3).
# A DEDICATED job (not a step in web-live): web-live runs a 30-min live stack
# and owns the LIVE report.json (e2e/live/.results), while the trend wants the
# MOCK-tier duration from the `web` job — folding it into web-live would couple
# trend bookkeeping to the release gate and force a mock-artifact download into
# the live job. A ~2-min job stays independently green/red. Nightly + dispatch
# only (no per-PR trend noise). `needs: [web]` for the report.json artifact —
# a red `web` skips the trend (no point trending a failed run).
web-trend:
name: Web Trend + History Budget
runs-on: ubuntu-latest
timeout-minutes: 10
needs: [web]
if: >-
github.event_name == 'schedule' ||
github.event_name == 'workflow_dispatch'
permissions:
contents: read
steps:
- name: Checkout (full history for the screenshot blob-volume measurement)
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- name: Download mock-tier e2e report (from the `web` job)
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: web-e2e-report
path: web-report
- name: e2e duration + flaky trend line (design 06 §6.3)
run: |
REPORT=$(find web-report -name report.json -print -quit 2>/dev/null || true)
REPORT="${REPORT:-go/web/e2e/.results/report.json}" TREND_DIR=trend \
bash .github/scripts/e2e-trend.sh
- name: Screenshot history budget (design 06 §3.1/§6.3)
# Nightly runs on root ⇒ HEAD==root; the script defaults to the `root`
# ref when present, HEAD otherwise. Annotates >=60 MB, escalation-due
# >=150 MB — never an auto-fail (§6.3: the orphan-branch/LFS decision is
# the User's, taken WITH the measurement).
run: bash .github/scripts/history-budget.sh
- name: Upload trend line
if: ${{ !cancelled() }}
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: web-trend
path: trend/
# Generous retention (design 06 §6.3): one line per run reconstructs
# the duration/flaky trend from the retained set.
retention-days: 90
if-no-files-found: ignore
# ─── Stage 1e: Web LHCI Nightly (timing trend, wave C-W3) ────────────────────
# LHCI is the lab TIMING probe — nightly-only, warn-only, NEVER a PR gate
# and NEVER a byte authority (DECISIONS C-E1/C-E2, keeps 06-E6: bytes are
# judged by the PLc budget gate in the `web` job). A DEDICATED job, not a
# step in `web` and without `needs:` — it must stay green/red independently
# (web-trend dies with a red `web`; this must not). Runs the same pinned
# toolchain container; Chromium + node come from the image (no
# chrome-launcher dep, CHROME_PATH globbed in lighthouserc.cjs). Lab noise
# floor is ±20-40% on shared runners — the lhr artifacts feed the 90-day
# trend (C-W4), single runs are never interpreted. timeout-minutes is the
# wall-clock budget: a hung Chromium turns the job visibly red, never a
# silent multi-hour runner burn (C2-3).
web-lhci:
name: Web LHCI Nightly (timing trend)
runs-on: ubuntu-latest
timeout-minutes: 20
# workflow_dispatch is included deliberately: it is the only way to test
# the nightly path without waiting for 03:00 UTC (same rationale as
# web-live/web-trend).
if: >-
github.event_name == 'schedule' ||
github.event_name == 'workflow_dispatch'
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Build pinned toolchain container (e2e/toolchain.lock)
run: bash go/web/e2e-container.sh bun --version
- name: Install (bun, frozen lockfile)
run: bash go/web/e2e-container.sh bun install --frozen-lockfile
- name: Release build (no VITE_E2E — LHCI serves dist/ statically)
run: bash go/web/e2e-container.sh bun run build
- name: LHCI (3 runs, median, warn-only)
run: bash go/web/e2e-container.sh bun run perf:lhci
# e2e-container.sh writes .lhci/ as root (the pinned container runs as
# root, repo bind-mounted at /work). The host-side trend step below runs
# as the runner user and cannot mkdir into a root-owned dir — reclaim
# ownership first. Non-fatal: a missing .lhci/ means perf:lhci already
# failed and owns the red; this step must never introduce a new one.
- name: Reclaim LHCI output ownership (container writes .lhci/ as root)
if: ${{ !cancelled() }}
run: |
if [ -d go/web/e2e/perf/.lhci ]; then
sudo chown -R "$(id -u):$(id -g)" go/web/e2e/perf/.lhci
fi
# Trend line (wave C-W4): ONE JSON line from the representative (median)
# run — the 90-day artifact set reconstructs the timing trend. Noise
# floor ±20-40% (annotation only, never a gate — C1-M2/M3).
- name: LHCI timing trend line (wave C-W4)
if: ${{ !cancelled() }}
run: bash .github/scripts/lhci-trend.sh
- name: Upload trend line (90d)
if: ${{ !cancelled() }}
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: web-lhci-trend
path: go/web/e2e/perf/.lhci/trend/
retention-days: 90
if-no-files-found: ignore
# .lhci is a dot-dir — upload-artifact excludes hidden paths by default.
include-hidden-files: true
# if-no-files-found: error — an LHCI run that produced no lhr JSON is a
# broken measurement and must be visibly red, never silently green.
- name: Upload lhr reports
if: ${{ !cancelled() }}
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: web-lhci-reports
path: go/web/e2e/perf/.lhci/
retention-days: 14
if-no-files-found: error
# .lhci is a dot-dir — upload-artifact excludes hidden paths by default.
include-hidden-files: true
# ─── Stage 2: Unit Tests ──────────────────────────────────────────────────────
unit-tests:
name: Unit Tests
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Go ${{ env.GO_VERSION }}
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: ${{ env.GO_VERSION }}
cache: true
cache-dependency-path: go/go.sum
- name: Run unit tests
working-directory: go
run: |
go test \
-v \
-short \
-race \
-count=1 \
-timeout=5m \
-coverprofile=coverage.out \
-covermode=atomic \
./...
- name: Upload coverage
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: coverage-unit
path: go/coverage.out
retention-days: 7
# ─── Stage 3: Integration Tests ───────────────────────────────────────────────
integration-tests:
name: Integration Tests
runs-on: ubuntu-latest
timeout-minutes: 20
needs: [unit-tests]
permissions:
contents: read
env:
TESTCONTAINERS_RYUK_DISABLED: "false"
TESTCONTAINERS_PULL_POLICY: "default"
CONTEXT_DB: context_store
CONTEXT_DB_USER: context_user
CONTEXT_DB_PASSWORD: testpassword
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Go ${{ env.GO_VERSION }}
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: ${{ env.GO_VERSION }}
cache: true
cache-dependency-path: go/go.sum
- name: Cache testcontainers Docker image
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: ~/.cache/docker-images
key: docker-testcontainers-timescale-pg18-${{ runner.os }}
- name: Load or pull testcontainers Docker image
run: |
if [ -f ~/.cache/docker-images/timescaledb-ha-pg18.tar ]; then
docker load -i ~/.cache/docker-images/timescaledb-ha-pg18.tar
else
docker pull timescale/timescaledb-ha:pg18
mkdir -p ~/.cache/docker-images
docker save timescale/timescaledb-ha:pg18 -o ~/.cache/docker-images/timescaledb-ha-pg18.tar
fi
- name: Run integration tests
working-directory: go
run: |
go test \
-v \
-tags=integration \
-count=1 \
-timeout=10m \
-coverprofile=coverage-integration.out \
-covermode=atomic \
./...
- name: Upload integration coverage
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: coverage-integration
path: go/coverage-integration.out
retention-days: 7
# ─── Stage 4: Build Binary ────────────────────────────────────────────────────
build:
name: Build
runs-on: ubuntu-latest
timeout-minutes: 10
needs: [lint, unit-tests]
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Go ${{ env.GO_VERSION }}
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: ${{ env.GO_VERSION }}
cache: true
cache-dependency-path: go/go.sum
- name: Build binary
working-directory: go
env:
CGO_ENABLED: "0"
GOOS: linux
GOARCH: amd64
run: |
go build \
-trimpath \
-ldflags="-s -w \
-X main.Version=${{ github.ref_name }} \
-X main.Commit=${{ github.sha }} \
-X main.BuildDate=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
-o ../ctxd \
./cmd/ctxd/
- name: Verify binary
run: |
file ctxd
./ctxd --version 2>/dev/null || ./ctxd version 2>/dev/null || true
- name: Upload binary artifact
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: ctxd-linux-amd64
path: ctxd
retention-days: 1
# ─── Stage 5: Docker Build + Push ─────────────────────────────────────────────
docker:
name: Docker Build + Push
runs-on: ubuntu-latest
timeout-minutes: 20
# web gates the image push like lint gates the binary build: a red
# frontend job must not publish an image (the Dockerfile embeds web/dist,
# so this only moves the red earlier and names it — wave PV8).
needs: [build, integration-tests, web]
# Off-schedule gate (PV10): the nightly run is a verification pass for the
# live tier — it must never re-push an image of an unchanged ref.
if: >-
github.event_name != 'schedule' &&
(github.ref == 'refs/heads/root' ||
github.event_name == 'workflow_dispatch')
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Download binary artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ctxd-linux-amd64
- name: Make binary executable
run: chmod +x ctxd
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
- name: Login to GitHub Container Registry
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Docker metadata
id: meta
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
with:
images: ${{ env.IMAGE_NAME }}
tags: |
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/root' }}
type=sha,prefix=sha-,format=short
type=raw,value={{date 'YYYY-MM-DD'}}
labels: |
org.opencontainers.image.title=ctx
org.opencontainers.image.description=The memory your LLM pretends to have. GottZ 4-Way RRF + GottZ Scope Model.
org.opencontainers.image.url=https://github.com/GottZ/ctx
org.opencontainers.image.source=https://github.com/GottZ/ctx
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.licenses=MPL-2.0
- name: Build and push Docker image
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
with:
context: ./go
file: ./go/Dockerfile
platforms: linux/amd64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: |
type=registry,ref=${{ env.IMAGE_NAME }}:buildcache
type=gha
cache-to: |
type=registry,ref=${{ env.IMAGE_NAME }}:buildcache,mode=max
type=gha,mode=max