The CI/PR gate
Everywhere else Vectadyne is advisory. Here it is a gate — and it is a gate because your CI system enforces it, not because Vectadyne reached into your pipeline.
That distinction is the whole design. You opt in, on the repositories you choose, for the actions you choose.
What it does
Section titled “What it does”On a pull request: describe the change as an action, get a verdict, fail the check on block or
require_approval.
warn passes. It posts the advisory and lets the PR through. A warn that failed a build would
be the inline gate this is designed not to be, moved into CI — and your team would learn to ignore
the check, which costs you the blocks too.
Use a machine principal, not a person’s token:
name: Vectadyne preflighton: pull_request
jobs: preflight: runs-on: ubuntu-latest env: VECTADYNE_MCP_URL: ${{ vars.VECTADYNE_MCP_URL }} VECTADYNE_CLIENT_ID: ${{ secrets.VECTADYNE_CLIENT_ID }} VECTADYNE_CLIENT_SECRET: ${{ secrets.VECTADYNE_CLIENT_SECRET }} steps: - uses: actions/checkout@v5 with: { fetch-depth: 0 } - name: Preflight the change run: ./scripts/vectadyne-preflight.shGitHub Actions OIDC is not accepted as a bearer. Exchange the client credential.
The principal needs preflight.action and memory.search. It does not need write capabilities —
a gate that can also write to the corpus is a gate that can be talked into rewriting the rule it is
enforcing.
The script
Section titled “The script”import os, subprocess, sysfrom vectadyne import Vectadyne, interpret_verdict
changed = subprocess.check_output( ["git", "diff", "--name-only", "origin/main...HEAD"], text=True).split()
with Vectadyne(mcp_url=os.environ["VECTADYNE_MCP_URL"]) as vd: assembled = vd.assemble( action_type="code.apply_patch", summary=os.environ.get("PR_TITLE", "pull request"), changed_files=changed, scope_key=os.environ["VECTADYNE_SCOPE"], purpose="code_review", )
d = interpret_verdict(assembled.verdict)
for w in d.warnings: print(f"::warning::{w}")
if not d.proceed: print(f"::error::{d.summary()}") for c in d.cited_memory: print(f"::error:: cited: {c}") sys.exit(1)Make the failure actionable
Section titled “Make the failure actionable”A failing check that says “blocked” makes you the bottleneck. Post the reasons and citations — which policy, which incident, which prior rollback. Then the author can fix it, or make a reasoned case that the memory is stale, without finding you first.
That second path matters: a block citing a stale_memory_conflict is often the corpus being wrong,
and the fix is a correction, not an override.
Start advisory
Section titled “Start advisory”Run it non-blocking first — report the verdict, never fail the build. Watch for a week or two.
You are looking for false blocks, which mean the corpus has stale or over-broad memory. Fix those
before making the check required, or the first week of enforcement will be spent on override requests
and the team will conclude the tool is noise.
Scope it to what deserves it
Section titled “Scope it to what deserves it”Not every PR needs this. The action taxonomy exists to let you gate the things that are hard to undo:
- migrations (
data.run_migration) - CI config changes (
vcs.modify_ci_config) - infrastructure (
deploy.modify_infra) - force-pushes (
vcs.force_push) - dependency changes (
code.modify_dependency)
A gate on everything is a gate people route around.