Vetrix Docs

Policy gates

Vetrix evaluates operator-defined policy gates at workflow-schedule time. Each gate is stored in the policy_gates table, carries an enforcement mode (warn or block) and a scope tier (instance / org / repo / environment), and is compiled by a pluggable Evaluator backend. The engine fetches every gate applicable to the run's scope ladder, evaluates each against a canonical PolicyInput, and either blocks the run (at least one block enforcement returned violations) or attaches banner annotations (warn enforcement).

This document covers the ship-today state:

  • Gate schema + CRUD semantics.
  • The default Vetrix native policy language evaluator.
  • Reusable-workflow nesting + input type-check enforcement.

A Rego/OPA-backed evaluator is tracked as a focused follow-up ticket so this page stays concise; the Evaluator interface is the plug-point for that work.

Schema

policy_gates (
  id           UUID PK
  scope        ENUM (instance | org | repo | environment)
  scope_id     UUID                       -- NULL for instance; required otherwise
  name         TEXT                       -- UNIQUE per (scope, scope_id)
  source       TEXT                       -- opaque to the DB; parsed by the evaluator
  enforcement  ENUM (warn | block)
  created_by   UUID REFERENCES users(id)
  created_at   TIMESTAMPTZ
)

The policy_gates table is provisioned by Vetrix's database migrations.

Enforcement

Mode Effect on a run that violates the gate
warn Run is scheduled; the detail page shows a banner listing
the rule names and messages.
block Run is not scheduled; workflow_runs.status becomes
blocked and the description lists the violations.

ShouldBlock(decisions) returns true iff at least one decision carries enforcement = block AND a non-empty violations list.

Scope ladder

A run is evaluated against gates in every applicable tier:

  1. Instance (scope_id = NULL)
  2. Org (scope_id = org_id)
  3. Repo (scope_id = repo_id)
  4. Environment (scope_id = environment_id when the run is bound to one)

Service.Evaluate walks all matching gates, not just the most-specific tier — an org-level policy cannot be bypassed by adding a narrower gate.

Vetrix native policy language

# Every rule is one line. `#` comments are stripped before parsing.
#
# Rule: allowed_uses — every `uses:` on a job must match at least one
# glob. * matches [^/]*, ** matches anything including /.
allowed_uses = vetrix/*@*, github.com/allowed/**@*

# Rule: required_attestation — each declared kind must appear on at
# least one artifact attached to the run.
required_attestation = slsa-provenance, sbom

# Rule: deny_image — any job whose image matches the regex fails the
# gate.
deny_image = ^untrusted/.*$

# Optional: override the default violation message for a rule.
rule_msg uses_not_allowlisted = Contact sec-team before adding actions

Sandbox

Parse-time refusal on any of:

http.send, http.post, http.get, io.read, io.write,
os.exec, os.env, fs.read, fs.write

These tokens cover the Rego surface a copy-pasted OPA policy might smuggle in. The operator sees policy_sandbox_violation at create time — never at eval time.

Evaluation budget

Each gate runs under a wall-clock budget (default 500 ms). Exceeding surfaces policy_evaluation_timeout on the run; other gates still run.

Reusable-workflow resolver

uses: owner/repo/.vetrix/automations/foo.yml@SHA is resolved at ingest time by ResolveReusable:

  • Max nesting depth = 1. A reusable workflow that itself references another reusable workflow surfaces reusable_workflow_nesting_exceeded.
  • Input type-check. The caller's with: values must satisfy the declared types in the callee's on.workflow_call.inputs block. Mismatched types surface reusable_workflow_input_type_mismatch; an unknown with: key surfaces reusable_workflow_unknown_input; missing required inputs surface reusable_workflow_required_input_missing.
  • Callee must declare workflow_call. The on.workflow_call trigger is mandatory on the callee; otherwise ingest fails.

The resolver is purely source-to-source; the callee's content is supplied via a LoaderFunc so the resolver has no filesystem or network access.

Deferred follow-ups

  • Full OPA Rego interpreter backend + opa go-module dependency.
  • Admin CRUD REST surface + UI (/api/v1/admin/policy-gates).
  • PgStore implementation (today: MemStore in tests and dev stacks; the Store interface is the plug-point).
  • Per-env frontend policy editor.