One table for the 26 GitHub Actions workflows in
.github/workflows/. Use this when you can't remember which one fires on what trigger, what permissions it needs, or how long it usually takes.When in doubt about a workflow's behaviour, read the file — comments in the YAML are the canonical contract. This index is a navigator, not a substitute.
The pipeline separates concerns: one workflow per operational
contract rather than one per step. So apply to prod lives in
deploy.yml, apply to integration in integration-deploy.yml,
and apply to integration from a snapshot of prod in
promote-to-integration.yml — each has different inputs, different
safety rails, different audiences. Consolidating would save lines
but lose the operator's "which workflow do I click" mental model.
| Category | Workflows |
|---|---|
| Deploy & apply | deploy.yml, integration-deploy.yml, promote-to-integration.yml, retry-failed.yml |
| Read-only PR gates | ci.yml, validate.yml, lint.yml, dco.yml, sast.yml, secret-scan.yml |
| Operational | prune.yml, lock-unlock.yml, emergency-disable.yml, rollback (CLI only) |
| Continuous monitoring | drift.yml, collect.yml, silent-rules.yml, coverage.yml, portfolio.yml, conformance.yml, audit-verify.yml, defender-graph-probe.yml |
| Release & mirror | release.yml, public-sync.yml |
| Validation suites | e2e-capability-tests.yml, production-promotion-check.yml |
| File | Trigger | Purpose | Key permissions | Typical runtime |
|---|---|---|---|---|
audit-verify.yml |
weekly cron | Run contentops audit verify over the full hash chain. Fails if any record's prev_hash / record_hash / timestamp regresses. |
contents: read |
~1 min |
ci.yml |
PR + push to main | Full pytest suite (pytest -n auto via xdist) plus pip-audit. |
contents: read |
~3–5 min |
collect.yml |
weekly cron | Run contentops collect --full against production; commit any drift back as a chore(collect) PR. |
contents: write, pull-requests: write, id-token: write |
~10 min |
conformance.yml |
weekly cron | Run L1–L7 conformance read-only checks against the configured tenants. Reports skipped layers when GH CLI / secrets are absent. | contents: read, id-token: write |
~3 min |
coverage.yml |
weekly cron | Generate the MITRE ATT&CK coverage report from detections/ + contentops/coverage/data/. Uploads as artifact. |
contents: read |
~2 min |
dco.yml |
PR | Every commit must carry a Signed-off-by: trailer. Dependabot / Renovate / github-actions[bot] allowlisted. |
pull-requests: read |
<30 s |
defender-graph-probe.yml |
weekly cron | Probe the Defender Graph beta endpoints (deferred features) to detect when MS ships them GA. Reports to step summary. | id-token: write, contents: read |
~2 min |
deploy.yml |
push to main (paths: detections/**) + manual |
Apply changed detection content to every prod-role Sentinel workspace + Defender. Includes post-deploy smoke + audit-verify gate. Skips bot-authored mirror commits. | id-token: write, contents: write (state branch) |
~5–15 min |
drift.yml |
daily cron + PR comment | Compare tenant ↔ detections/; open / update a drift PR when remote state diverges. Two jobs: drift-pr (read-only diff) + drift-write (commit if drift). |
contents: write, pull-requests: write, id-token: write |
~5 min |
e2e-capability-tests.yml |
weekly cron + manual | Full CLI capability matrix (every leaf command × offline / mocked / live mode). Non-destructive sandbox. | contents: read, id-token: write (live mode) |
~10 min |
emergency-disable.yml |
manual | Break-glass: branch + contentops disable <rule> + commit + open PR. Wall-clock target <5 min from dispatch to opened PR. Does NOT auto-apply. |
contents: write, pull-requests: write |
~1 min |
integration-deploy.yml |
PR (paths: detections/**) + manual |
Deploy changed detections to the integration workspace as a smoke test. PR-time --changed-since + --continue-on-error so one broken rule doesn't block a merge. |
id-token: write, contents: read |
~3–10 min |
integration.yml |
manual + scheduled (DANGER flag) | Live tenant integration tests. Requires explicit ack input. | id-token: write, contents: read |
~10 min |
lint.yml |
PR | Standalone KQL + envelope lint, no Azure auth. Lighter-weight than validate.yml. |
contents: read |
~1 min |
lock-unlock.yml |
manual | Wrapper around contentops lock / unlock. Mutates a YAML file and opens a PR for review. |
contents: write, pull-requests: write |
~1 min |
portfolio.yml |
weekly cron | Generate per-detection CSV + JSON + summary. Posts summary to a tracked file or issue. | contents: read, pull-requests: write (summary) |
~2 min |
production-promotion-check.yml |
post-merge to main | Validation gate that runs after merge but before deploy proceeds. Sanity checks state of detections + tenant config. | contents: read, id-token: write |
~2 min |
promote-to-integration.yml |
manual | Snapshot the prod tenant via collect, then apply to integration. Used to validate integration workspace parity with prod. |
id-token: write, contents: read |
~10 min |
prune.yml |
manual | Delete remote orphans (tenant-side resources that no longer have a YAML in repo). Defaults to dry-run; requires CONFIRM input for real deletes. Environment-protected. |
id-token: write, contents: read |
~3 min |
public-sync.yml |
daily cron + manual | Rebuild KustoKing/ContentOps (public mirror) from the allowlist. Forbidden-path safety check. One-way. |
contents: write on the mirror (separate token) |
~3 min |
release.yml |
tag push | Release automation (tag → GitHub Release). | contents: write |
~2 min |
retry-failed.yml |
manual | Read the latest audit JSONL, derive (asset, id) pairs with status == failed, re-apply only those. |
id-token: write, contents: read |
~3 min |
sast.yml |
PR | bandit + semgrep static analysis. | contents: read |
~2 min |
secret-scan.yml |
PR + weekly cron | gitleaks scan. PR uses incremental (fetch-depth 50); cron does a full-history scan. Uses repo .gitleaks.toml. |
contents: read |
~1 min PR / ~3 min cron |
silent-rules.yml |
weekly cron | Report rules that fired zero alerts over the recent window. | id-token: write, contents: read |
~3 min |
validate.yml |
PR | Envelope parse + lint without Azure auth — the lightest PR gate. | contents: read |
<1 min |
Two reusable composite actions live under .github/actions/:
| Path | Purpose |
|---|---|
.github/actions/pipeline-setup/action.yml |
Checkout + setup-python@v5 + pip install + azure/login@v2 (OIDC). Used by every workflow that touches Azure. Materialises config/tenant.yml from secret. |
.github/actions/notify-workflow-failure/action.yml |
Post a step-summary block on failure with the run URL + trigger context. Used by long-running workflows. |
CLAUDE.md invariant: branch protection on main requires the
following checks: dco, spdx-headers, bandit, semgrep,
cli-smoke, pytest, gitleaks, actionlint. These names map
to job names inside the workflow files (not the workflow filenames),
e.g. bandit and semgrep are jobs inside sast.yml; cli-smoke
pytestare jobs insideci.yml;actionlintis a job inside its own check (verify in repo settings before assuming).
When adding a new workflow that should be required for merge, update branch protection in the repo settings — this index does not auto-promote anything.
generated-catalog.md— auto-generated catalog of every CLI command, lint rule, handler, and workflow; drift-checked by CI.audit-trail.md— theaudit/*.jsonlschema thataudit-verify.ymlvalidates.architecture.md— how the workflows fit into the larger handler / envelope / state design.