Project manifest (forge.yaml) & pipeline files
FORGE writes a project’s layout into forge.yaml at the repository root. CORE only reads it, to verify each component separately. This page gives the manifest spec exactly as CORE’s validator (grpc/cmd/forgecheck/manifest.go) enforces it, and describes the narrow check CORE applies to RIVER pipeline files.
forge.yaml, spec v1
A FORGE project is one repository with several lanes. Each lane is a component folder. When a forged repository (web or pipeline, never forge itself) carries a forge.yaml, central verification ignores the repository’s topic profile and verifies every lane in its own folder. See Central verification.
version: 1
project: social-ops # the repo name (informational)
lanes:
- id: source-mapping # ^[a-z0-9][a-z0-9-]{0,38}$, unique
name: Source mapping # free text, <= 80 characters
kind: pipeline # pipeline | web
path: pipelines/source-mapping # relative + clean, unique, never nested
components: [source.postgres] # catalog ids — informational, ignoredFields
| Field | Rule |
|---|---|
version | Must be the integer 1. A missing version is refused with “no version (want version: 1)”. A quoted "1" or a 1.5 is refused with “version … is not supported (want 1)” |
project | Informational. Not checked |
lanes | At least one lane, at most 50 |
lanes[].id | Must match ^[a-z0-9][a-z0-9-]{0,38}$, and must be unique. It becomes the status context core/ci/<id> |
lanes[].name | Free text, at most 80 characters |
lanes[].kind | pipeline or web |
lanes[].path | A relative, clean folder inside the repo. Not empty, not absolute, no trailing /, no backslash, NUL or control character, no .. segment, and unchanged by path cleaning (no ./, // or trailing .). Paths must be unique, and no lane may sit inside another lane’s folder. "." is allowed only when there is exactly one lane |
lanes[].components | Catalog ids. Informational, ignored by the verifier |
Unknown keys are ignored, at the top level and per lane, so FORGE can add fields before CORE learns about them. Anything else that is wrong makes the whole manifest invalid. Verification then posts core/ci = failure with the description forge.yaml is invalid: <first reason> and no per-lane status, because a half-read manifest could report on components FORGE did not mean.
The file itself must be:
- a regular file. A symlink is refused as “forge.yaml is a symlink; it must be a regular file”;
- no larger than 1 MiB;
- a single YAML document. A second document is refused as “more than one YAML document”.
A two-lane example
This manifest passes forgecheck manifest. It was checked against the validator built from this repository:
version: 1
project: social-ops
lanes:
- id: source-mapping
name: Source mapping
kind: pipeline
path: pipelines/source-mapping
components: [source.postgres]
- id: dashboard
name: Operations dashboard
kind: web
path: apps/dashboard$ forgecheck manifest -file forge.yaml
lane source-mapping pipeline pipelines/source-mapping
lane dashboard web apps/dashboard
forgecheck: forge.yaml is valid, 2 lane(s)If neither folder exists yet, the manifest is still valid, and both lanes report “Nothing to verify yet”. FORGE declares a lane before its agent writes it.
What each lane kind must contain
| Kind | Verified when the folder has… | Otherwise |
|---|---|---|
pipeline | .dsl / .herd files (TOML-parsed) and/or Go packages of a module, meaning its own go.mod or the repo’s root go.mod above it (go build, go vet, go test ./...) | Nothing to verify yet: no .dsl or .herd file and no Go package in <path> |
web | a go.mod in the folder (go build/vet/test ./...) and/or a package.json in the folder or in its web/ subfolder (npm ci, npm run build, and npm run lint when the package declares a lint script) | Nothing to verify yet: no go.mod or package.json in <path> |
Pipeline files (.dsl, .herd)
RIVER pipeline definitions are TOML files, features/*.dsl and herd/*.herd. CORE’s check on them is syntax only:
- every
*.dsland*.herdfile under the tree must parse as TOML 1.0 (github.com/pelletier/go-toml/v2); - the meaning of the keys (
[metadata],[dag],[[dag.step]],[golden]) belongs to the shared RIVER library, which does not exist yet, so CORE does not check it; .contractfiles are not checked, because no parser for that format exists yet;- the scan never enters
.git,.github,node_modules,vendor,testdata, or any directory whose name starts with.or_.
This documentation gives no pipeline grammar beyond “valid TOML”, because CORE does not define one. A file that fails to parse is reported by file with its position:
::error file=bad.dsl::not valid TOML: line 1, column 5: toml: array is incomplete
ok ok.herd
forgecheck: 2 pipeline file(s), 1 invalid; 0 Go package(s)
::error::1 of 2 pipeline file(s) not valid TOML, first: bad.dslAn empty pipeline (no .dsl/.herd file and no Go package) is a failure with the description Nothing to verify yet: no .dsl or .herd file and no Go package. It is deliberately not a skip: a skipped check concludes success in GitHub Actions, and FORGE would show a pipeline with nothing in it as green.
See Verification profiles for the exact commands.