CI / CD integration
seal verify is designed for CI. It is read‑only, never prompts, never makes network calls, and exits with the same well‑defined codes on every platform.
The simplest gate is one line:
seal verify --quietExit 0 permits the pipeline to continue under the configured policy. Exit 1 means a mismatch or unverified bundle under block. Exit 2 means a fatal configuration or system error, including a missing or invalid lockfile.
0 does not always mean everything matches: removed-only bundles produce Drift, and warn allows mismatches with Warning. Keep policy: "block" for a gate that fails on changed or newly discovered bundles.
Place the check before use
Section titled “Place the check before use”Run verification after installing or preparing the bundle files, then make it a required successful step before any pipeline step that consumes them. Use the same files and lockfile for the check and subsequent use.
Seal checks the checkout at that moment. It does not intercept an agent’s loads, verify later writes, or protect a developer session that already read changed instructions. Continuous enforcement requires integration in the agent’s load path.
Protect the approval baseline
Section titled “Protect the approval baseline”Review bundle changes together with seal.json, including edits to discovery and policy. Use required reviews and protected branches or equivalent controls to establish which snapshot is approved. Apply those controls to the CI workflow too.
If a pull request changes both a bundle and its hashes, seal verify can pass. The check establishes agreement with the supplied lockfile, not independent approval of that lockfile. Do not automatically re-pin in a verification job.
Seal also does not pin the CI actions, container images, or remote downloads used to install the verifier. Manage those dependencies through your normal CI supply-chain controls.
GitHub Actions
Section titled “GitHub Actions”name: CI
on: [push, pull_request]
jobs: seal: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- name: Install seal run: | SEAL_VERSION=v1.1.1 curl -L -o /usr/local/bin/seal \ "https://github.com/SamyGhannad/seal-cli/releases/download/${SEAL_VERSION}/seal-linux-amd64" chmod +x /usr/local/bin/seal
- name: Verify Seal lockfile run: seal verify --quietPinning to a specific tag is recommended for reproducibility; check the releases page before bumping SEAL_VERSION.
If you prefer building from source on every run (no asset fetch):
- uses: actions/setup-go@v5 with: go-version: 'stable' - run: go install github.com/SamyGhannad/seal-cli/cmd/seal@latest - run: seal verify --quietThe source-build examples use @latest for brevity. For locked-down CI, replace it with a reviewed commit such as @949c84e96b4926616ba7ec81fccbf055965201c8.
GitLab CI
Section titled “GitLab CI”seal-verify: image: golang:latest script: - go install github.com/SamyGhannad/seal-cli/cmd/seal@latest - seal verify --json > seal-report.json artifacts: when: always paths: - seal-report.json--json writes the report and returns the same gating exit code as the other output modes. One invocation is enough; when: always preserves the report when verification blocks the job.
CircleCI
Section titled “CircleCI”version: 2.1
jobs: seal: docker: - image: cimg/go:stable steps: - checkout - run: name: Install seal command: go install github.com/SamyGhannad/seal-cli/cmd/seal@latest - run: name: Verify Seal lockfile command: seal verify --quiet
workflows: build: jobs: - sealPre‑commit hook (local)
Section titled “Pre‑commit hook (local)”A .git/hooks/pre-commit (or pre‑commit) hook catches drift before it leaves your machine:
#!/usr/bin/env shset -eseal verify --quietchmod +x .git/hooks/pre-commit and the next commit runs the check.
For a pre-commit framework config:
repos: - repo: local hooks: - id: seal-verify name: seal verify entry: seal verify --quiet language: system pass_filenames: falseReading the JSON output in scripts
Section titled “Reading the JSON output in scripts”seal verify --json emits to stdout. Diagnostics still go to stderr, so piping is safe:
seal verify --json > seal-report.json 2> seal-report.logThe JSON output is stable:
{ "status": "Blocked", "verified": ["./.claude/skills/code-review"], "unverified": ["./.claude/skills/new-thing"], "removed": [], "mismatch": ["./.claude/skills/deploy-guard"]}statusis one of"Verified","Drift","Warning","Blocked".- The four arrays (
verified,unverified,removed,mismatch) are always present. - Arrays are sorted lexicographically by UTF‑8 bytes (NFC).
For local inspection, a jq one-liner lists every bundle that needs attention:
seal verify --json | jq '.unverified + .mismatch + .removed | unique'Do not use that pipeline as the CI gate without preserving Seal’s exit code: the shell can return jq’s success even when verification failed. Gate with a direct seal verify invocation.
Caching considerations
Section titled “Caching considerations”seal verify is fast (one SHA‑256 per file under each sealed root) but if you check out very large bundle trees, CI caches can help:
- Cache the Go install of
sealbetween jobs. - Do not cache
seal.jsonitself. It is part of the repository and must be the version under review.
Failing the build with a useful message
Section titled “Failing the build with a useful message”seal verify prints a human summary by default, but it goes to stderr. CI systems that only capture stdout will see nothing. Either:
- Use
--quietand rely on the exit code (recommended). - Use
--jsonand parse the result in the script. - Redirect stderr to stdout:
seal verify 2>&1.
Avoid running plain seal verify and relying on the human output in CI. The human format is meant for local terminals.
Common CI failure modes
Section titled “Common CI failure modes”| Symptom | Likely cause | Fix |
|---|---|---|
seal: verify: seal.json not found in current directory |
Job is running outside the repo root, or seal.json is missing. |
cd to the repo root before running; ensure seal.json is committed. |
seal: verify: invalid lockfile: … |
Lockfile is corrupted or out of spec. | Read the message and correct the invalid configuration; review changes before re-pinning. |
Exit 1 with status: "Blocked" |
A tracked bundle changed, or a new bundle appeared under discovery, without re‑pinning. |
Locally: git diff the bundle, then seal pin <path> if the change is intentional. |
Exit 2 after merging |
The lockfile is invalid, such as a recorded key not statically produced by discovery, nested bundle keys, or overlapping discovery patterns. |
Read the diagnostic, choose the intended boundary model, then re‑pin or prune and commit seal.json. |