A worked CI pipeline, end to end
One repository, one workflow, from install to a gated land.
Written By Dustin
Last updated About 3 hours ago
A complete GitHub Actions workflow that checks out a TOVIO repository with no stored secret, runs a build, and gates the land. Adapt the names; the shape is the point.
1. Trust the runner's issuer, once
tovio agent issuer-policy ci-main \ --issuer https://token.actions.githubusercontent.com \ --audience tovio-forge \ --subject repo:my-org/my-repo:ref:refs/heads/main \ --path "src/**" --path "tests/**" \ --op read --op relay:fetch \ --max-session-seconds 900 \ --file ci-main.vexCommit ci-main.vex to the served repository at .tovio/forge/runner-issuers/ci-main.vex. The owner key stays on your machine.
2. The workflow
name: CIon: [pull_request]permissions: contents: read id-token: writejobs: build: runs-on: ubuntu-latest steps: - name: Install a verified TOVIO release id: tovio uses: Tovio-VCS/tovio/packages/tovio-ci-bridge/setup@<full-commit-sha> with: version: 1.0.0 env: GH_TOKEN: ${{ github.token }} - name: Checkout the change uses: Tovio-VCS/tovio/packages/tovio-ci-bridge@<full-commit-sha> with: operation: checkout tovio-path: ${{ steps.tovio.outputs.tovio-path }} remote: https://forge.example.test repository-id: ${{ vars.TOVIO_REPO_ID }} destination: ${{ github.workspace }} sparse: | src/** tests/** oidc-policy: ci-main oidc-audience: tovio-forge timeout-seconds: "900" - name: Conflict-free gate working-directory: ${{ github.workspace }} run: ${{ steps.tovio.outputs.tovio-path }} build-check - name: Build and test working-directory: ${{ github.workspace }} run: | cargo build --release cargo test --workspaceThe gate is a plain run: step, not a bridge operation — the bridge's operation set is closed to checkout, sync, git-bridge, health, and status, and health is a diagnostic that exits 0 even on a conflicted lane. build-check is the one that actually fails.
3. What each piece is buying you
4. Make the check required
Declare the check as required on the target lane. The land gate is default-closed: a required check that is missing counts as pending and blocks, so a runner that crashes blocks the land rather than letting the change through.
5. Reading a failure
The wrapper preserves the child's output and never replaces a successful child exit with an invented result. A bridge-level refusal is TVO-CI-007 (bad input, nothing started) or TVO-CI-008 (the child would not start, exited non-zero, or timed out). A more specific TOVIO code from the CLI itself appears in the preserved output — read that one first.
Variations
- No OIDC available? Export a passphrase-encrypted runner bundle and mount the bundle and passphrase as two separate secret files, passing their paths.
- Spacelift? Same executable from a
before_inithook:tovio-ci-bridge syncthentovio-ci-bridge health. - Mirroring to Git? The
git-bridgeoperation always runs bidirectionally and never force-pushes or collapses the two ref namespaces.