docs: the server runs Gitea 1.27.2, not 1.24.2; say what it actually honours #61

Merged
Zayin_Admin merged 1 commits from docs/gitea-1.27-ci-facts into main 2026-10-06 03:50:31 +00:00
5 changed files with 57 additions and 54 deletions
Showing only changes of commit 0ec1755279 - Show all commits
+7 -12
View File
@@ -1,18 +1,13 @@
name: CI name: CI
# IMPORTANT — the currently inventoried server is Gitea 1.24.2, which *ignores* several keys used # The server runs Gitea 1.27.2 (docs/development/security.md, "What this server honours"):
# below. Per https://docs.gitea.com/1.24/usage/actions/comparison, its unsupported workflow syntax
# includes `permissions` / `jobs.<job_id>.permissions`, `concurrency`, and
# `jobs.<job_id>.timeout-minutes`. They are declared here deliberately, so the workflow is already
# correct on a supported release and on GitHub Actions, but on this server they are inert:
# #
# * `permissions:` does NOT restrict the in-job `GITEA_TOKEN`. Job-token permission controls, # * `concurrency:` is honoured: a newer push to a pull request cancels the run it supersedes, so a
# repository/owner defaults, and server-side maximum-permission clamping arrive in the 1.26 # cancelled run's own cleanup is never guaranteed (see the CI stack sweep in each stack job).
# feature line (https://docs.gitea.com/1.26/usage/actions/token-permissions). Until the server # * `permissions:` is supported by 1.27 with Gitea's own scopes, but what the job token can do on
# is upgraded and configured, the token's authority is whatever the server grants — not what # this server is not yet proven, so nothing here relies on it being read-only.
# this file requests. # * `timeout-minutes:` is no longer listed as unsupported, but is not verified here either;
# * `concurrency:` and `timeout-minutes:` are NOT bounded-execution guarantees here. Cancellation, # teardown relies on the stack sweep and the runner's own reaper.
# timeout, and teardown must be enforced by the trusted runner provisioner/reaper.
# #
# `persist-credentials: false` below is unaffected by any of this and is enforced by the action # `persist-credentials: false` below is unaffected by any of this and is enforced by the action
# itself. See docs/development/security.md. # itself. See docs/development/security.md.
+4 -3
View File
@@ -218,9 +218,10 @@ undeployed state, and the launch checklist in a single place. **CI is accepted a
trusted-code-only boundary**: the self-hosted Gitea runner executes only code controlled and trusted-code-only boundary**: the self-hosted Gitea runner executes only code controlled and
reviewed by trusted repository collaborators, and unknown or untrusted pull requests must not reviewed by trusted repository collaborators, and unknown or untrusted pull requests must not
execute on it automatically. What remains is operational and cannot be proven from this repository — execute on it automatically. What remains is operational and cannot be proven from this repository —
upgrading Gitea past 1.24.2 (which *ignores* the workflow's `permissions`, `concurrency`, and configuring and proving job-token authority server-side (the server now runs Gitea 1.27.2, which
`timeout-minutes` declarations), clamping job-token authority server-side, enforcing timeout and honours `concurrency` and documents `permissions`, but neither token limits nor `timeout-minutes` are
teardown in a trusted provisioner/reaper, and proving disposable runner isolation. Those are verified on it yet), enforcing timeout and teardown in a trusted provisioner/reaper, and proving
disposable runner isolation. Those are
prerequisites for **executing untrusted contributions**, alongside the deployment checklist that prerequisites for **executing untrusted contributions**, alongside the deployment checklist that
gates a **public launch**; moving future public contribution workflows to a service with isolated gates a **public launch**; moving future public contribution workflows to a service with isolated
hosted runners is an acceptable alternative. **Neither set of requirements blocks Phase 6** — Surface hosted runners is an acceptable alternative. **Neither set of requirements blocks Phase 6** — Surface
@@ -163,27 +163,28 @@ It references no secret, needs no registry credential (the harness image is publ
organization), and uses no `pull_request_target`. The trusted-code-only rule for self-hosted runners organization), and uses no `pull_request_target`. The trusted-code-only rule for self-hosted runners
applies to module repositories exactly as it does to the platform (see `docs/development/security.md`). applies to module repositories exactly as it does to the platform (see `docs/development/security.md`).
**Why `docker run`, and not a reusable workflow or a composite action.** The inventoried server is **Why `docker run`, and not a reusable workflow or a composite action.** The server runs Gitea
Gitea 1.24.2. 1.27.2. (This proposal first assumed 1.24.2; the reasons below hold on the actual version.)
- Its runner does support calling a reusable workflow from another repository, by cloning that - Its runner can call a reusable workflow from another repository by cloning that repository, and
repository. That support is thinly documented, and has known problems in practice. since 1.26 that includes private repositories (go-gitea/gitea#32562). That support is still thinly
- Actions and reusable workflows from **private** repositories only arrive with Gitea 1.26 documented, and has known problems in practice.
(go-gitea/gitea#32562). - A reusable workflow would make the harness's behaviour depend on how this server and runner
version expand it. An image runs the same everywhere it runs.
- Composite actions from another repository work through absolute `uses:` URLs. That would still - Composite actions from another repository work through absolute `uses:` URLs. That would still
wrap the same `docker run`, adding a second pinned reference and nothing else. wrap the same `docker run`, adding a second pinned reference and nothing else.
An image digest is the one reference that is equally immutable, equally reviewable, and works An image digest is the one reference that is equally immutable, equally reviewable, and works
unchanged on Gitea 1.24, Gitea 1.26, and GitHub Actions. A composite action can be added later as a unchanged on any Gitea release and on GitHub Actions. A composite action can be added later as a
convenience over the same image, without changing what a module pins. convenience over the same image, without changing what a module pins.
### Nothing the harness starts outlives the job that started it ### Nothing the harness starts outlives the job that started it
The harness starts Docker stacks on a shared runner, and a runner can kill a job outright: a The harness starts Docker stacks on a shared runner, and a runner can kill a job outright: a
cancelled job, or the runner's own time limit, since Gitea 1.24 ignores `timeout-minutes`. A script's cancelled or superseded job (`concurrency` cancels superseded runs on this server), or the runner's
own cleanup trap does not run then. The platform's CI leaked a whole migration stack that way, and it own time limit. A script's own cleanup trap does not run then. The platform's CI leaked a whole
ran until someone removed it by hand. So the harness follows the platform's rule migration stack that way, and it ran until someone removed it by hand. So the harness follows the
(`scripts/ci-sweep.sh`, `docs/development/security.md`): platform's rule (`scripts/ci-sweep.sh`, `docs/development/security.md`):
- everything it starts is named and labeled as a CI stack of its run, attempt, and job key; - everything it starts is named and labeled as a CI stack of its run, attempt, and job key;
- it removes its own stacks in a trap; - it removes its own stacks in a trap;
@@ -233,9 +234,9 @@ The registry is reached exactly as the instance's publish job reaches it.
- **Each module keeps its own copy of the smoke scripts.** Rejected. That is today's situation in - **Each module keeps its own copy of the smoke scripts.** Rejected. That is today's situation in
embryo, and it drifts. embryo, and it drifts.
- **A reusable workflow in the platform repository.** Rejected for now, for the Gitea 1.24 reasons - **A reusable workflow in the platform repository.** Rejected for now, for the reasons above: thin
above. It would also give the harness's behaviour to whichever runner and server version happens support in practice, and a harness whose behaviour depends on whichever runner and server version
to expand it. happens to expand it.
- **Publish the harness as an npm package.** Rejected. The harness needs Docker, a browser, and the - **Publish the harness as an npm package.** Rejected. The harness needs Docker, a browser, and the
platform's deployment files anyway, so a package would ship a fraction of it and assume the rest. platform's deployment files anyway, so a package would ship a fraction of it and assume the rest.
- **Test only the newest platform version.** Rejected. A module's range floor would go untested - **Test only the newest platform version.** Rejected. A module's range floor would go untested
+27 -22
View File
@@ -693,8 +693,10 @@ public Dashboard login must be assessed with the actual edge/proxy controls befo
## Continuous integration boundary ## Continuous integration boundary
CI runs on self-hosted Gitea Actions runners. The inventoried server is **Gitea 1.24.2**, and that CI runs on self-hosted Gitea Actions runners. The server reports **Gitea 1.27.2** (`GET
version determines which parts of the workflow are real guarantees and which are not. /api/v1/version`, checked 2026-10-06; this document previously said 1.24.2, which was stale). The
server's actual version, not the documentation of an earlier one, determines which parts of the
workflow are real guarantees and which are not.
### The current operating rule: trusted code only ### The current operating rule: trusted code only
@@ -727,24 +729,26 @@ so a later step in the same job cannot read or reuse it from there.
It does **not** remove the short-lived `GITEA_TOKEN` from the job environment, and it does **not** It does **not** remove the short-lived `GITEA_TOKEN` from the job environment, and it does **not**
reduce that token's server-side authority. A step that wants the token can still read it. reduce that token's server-side authority. A step that wants the token can still read it.
### What Gitea 1.24 ignores — so the branch cannot claim it ### What this server honours, and what is not yet verified
Per the official comparison for this version Each item separates what Gitea documents for 1.27
(<https://docs.gitea.com/1.24/usage/actions/comparison>), Gitea 1.24's unsupported workflow syntax (<https://docs.gitea.com/1.27/usage/actions/comparison>) from what has been observed on this server:
includes `permissions` and `jobs.<job_id>.permissions`, `concurrency`, and
`jobs.<job_id>.timeout-minutes`. On this server those declarations are inert. Therefore:
- **The workflow's `permissions: contents: read` does not restrict the job token.** This branch - **`concurrency` is honoured.** Gitea supports it since 1.26 (go-gitea/gitea#32751), and on this
cannot claim the `GITEA_TOKEN` is read-only, because nothing in workflow YAML makes it so on server a newer push to a pull request cancels the run it supersedes. Observed: Ekklesia run 133 was
1.24. Job-token permission controls, repository/owner defaults, and server-side cancelled when run 134 started for the same pull request, and run 135 when run 137 did. A
maximum-permission clamping arrive in the 1.26 feature line superseded run's own cleanup is therefore never guaranteed to run, which is one reason for the
(<https://docs.gitea.com/1.26/usage/actions/token-permissions>). stack sweep below.
- **`concurrency` and `timeout-minutes` are not bounded-execution guarantees here.** Cancellation, - **`permissions` is supported by 1.27, with Gitea's own scopes, but is not verified here.** The
timeout, and teardown must be enforced by a trusted provisioner and reaper outside the workflow. server-side token-permission settings are not visible through the API, and nothing has yet proven
what the job token can and cannot do on this server. Until that proof exists (requirement 3
below), this repository still does not claim the `GITEA_TOKEN` is read-only.
- **`timeout-minutes` is no longer listed as unsupported for 1.27, but is not verified here.**
Teardown does not rely on it: the stack sweep and a trusted reaper do.
- **Still ignored:** `jobs.<job_id>.environment`, and any `runs-on` other than a single label.
The declarations are kept deliberately: they are already correct for a supported Gitea release and The declarations stay in the workflow either way: they state the intent, and are correct on GitHub
for GitHub Actions, and keeping them means the upgrade does not require re-deriving the intent. They Actions as well.
are documented as ignored, not removed.
### Disposable stacks never outlive their job ### Disposable stacks never outlive their job
@@ -793,16 +797,17 @@ they are not conditions on merging trusted, reviewed work.
None of them can be satisfied by editing this repository; each requires access to the Gitea server None of them can be satisfied by editing this repository; each requires access to the Gitea server
and runner hosts, and each must be proven there. and runner hosts, and each must be proven there.
1. Upgrade Gitea to a reviewed supported release with job-token permission controls — at least the 1. ~~Upgrade Gitea to a reviewed supported release with job-token permission controls — at least
1.26 feature line. Select the actual release during infrastructure work, after backup and the 1.26 feature line.~~ **Done:** the server runs 1.27.2. Items 2 and 3 now configure and prove
compatibility review; do not pin a version from this document. what it makes possible.
2. Configure repository and owner defaults, and the server-side maximum permission, so workflow 2. Configure repository and owner defaults, and the server-side maximum permission, so workflow
code cannot request more than read-only repository access. code cannot request more than read-only repository access.
3. Prove against the real server that the job's `GITEA_TOKEN` cannot push code, create or update 3. Prove against the real server that the job's `GITEA_TOKEN` cannot push code, create or update
releases, packages, issues, or pull requests, change Actions configuration, or reach another releases, packages, issues, or pull requests, change Actions configuration, or reach another
private repository. private repository.
4. Enforce job timeout, cancellation, concurrency, and teardown in the trusted 4. Enforce job timeout and teardown in the trusted provisioner/reaper. `concurrency` is honoured
provisioner/reaper rather than relying on the ignored workflow keys above. by the server; `timeout-minutes` is not yet verified, and the in-workflow stack sweep only
complements a reaper.
5. Complete a disposable-guest runner isolation proof: one throwaway guest per job, torn down by a 5. Complete a disposable-guest runner isolation proof: one throwaway guest per job, torn down by a
trusted reaper outside workflow YAML, holding no deployment credential, registration token, or trusted reaper outside workflow YAML, holding no deployment credential, registration token, or
reusable key. reusable key.
+4 -3
View File
@@ -6,9 +6,10 @@
# bash scripts/ci-sweep.sh --job this job's own stacks, at any age # bash scripts/ci-sweep.sh --job this job's own stacks, at any age
# #
# Every CI script already removes its own stack in an EXIT trap, but a trap never runs when the runner # Every CI script already removes its own stack in an EXIT trap, but a trap never runs when the runner
# kills the job container: a cancelled job, or the runner's own time limit (Gitea 1.24 ignores # kills the job container: a cancelled or superseded job (the server honours `concurrency`), or
# `timeout-minutes`). So each workflow job that starts stacks ends with `--job` in an `if: always()` # the runner's own time limit. So each workflow job that starts stacks ends with `--job` in an
# step, and begins with `--stale`, which catches whatever a killed job left before it could clean up. # `if: always()` step, and begins with `--stale`, which catches whatever a killed job left before
# it could clean up.
# #
# What a stack is, and how it is found, is defined in scripts/lib/ci-project.sh: a Compose project # What a stack is, and how it is found, is defined in scripts/lib/ci-project.sh: a Compose project
# named with the `ekklesia-ci-` prefix (Compose labels everything it creates with that name), plus # named with the `ekklesia-ci-` prefix (Compose labels everything it creates with that name), plus