diff --git a/.bulwark.yml b/.bulwark.yml new file mode 100644 index 0000000..4ad8e2d --- /dev/null +++ b/.bulwark.yml @@ -0,0 +1,20 @@ +# .bulwark.yml — this file is yours. +# +# gt created it once and will never modify or delete it again. bulwark owns +# what goes in it; gt only makes sure it exists, because the one setting below +# is load-bearing for the pipeline and silently wrong by default. +# +# `coverage.source` says who produces the coverage bulwark gates on. In this +# pipeline ci-test produces it and uploads it as the `gt-coverage` artifact, +# which the bulwark stage extracts before bulwark runs — so `report` is +# correct. Leaving it unset means `run`, and bulwark would execute your suite a +# second time without saying so. +# +# A stage skipped by ci-preflight simply produces no report, which bulwark +# treats as no coverage for that ecosystem rather than an error. +# +# Everything else bulwark supports — per-language enablement, coverage +# tolerances, patch-coverage opt-outs, toolchain overrides — belongs here too. +# See bulwark's README. +coverage: + source: report diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..dabd939 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,70 @@ +# Managed by gt — edit .gt-repo.yaml, then run `gt repo sync`. +# +# Shared policy (cooldown, commit-message prefix, schedule) lives in gt's +# template, not here, so changing it for every repo is a one-line edit in gt. +# +# The 7-day cooldown is the supply-chain guard: by the time a PR exists, +# the upstream release has been in the wild long enough to surface yanks and +# compromised publishers before an auto-merge-eligible PR lands on the default +# branch. +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + # Dependabot's commit message is also its PR title, and PRs are + # squash-merged, so the prefix is what keeps dependency updates inside + # Conventional Commits. `include: scope` appends the dependency scope, + # producing e.g. `ci(deps): bump …`. + commit-message: + prefix: "ci" + include: scope + cooldown: + default-days: 7 + open-pull-requests-limit: 25 + + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "weekly" + # Dependabot's commit message is also its PR title, and PRs are + # squash-merged, so the prefix is what keeps dependency updates inside + # Conventional Commits. `include: scope` appends the dependency scope, + # producing e.g. `build(deps): bump …`. + commit-message: + prefix: "build" + include: scope + cooldown: + default-days: 7 + open-pull-requests-limit: 25 + + - package-ecosystem: "npm" + directory: "/page" + schedule: + interval: "weekly" + # Dependabot's commit message is also its PR title, and PRs are + # squash-merged, so the prefix is what keeps dependency updates inside + # Conventional Commits. `include: scope` appends the dependency scope, + # producing e.g. `build(deps): bump …`. + commit-message: + prefix: "build" + include: scope + cooldown: + default-days: 7 + open-pull-requests-limit: 25 + + - package-ecosystem: "npm" + directory: "/worker" + schedule: + interval: "weekly" + # Dependabot's commit message is also its PR title, and PRs are + # squash-merged, so the prefix is what keeps dependency updates inside + # Conventional Commits. `include: scope` appends the dependency scope, + # producing e.g. `build(deps): bump …`. + commit-message: + prefix: "build" + include: scope + cooldown: + default-days: 7 + open-pull-requests-limit: 25 diff --git a/.github/workflows/deploy.yml b/.github/workflows/cd-deploy.yml similarity index 63% rename from .github/workflows/deploy.yml rename to .github/workflows/cd-deploy.yml index c4be6f2..6534318 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/cd-deploy.yml @@ -1,46 +1,29 @@ -# Deploys the status Worker (page assets + prober + API) to Cloudflare on push -# to main. Topology changes need NO deploy — topology.yaml is fetched at runtime. -name: deploy +# cd-deploy — this file is yours. +# +# gt created it once and will never modify or delete it again. It is called by +# cd-orchestration.yml, which verifies the tagged tree carries a passing +# gt/validated-tree attestation before this runs. +# +# deploy.yml's own test job is deliberately gone: it re-ran the same +# type-check and suite ci-test already ran on the PR, and the attestation +# proves that exact tree passed rather than proving it again. +# +# The paths-ignore that skipped deploys for topology/docs-only pushes is gone +# too. topology.yaml is still fetched at runtime, so those changes still need +# no deploy — but with tag-triggered delivery, tagging *is* the decision to +# ship, so inferring it from paths no longer earns its place. +name: cd-deploy on: - push: - branches: [main] - # Skip deploys for data/docs-only changes. - paths-ignore: - - "topology.yaml" - - "docs/**" - - "*.md" - workflow_dispatch: + workflow_call: permissions: contents: read + # @wardnet/* resolve from GitHub Packages during install. packages: read -concurrency: - group: deploy - cancel-in-progress: false - jobs: - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4 - with: - node-version: 22 - cache: pnpm - - name: Install - run: pnpm install --frozen-lockfile - env: - NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Type-check - run: pnpm type-check - - name: Test - run: pnpm test - deploy: - needs: test runs-on: ubuntu-latest environment: production steps: diff --git a/.github/workflows/cd-orchestration.yml b/.github/workflows/cd-orchestration.yml new file mode 100644 index 0000000..a00c2fe --- /dev/null +++ b/.github/workflows/cd-orchestration.yml @@ -0,0 +1,104 @@ +# Managed by gt — edit .gt-repo.yaml, then run `gt repo sync`. +# +# gt owns the orchestration; the cd-* stages it calls are yours. It mirrors +# ci-orchestration deliberately, so there is one pipeline idea to learn. +# +# `cd-gate` is not a branch-protection check — nothing merges here. It exists so +# a release has one status meaning "the whole delivery succeeded" rather than +# four job results to read. +name: CD + +on: + push: + tags: + - "v*.*.*" + workflow_dispatch: + +# One delivery at a time per repository, and never cancelled. +# +# Deliberately not grouped by ref. wardnet-design-system pushes one tag per +# package for a single release, and `changeset publish` ships every pending +# version on the first run — so per-ref grouping would have those runs racing +# to publish the same versions and 409-ing each other. Its release.yml +# serialises for exactly that reason. +# +# The asymmetry decides it: over-serialising costs latency, under-serialising +# corrupts a release. A repo that genuinely needs parallel component delivery +# can make this a setting when one actually does. +concurrency: + group: cd-${{ github.workflow }} + cancel-in-progress: false + +permissions: + contents: read + +jobs: + # Refuses to ship a tree that never passed the gate. Stronger than trusting + # that CI ran: a re-run would only say the code passes now, on a runner, + # again — this says this exact tree passed the full gate. + verify-attestation: + uses: pedromvgomes/gt/.github/workflows/reusable-attest.yml@v1 + with: + require: true + # Must cover everything reusable-attest declares; a caller granting less + # fails the run at parse time. + permissions: + contents: read + statuses: read + pull-requests: read + + deploy: + needs: [verify-attestation] + uses: ./.github/workflows/cd-deploy.yml + secrets: inherit + # Delivery writes: a publish stage creates a release, a deploy stage moves + # artifacts. A called workflow can only narrow this, so a stage needing + # less should say so in its own permissions block. A stage needing *more* + # — OIDC id-token, package registries — is a gt change today; no consumer + # has asked for one yet. + permissions: + contents: write + # write, not read: delivery both consumes private @scope dependencies and + # publishes to GitHub Packages — wardnet-design-system's changeset + # publish needs it. A called workflow can only narrow, so a stage that + # merely installs can drop this to read in its own block. + packages: write + + verify: + needs: [verify-attestation, deploy] + uses: ./.github/workflows/cd-verify.yml + secrets: inherit + # Delivery writes: a publish stage creates a release, a deploy stage moves + # artifacts. A called workflow can only narrow this, so a stage needing + # less should say so in its own permissions block. A stage needing *more* + # — OIDC id-token, package registries — is a gt change today; no consumer + # has asked for one yet. + permissions: + contents: write + # write, not read: delivery both consumes private @scope dependencies and + # publishes to GitHub Packages — wardnet-design-system's changeset + # publish needs it. A called workflow can only narrow, so a stage that + # merely installs can drop this to read in its own block. + packages: write + + cd-gate: + name: cd-gate + needs: [verify-attestation, deploy, verify] + if: always() + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Verify every stage succeeded or was legitimately skipped + env: + RESULTS: ${{ toJSON(needs.*.result) }} + run: | + set -euo pipefail + echo "stage results: ${RESULTS}" + if [[ "${{ contains(needs.*.result, 'failure') }}" == "true" ]]; then + echo "::error::a delivery stage failed" + exit 1 + fi + if [[ "${{ contains(needs.*.result, 'cancelled') }}" == "true" ]]; then + echo "::error::a delivery stage was cancelled" + exit 1 + fi diff --git a/.github/workflows/cd-verify.yml b/.github/workflows/cd-verify.yml new file mode 100644 index 0000000..74b9c87 --- /dev/null +++ b/.github/workflows/cd-verify.yml @@ -0,0 +1,101 @@ +# cd-verify — this file is yours. +# +# gt created it once and will never modify or delete it again. It is called by +# cd-orchestration.yml after cd-deploy. +# +# Confirms the deploy actually took, rather than assuming wrangler exiting zero +# means the site is serving. Two endpoints are checked, and they fail +# differently on purpose: +# +# / — the page assets. Served from the edge. +# /api/status — the worker itself, which reads D1. A broken migration +# returns 500 here while the page still returns 200, so +# checking only the root would miss exactly the failure this +# deploy is most likely to introduce. +# +# Two *consecutive* passes are required. One success can be a lucky hit on an +# edge node still serving the previous version; requiring two spaced apart +# makes a half-propagated or flapping deploy fail rather than squeak through. +# It does not prove global propagation — nothing reachable from one runner +# does — it proves the deploy is not obviously broken. +# +# Requests bypass cache for the reason 6e0e383 records for the probes +# themselves: a cached 200 from before the deploy tells you nothing about it. +name: cd-verify + +on: + workflow_call: + +permissions: + contents: read + +jobs: + smoke: + name: smoke + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Wait for the deploy to serve + env: + BASE: https://status.wardnet.network + ATTEMPTS: "18" + INTERVAL: "10" + REQUIRED_CONSECUTIVE: "2" + run: | + set -uo pipefail + + # Returns 0 when both endpoints answer 200 and the API returns JSON. + # + # No `|| echo 000` on the curl calls: -w already prints 000 when the + # connection fails, and the fallback appended a second one, yielding + # a nonsense "000000" in the diagnostics. There is no `set -e` here, + # so a failed curl does not abort the loop. + probe() { + local stamp="$1" code + code=$(curl -sS -o /tmp/page.out -w '%{http_code}' \ + --max-time 15 -H 'Cache-Control: no-cache' \ + "${BASE}/?cb=${stamp}") + if [[ "$code" != "200" ]]; then + echo " page ${code}" + return 1 + fi + + code=$(curl -sS -o /tmp/api.out -w '%{http_code}' \ + --max-time 15 -H 'Cache-Control: no-cache' \ + "${BASE}/api/status?cb=${stamp}") + if [[ "$code" != "200" ]]; then + echo " page 200, api ${code}" + return 1 + fi + + # A 200 carrying an error body is still a broken deploy. + if ! jq -e . /tmp/api.out >/dev/null 2>&1; then + echo " page 200, api 200 but the body is not JSON" + return 1 + fi + echo " page 200, api 200, JSON ok" + return 0 + } + + consecutive=0 + for attempt in $(seq 1 "$ATTEMPTS"); do + echo "attempt ${attempt}/${ATTEMPTS}:" + if probe "$(date +%s)-${attempt}"; then + consecutive=$((consecutive + 1)) + if [[ "$consecutive" -ge "$REQUIRED_CONSECUTIVE" ]]; then + echo "✅ ${consecutive} consecutive healthy responses — deploy is serving" + exit 0 + fi + else + # Reset rather than decrement: the point is *consecutive* health, + # so a failure between two passes means the deploy is flapping. + consecutive=0 + fi + sleep "$INTERVAL" + done + + echo "::error::status.wardnet.network did not return ${REQUIRED_CONSECUTIVE} consecutive healthy responses" + echo "Last page body:" >&2; head -c 500 /tmp/page.out >&2 || true + echo "" >&2 + echo "Last api body:" >&2; head -c 500 /tmp/api.out >&2 || true + exit 1 diff --git a/.github/workflows/validate-topology.yml b/.github/workflows/ci-build.yml similarity index 58% rename from .github/workflows/validate-topology.yml rename to .github/workflows/ci-build.yml index 26d2a5c..1535484 100644 --- a/.github/workflows/validate-topology.yml +++ b/.github/workflows/ci-build.yml @@ -1,18 +1,18 @@ -# Topology changes deliberately skip the deploy workflow (they're runtime data), -# but main's topology.yaml is exactly what every prober fetches — an invalid -# file would pin monitoring to last-known-good forever. This workflow is the -# validation gate deploy.yml's paths-ignore opts out of. -name: validate-topology +# ci-build — this file is yours. +# +# gt created it once and will never modify or delete it again. It is called by +# ci-orchestration.yml and must keep `workflow_call`. +# +# Validates topology.yaml against its schema. +# +name: ci-build on: - push: - branches: [main] - paths: ["topology.yaml"] - pull_request: - paths: ["topology.yaml"] + workflow_call: permissions: contents: read + # @wardnet/* resolve from GitHub Packages during install. packages: read jobs: diff --git a/.github/workflows/ci-orchestration.yml b/.github/workflows/ci-orchestration.yml new file mode 100644 index 0000000..ff7ac4c --- /dev/null +++ b/.github/workflows/ci-orchestration.yml @@ -0,0 +1,165 @@ +# Managed by gt — edit .gt-repo.yaml, then run `gt repo sync`. +# +# gt owns the orchestration; the ci-* stages it calls are yours. Because every +# stage is a job in this one workflow, `ci-gate` aggregates them with +# `needs:` rather than polling the checks API — no timeout, and no way to +# confuse "absent" with "not started yet". +# +# Branch protection requires exactly one check: "ci-gate". +name: CI + +on: + # No `branches:` filter: a PR stacked onto a feature branch must run CI too. + pull_request: + # `edited` is load-bearing: without it, correcting a rejected title leaves + # the check red until an unrelated push. + types: [opened, synchronize, reopened, edited, ready_for_review] + # On the default branch an already-validated tree skips every stage. + push: + branches: [main] + workflow_dispatch: + +# Superseded pushes to the same PR are cancelled; runs on the default branch and +# in a merge queue are not. Cancelling a PR run costs nothing — a newer commit +# is about to be validated anyway — whereas cancelling a default-branch run +# discards the attestation it was about to record, and cancelling a queued +# merge would drop it from the queue. +concurrency: + group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +permissions: + contents: read + +jobs: + # Reports whether this exact tree already passed the gate, so a push that + # merely squashed an already-validated PR does not run everything again. + attest: + uses: pedromvgomes/gt/.github/workflows/reusable-attest.yml@v1 + # A called workflow may only narrow these. Granting less than + # reusable-attest declares fails the run at parse time, before any job + # starts — so ci-gate never reports and every PR blocks. + permissions: + contents: read + statuses: read + pull-requests: read + + preflight: + needs: [attest] + if: needs.attest.outputs.validated != 'true' + uses: ./.github/workflows/ci-preflight.yml + secrets: inherit + # packages: read because several repositories resolve private @scope + # dependencies from GitHub Packages during install, and a called workflow + # can only narrow what the caller grants — a stage cannot ask for it back. + # Read-only, so granting it where it is unused costs nothing. A stage + # needing more than this is a gt change. + permissions: + contents: read + packages: read + + build: + needs: [attest, preflight] + if: needs.attest.outputs.validated != 'true' && needs.preflight.outputs.run-build != 'false' + uses: ./.github/workflows/ci-build.yml + secrets: inherit + # packages: read because several repositories resolve private @scope + # dependencies from GitHub Packages during install, and a called workflow + # can only narrow what the caller grants — a stage cannot ask for it back. + # Read-only, so granting it where it is unused costs nothing. A stage + # needing more than this is a gt change. + permissions: + contents: read + packages: read + + test: + needs: [attest, build, preflight] + if: needs.attest.outputs.validated != 'true' && needs.preflight.outputs.run-test != 'false' + uses: ./.github/workflows/ci-test.yml + secrets: inherit + # packages: read because several repositories resolve private @scope + # dependencies from GitHub Packages during install, and a called workflow + # can only narrow what the caller grants — a stage cannot ask for it back. + # Read-only, so granting it where it is unused costs nothing. A stage + # needing more than this is a gt change. + permissions: + contents: read + packages: read + + conventional-commits: + needs: [attest] + if: needs.attest.outputs.validated != 'true' + uses: pedromvgomes/gt/.github/workflows/reusable-conventional-commits.yml@v1 + permissions: + contents: read + pull-requests: read + + governance: + needs: [attest] + if: needs.attest.outputs.validated != 'true' + uses: pedromvgomes/gt/.github/workflows/reusable-governance.yml@v1 + + bulwark: + needs: [attest, test] + # `!cancelled()` because a job whose needs were skipped is skipped too, and + # ci-gate counts skipped as a pass. Without it a preflight that skips tests + # would silently disable the security gate while the required check stayed + # green. bulwark still runs *after* tests, to consume their coverage. + if: "!cancelled() && needs.attest.outputs.validated != 'true'" + uses: pedromvgomes/gt/.github/workflows/reusable-bulwark.yml@v1 + secrets: inherit + permissions: + contents: write + pull-requests: write + + # The single required check. `if: always()` so it still reports when a stage + # was skipped, and a skipped stage passes — that is the point of preflight. + ci-gate: + name: ci-gate + needs: [attest, preflight, build, test, conventional-commits, governance, bulwark] + if: always() + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: read + statuses: write + steps: + - name: Verify every stage succeeded or was legitimately skipped + env: + RESULTS: ${{ toJSON(needs.*.result) }} + run: | + set -euo pipefail + echo "stage results: ${RESULTS}" + if [[ "${{ contains(needs.*.result, 'failure') }}" == "true" ]]; then + echo "::error::a required stage failed" + exit 1 + fi + if [[ "${{ contains(needs.*.result, 'cancelled') }}" == "true" ]]; then + echo "::error::a required stage was cancelled" + exit 1 + fi + + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + # Records the tree this run validated, so a later push or tag can prove + # the same content already passed rather than re-running to find out. + - name: Attest the validated tree + # Read-only GITHUB_TOKEN (fork PRs, and every Dependabot event) cannot + # POST a status. Losing the attestation only costs a re-run on the + # default branch; failing the gate would block the PR entirely. + continue-on-error: true + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + SHA: ${{ github.event.pull_request.head.sha || github.sha }} + run: | + set -euo pipefail + # For a pull_request event HEAD is refs/pull/N/merge, so this is the + # tree of the merged result — which is exactly what a squash merge + # puts on the default branch. + tree=$(git rev-parse "HEAD^{tree}") + gh api "repos/${REPO}/statuses/${SHA}" \ + -f state=success \ + -f context=gt/validated-tree \ + -f description="$tree" >/dev/null + echo "attested tree ${tree} on ${SHA}" diff --git a/.github/workflows/ci-preflight.yml b/.github/workflows/ci-preflight.yml new file mode 100644 index 0000000..f01800f --- /dev/null +++ b/.github/workflows/ci-preflight.yml @@ -0,0 +1,38 @@ +# ci-preflight — this file is yours. +# +# gt created it once and will never modify or delete it again. +# +# This stage decides which later stages run. Emit `false` for a stage to skip +# it; anything else — including nothing at all, as below — runs it. That is why +# the stub is a no-op: out of the box every stage runs, and change detection is +# something you opt into rather than out of. +# +# A typical implementation sets these from a paths filter, so an untouched area +# is not rebuilt. A skipped stage still passes the gate. +name: ci-preflight + +on: + workflow_call: + outputs: + run-build: + description: Set to "false" to skip the build stage. + value: ${{ jobs.preflight.outputs.run-build }} + run-test: + description: Set to "false" to skip the test stage. + value: ${{ jobs.preflight.outputs.run-test }} + +permissions: + contents: read + +jobs: + preflight: + name: preflight + runs-on: ubuntu-latest + outputs: + run-build: ${{ steps.decide.outputs.run-build }} + run-test: ${{ steps.decide.outputs.run-test }} + steps: + - name: Decide which stages to run + id: decide + # Emitting nothing runs everything. Replace with change detection. + run: echo "every stage runs by default" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci-test.yml similarity index 57% rename from .github/workflows/ci.yml rename to .github/workflows/ci-test.yml index 7266339..957adde 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci-test.yml @@ -1,22 +1,20 @@ -# PR gate: type-check + unit tests for both workspaces on every pull request -# (and on pushes to main, so the branch tip is always known-green). Deploys -# live in deploy.yml; this workflow only decides whether a change is mergeable. -name: ci +# ci-test — this file is yours. +# +# gt created it once and will never modify or delete it again. It is called by +# ci-orchestration.yml and must keep `workflow_call`. +# +# Type-check and unit tests share one job so the install happens once. +# +name: ci-test on: - pull_request: - push: - branches: [main] + workflow_call: permissions: contents: read + # @wardnet/* resolve from GitHub Packages during install. packages: read -concurrency: - # One in-flight run per branch/PR; newer pushes cancel superseded runs. - group: ci-${{ github.ref }} - cancel-in-progress: true - jobs: test: runs-on: ubuntu-latest diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml new file mode 100644 index 0000000..3ba236d --- /dev/null +++ b/.github/workflows/dependabot-auto-merge.yml @@ -0,0 +1,28 @@ +# Managed by gt — edit .gt-repo.yaml, then run `gt repo sync`. +# +# Daily batch window for merging Dependabot's eligible bumps. The Dependabot +# cooldown (set in .github/dependabot.yml) is the supply-chain guard; this is +# the merge executor on top of it. +# +# Bumps above `minor` are left for human review. +# +# Known gap, not a bug: Dependabot PRs touching .github/workflows/** can never +# be merged here. There is no permissions: key granting GITHUB_TOKEN the +# `workflow` scope, so the github-actions ecosystem always needs a human or +# `gt repo fleet merge-pending`, which runs with your own credentials. +name: Dependabot auto-merge + +on: + schedule: + - cron: "0 1 * * *" + workflow_dispatch: + +# A called reusable workflow can only narrow the caller's token, never widen +# it, so the writes the merge job needs have to be granted here. +permissions: + contents: write + pull-requests: write + +jobs: + auto-merge: + uses: pedromvgomes/gt/.github/workflows/reusable-dependabot-auto-merge.yml@v1 diff --git a/.github/workflows/gt-sync.yml b/.github/workflows/gt-sync.yml new file mode 100644 index 0000000..a13d05f --- /dev/null +++ b/.github/workflows/gt-sync.yml @@ -0,0 +1,25 @@ +# Managed by gt — edit .gt-repo.yaml, then run `gt repo sync`. +# +# Weekly drift check and repair. Runs `gt repo sync`, and opens a PR only if +# something changed — it never pushes to main, so gt's own updates are +# reviewed by the gate like any other change. +# +# Workflow files are deliberately excluded: GITHUB_TOKEN cannot create or +# update anything under .github/workflows/**. When one of them has drifted the +# job reports it and asks for a local `gt repo fleet sync`, which runs with +# your own credentials. Because the callers pin the moving v1 tag, this is +# rare — gate logic changes need no file change at all. +name: gt sync + +on: + schedule: + - cron: "0 6 * * 1" + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +jobs: + sync: + uses: pedromvgomes/gt/.github/workflows/reusable-sync.yml@v1 diff --git a/.gt-repo.yaml b/.gt-repo.yaml new file mode 100644 index 0000000..1d16105 --- /dev/null +++ b/.gt-repo.yaml @@ -0,0 +1,72 @@ +# Repository governance for gt. This file is the source of truth; run +# 'gt repo sync' to render it, and 'gt repo check' to verify. +# +# Shared policy (Dependabot cooldown, commit-message prefixes, the weekly sync +# schedule) lives in gt's templates, not here, so it stays consistent across +# every governed repo. +# +# The pipeline stages below become jobs in ci-orchestration.yml, each calling a +# ci-*/cd-* workflow that belongs to this repository: gt creates those once and +# never touches them again. Branch protection needs exactly one check, ci-gate, +# which waits on all of them. + +gt_version: v1.0.0 +dependabot: + - ecosystem: github-actions + directory: / + - ecosystem: npm + directory: / + - ecosystem: npm + directory: /page + - ecosystem: npm + directory: /worker +dependabot_auto_merge: + enabled: true + schedule: 0 1 * * * + max_bump: minor + delete_branch: true +bulwark: + enabled: true +pipeline: + ci: + enabled: true + stages: + - preflight + - build + - test + merge_queue: false + cd: + enabled: true + stages: + - deploy + - verify + tags: + - v*.*.* +conventional_commits: + enabled: true + scope: pr_title + types: + - feat + - fix + - docs + - style + - refactor + - perf + - test + - build + - ci + - chore + - revert +settings: + merge: + squash: true + merge_commit: false + rebase: false + delete_branch_on_merge: true + branch_protection: + branch: main + required_approvals: 0 + require_up_to_date: false +files: + - sync + - dependabot-auto-merge