- Python 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
All checks were successful
ci / selftest (push) Successful in 14s
actions/checkout persists the Actions job token as http.extraheader, which overrides the ci-bot credentials in the remote URL. Since olsitec-foundation-T-0069 protected every default branch (13:00Z), Forgejo pre-receive fails for the Actions user (uid -2): "Unable to GetUserByID ... user does not exist", 500, "pre-receive hook declined" on infra/pulumi_modules_kubernetes run 2845 (14:59Z, rerun failed the same). ci-bot has write on that repo (permission API, 15:10Z). Incident fix by olsitec-foundation-lead-073. Undo: git revert of this commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
| .forgejo/workflows | ||
| ci | ||
| templates | ||
| README.md | ||
foundation-ci/ci_workflows — the forge CI every Olsitec repo ports onto
Reusable Forgejo Actions workflows (on: workflow_call), plus copy-ready caller templates.
They are the forge port of the GitLab include olsitec/gitlab/ci_templates (that repo stays on
GitLab; the port was read at 1.20.7, fef904d).
- Moved here from
foundation/foundation/.forgejo/workflows/by olsitec-foundation-T-0045, with history: 30 commits replayed withformat-patch/am, and the moved blobs are byte-identical to foundationdb7645e. - Changed here by olsitec-foundation-T-0052 (P-CI). The old copies in
foundation/foundationare frozen and stay until a forge-wide census shows no caller left. - Porting a board's repos: read the kit at
items:task-support/olsitec-foundation-T-0052/(olsitec-foundation board) first.
uses: foundation-ci/ci_workflows/.forgejo/workflows/reusable-<name>.yml@master # or @<sha> to freeze
Where this repo lives, and why it is public (olsitec-foundation-T-0055)
This repo is foundation-ci/ci_workflows. It moved here from foundation/ci_workflows on
2026-10-16, by a repo transfer, so the private-registry org foundation could become
signed-in only. The foundation-ci org is public on purpose: forgejo-runner (v12.12.0,
measured) fetches a cross-org uses: reusable workflow anonymously, so the owner of
this repo must stay readable without a token. Keep packages and product code out of
foundation-ci.
- Callers write
uses: foundation-ci/ci_workflows/.forgejo/workflows/<file>@<ref>. - The old path
foundation/ci_workflowsstill resolves, through Forgejo's transfer redirect. That is a safety net, not an address: port every caller to the new path. - Never create a repo named
foundation/ci_workflowsagain. A repo under the old name deletes the redirect, and every caller still on the old path fails at theuses:fetch.
Templates (copy one to .forgejo/workflows/ci.yml)
| Template | Ports the GitLab includes | Jobs |
|---|---|---|
templates/service-image.yml |
common + lint + container-build + semantic-release | lint, test, plan (dry-run), image :<sha> stamped with the planned version, version, version-match, promote :X.Y.Z |
templates/library-npm.yml |
common + lint + semantic-release + npm/bun publish | lint, test, version, publish (from the release tag) |
templates/lint-only.yml |
common + lint | lint |
release-version (service-image.yml)
plan runs semantic-release as a dry-run before image. Its next-version stamps the image
(reusable-docker-build input release-version: package.json version rewritten, plus
--build-arg RELEASE_VERSION), and version refuses before tagging if it would release
anything else (reusable-semantic-release input expected-version). version-match re-checks
after the release, and promote needs it. GitLab's kaniko template did the same stamping.
What the guard covers, and what it does not:
-
An empty
expected-versionmeansversionis unguarded. The template passes plan'snext-version, which is empty when plan computed no release. If history then moves between plan andversion(a tag deleted, a branch reset) so thatversiondoes release, it tags and releases an image stamped with nothing; onlyversion-matchgoes red, after the tag. -
"Refuses before tagging" holds for every mismatch present at gate time. The gate's dry-run and the Release step's real semantic-release are two invocations seconds apart; a tag landing between them still yields a tag at the other version (then "Determine released version" fails and no Release object is created).
-
Pin at or after the commit that added
release-version/expected-version(or@master). At an older sha both inputs are silently dropped: the image ships the Dockerfile'sARG RELEASE_VERSIONdefault and the previous release's package.json version. -
A red
planskipsimage. plan carries the tag-line gate; when it refuses, no:<sha>image is built either (before this change it was). Fix the tag line. -
A caller that passes
RELEASE_VERSION=inbuild-argsowns the version: package.json and the build-arg are then left alone. On that path the image carries the caller's build-arg and the previous release's package.json version, so the two can differ; drop the line to have both stamped with plan's version.
Local GitLab jobs (tests, services, artifacts, schedules) are NOT in any template. You port them
yourself, one job each, and list them in the needs: of the release path.
Building blocks
| Workflow | Does | Key inputs → outputs |
|---|---|---|
reusable-lint.yml |
eslint (pipeline eslint 10) + yamllint gate, advisory hadolint | eslint, yamllint, hadolint, package-manager |
reusable-node-build.yml |
strict install + build/test command | package-manager, build, workdir |
reusable-docker-build.yml |
install guard, docker build, push, registry read-back of the digest |
image, push, … → digest |
reusable-semantic-release.yml |
dry-run + tag-line gate, then tag/push/Forgejo Release | repo, branch, dry-run → version, next-version, last-version |
reusable-image-promote.yml |
copies the tested digest to :X.Y.Z (+ :X.Y, :X, :latest while newest), no rebuild, never overwrites |
image, digest, version |
reusable-npm-publish.yml |
strict install, build, npm publish to the forge registry, read-back |
package-manager, build, ref |
reusable-helm.yml |
helm lint/package/publish: OCI push to oci://forge.olsitec.net/<org> by default (FORGE_REGISTRY_USER/TOKEN), or the org's http helm registry with registry: http (FORGE_PACKAGE_TOKEN); a present version is skipped, never overwritten |
charts-dir, publish, registry |
What T-0052 changed
Each change is enforced by a test in ci/ that ci.yml runs on every push, green and red.
-
Strict frozen install (olsitec-foundation-T-0019 decided: frozen only, fail closed).
- The install runs
bun install --frozen-lockfileornpm cionly, never|| bun install. - A missing lockfile is red.
- A lockfile that still resolves
@olsitec/*from the GitLab npm registry is red, so it must be regenerated against the forge registry. ci/test-strict-install.pychecks that the block is identical in every reusable and runs it against stub package managers.
- The install runs
-
One npm registry for every org:
https://forge.olsitec.net/api/packages/foundation/npm/(@olsitec:registry,FORGE_PACKAGE_TOKEN). -
Olsitec
releaseRules, as on GitLab:ci,doc,docs,chore,build,fix,perf,refactor,styleandtestgive a patch release;featandfeaturegive a minor release;- breaking changes give a major release.
- The forge copy lacked
doc;ci/test-tag-line-gate.pynow provescianddocboth give a patch release, using the real semantic-release.
-
Tag-line gate. Every release first runs
semantic-release --dry-runand refuses when:- the next version is not strictly greater than the highest
X.Y.Z/vX.Y.Ztag on any branch, or - the next version already exists as a tag.
This catches a migrated line that semantic-release cannot see from the branch.
dry-run: truestops after the gate, so publishing stays fenced. - the next version is not strictly greater than the highest
-
The tested image is the released image.
- The build pushes
:<sha>and outputs the registry digest. reusable-image-promotepoints:X.Y.Zat that digest. This is GitLab parity (olsicloud4-T-102): the old forge pattern rebuilt the image for the release tag.- The promote copies the exact manifest bytes through the registry API.
docker buildx imagetools createmust not be used here: it wraps a single manifest in a new OCI index with a different digest. That was measured on services_forge-notifier run 6, where the read-back caught it.ci/test-image-promote.pycovers this case. - Neither the Dockerfile's
bun installwithout--frozen-lockfilenor a GitLab-resolving lock in the build context can get past the docker build.
- The build pushes
Forge facts the callers depend on
These are kept from the foundation README; see its history for the measurements.
- A
uses:job needsruns-on:, andsecrets: inheritis required; without it${{ secrets.X }}is empty inside the called workflow. - Cross-repo references use the short form
owner/repo/.forgejo/workflows/x.yml@ref. - A caller's
runs-ondoes not move auses:job's placement; the reusables run ondocker. - Filter
on: pushto the release branch, because the release tag push is itself a push. - Runner v12.12.0 fetches
@masteron each run. A SHA pin freezes behaviour. - A nested reusable (a
uses:inside aworkflow_call) is forbidden, because it ran hollow green on runner v6.4.0.ci/test-no-nested-reusable.pyenforces this. - An org gets the
ci-botteam and theFORGE_*org secrets only when it isciEnabledin foundationorgs/index.ts.