Every operation reachable from contentops.cli has a GitHub Actions
workflow that wraps it (or is documented as local-only). This is the
single source of truth for "what's automatable."
| CLI command | Workflow file | Trigger | Notes |
|---|---|---|---|
contentops plan |
validate.yml |
PR | Enforces dependency graph + lint at merge gate. The --against-tenant flag is local-only (closes G17 — apply-side CREATE/UPDATE/NO-CHANGE/ORPHAN preview against the live workspace). |
contentops apply (prod) |
deploy.yml |
push to main (skipped on chore(collect|drift): and bot commits) + manual |
Uses --changed-since; --role prod; uploads audit JSONL artefact. Subscription resolved from config/tenant.yml, not vars.AZURE_SUBSCRIPTION_ID. |
contentops apply (integration) |
integration-deploy.yml |
PR on detections/** + manual |
--role integration --continue-on-error. No-ops when no integration workspace exists. Dispatch defaults to dry-run. --role test available for dedicated-test workspaces (G21). |
contentops drift |
drift.yml |
daily 06:00 UTC + PR on detections/** + manual |
Scheduled: auto-PR via peter-evans/create-pull-request@v6. PR mode: informational comment via gh pr comment (no auto-PR). |
contentops drift-pr-body |
(internal helper) | n/a | Called from drift.yml scheduled mode, not exposed as a top-level workflow. |
contentops drift-resolve |
none — purely local | n/a | Per-rule reconciliation (--strategy git|remote|merge). |
contentops collect |
collect.yml |
weekly Mon 06:00 UTC + manual | Supports role, full, since, workers inputs; opens PR with snapshot diff. Allowlist auto-sync step runs between collect and tree-change detection. |
contentops clean |
none — purely local | n/a | Destructive — wipes local detection YAMLs. Pairs with collect --clear. |
contentops prune |
prune.yml |
manual only | Environment-protected; defaults to dry-run; audit artefact uploaded. |
contentops bootstrap |
(called from promote-to-integration.yml) |
manual | Idempotent first-run setup for a new env. |
contentops lock / contentops unlock |
lock-unlock.yml |
manual | Mutates YAML on disk → opens a PR for review. |
contentops retry-failed |
retry-failed.yml |
manual | Re-applies failed audit records; dry-run option. |
contentops disable |
emergency-disable.yml |
manual | Gated, breakglass workflow. |
contentops enable |
none — purely local | n/a | Inverse of disable; selectors mirror disable (positional id, --pattern, --cohort). |
contentops portfolio |
portfolio.yml |
nightly + manual | Renders CSV + JSON; uploaded as artefact. With --with-telemetry: F20 columns from LA workspace. |
contentops coverage |
coverage.yml |
PR (paths detections/, contentops/) + manual |
Renders MITRE ATT&CK heatmap; posts sticky PR comment + uploads JSON artefact. --d3fend flag (local-only) renders the defensive-axis MITRE D3FEND companion report. |
contentops lint |
lint.yml + validate.yml |
PR + push to main + nightly + manual | Standalone KQL + envelope lint. Both workflows pass --strict on PRs (KQL101 policy rule + optional Kusto.Language wrapper). |
contentops test --live |
integration.yml |
label run-integration + manual |
Hits the live tenant; gated to avoid accidental prod runs. Full runbook: docs/development/live-integration-tests.md. |
contentops audit verify |
audit-verify.yml |
weekly Mon 04:00 UTC + on-PR-touching-audit/ + manual | Hash-chain integrity check. |
contentops audit query |
none — purely local | n/a | Read-only forensic queries (latest, failures, by-actor, rollbacks, timeline). |
contentops explain |
none — purely local | n/a | Single-rule context: envelope + dependencies + state + recent audit + drift. |
contentops silent-rules |
silent-rules.yml |
weekly Mon 07:00 UTC + manual | Reports rules with zero alerts/incidents in the lookback window (F4). |
contentops auto-disabled-rules |
silent-rules.yml (same workflow, sequence step) |
weekly Mon 07:00 UTC + manual | NVISO Part 7. Surfaces rules Sentinel auto-disabled (SentinelHealth.Status in ("Disabled","Failure")) and rules with recent query failures (LAQueryLogs). Prerequisite: SentinelHealth diagnostic enabled (probed by contentops doctor --auth). |
contentops tuning preview |
tuning-impact-preview.yml |
PR on detections/drift_suppressions.yml |
NVISO Part 8. Posts/updates a PR comment with 30-day blast-radius for each new suppression entry. Fork-PR safe (--no-workspace-query falls back to dashes). |
contentops navigator |
none — purely local / on-demand | n/a | Renders MITRE ATT&CK Navigator layer JSON across three axes (repo + deployed + firings). Upload to https://mitre-attack.github.io/attack-navigator/. |
contentops detection-docs regenerate / check |
none — local; CI gate inside the pytest suite | n/a | NVISO Part 4. Per-detection markdown pages under docs/detections/<asset>/<id>.md. Byte-identical drift gate (test test_committed_detection_docs_are_in_sync). |
contentops restore |
none — purely local | n/a | DR inverse of contentops collect. |
contentops snapshot-diff |
none — purely local | n/a | Content-aware diff between two collect archives. |
contentops rollback |
none — purely local | n/a | Replays the YAML at SHA against the tenant. Defaults dry-run. |
contentops lifecycle promote |
none — purely local | n/a | Promote experimental → production after gates pass. |
contentops state sync (push/pull/status) |
called from workflows that need state | n/a | Orphan-branch refs/heads/state/<env> convention. |
contentops state show / state forget |
none — purely local | n/a | State file inspection / surgical drop. |
contentops defender-extensions-probe |
none — wire to cron on demand | n/a | Secondary-signal probe of three Defender Graph endpoints (savedQueries / detection-tuning / alert-suppression). |
contentops defender-roundtrip-diff |
none — purely local | n/a | Diagnostic for verified=False on a Defender rule. Read-only. |
contentops sentinel-roundtrip-diff |
none — purely local | n/a | Sentinel counterpart to defender-roundtrip-diff. |
contentops new |
none — purely local | n/a | Scaffolds files; no remote interaction. |
contentops doctor |
none — purely local | n/a | Diagnostic; runs as a step in CI smoke (ci.yml). With --auth: hits tenant for token / workspace / SentinelHealth / Graph probes. |
contentops test (no --live) |
ci.yml |
PR + push to main | Unit suite. |
(script) scripts/check_references.py |
references-check.yml (full corpus, Saturdays 06:00 UTC) + validate.yml (added URLs only, PR-time) |
weekly + PR | NVISO Part 3. HEAD-checks URLs in metadata.references[] + runbookUrl. PR-time variant uses --diff-base to walk only added URLs. |
(tool) codespell |
spelling.yml |
PR on prose paths | NVISO Part 3. Catches typos in author-controlled prose. Config: .codespellrc. |
| Workflow | Purpose |
|---|---|
promote-to-integration.yml |
Manual bootstrap → collect (prod) → rewrite → apply (integration) chain. Confirmation gate confirm == 'PROMOTE'. |
production-promotion-check.yml |
Asserts no analytic rule is being promoted to production without the right metadata flags. |
release.yml |
On v* tag push: build sdist tarball, render changelog, create GitHub Release. |
Every destructive workflow follows these rules (verified by
docs/operations/prune.md and the lock-unlock.yml /
retry-failed.yml review):
- OIDC for Azure auth;
azure/login@<sha>pinned by full SHA. environment:input so the GitHub Environment matches the tenant slug — production gets reviewer-gated approval.- Dry-run default for any workflow that can mutate state.
contentops pruneandpromote-to-integrationboth default to dry-run. - Audit artefact upload on every workflow that writes records:
actions/upload-artifact@<sha>, 90-day retention. - Structured
$GITHUB_STEP_SUMMARYwith the inputs used and the result. concurrency:group on env-scoped workflows so two simultaneous runs against the same tenant queue rather than race.
| Command | Why no workflow |
|---|---|
contentops new |
File-system scaffold; analyst-driven. |
contentops doctor |
Diagnostic-only; embedded in ci.yml as a smoke step. |
contentops audit query |
Read-only forensic; CI gate is on audit verify, not on queries. |
contentops explain |
Local context surface; not automatable. |
contentops clean |
Destructive local-state operation; analyst-driven. |
contentops drift-resolve |
Per-rule reconciliation with strategy choice — requires operator judgment. |
contentops defender-roundtrip-diff |
One-off diagnostic for MISMATCH triage. |
contentops sentinel-roundtrip-diff |
One-off diagnostic for MISMATCH triage on Sentinel rules. |
contentops rollback |
Defaults dry-run; requires operator + --yes to actually mutate. |
contentops navigator |
Operator-triggered layer generation; output uploaded to the hosted Navigator UI. Could be wired to a scheduled cron if a SOC dashboard wants periodic refreshes. |
contentops detection-docs regenerate |
Run alongside catalog regenerate; the drift gate inside pytest catches forgotten regens. |
When you add a new CLI subcommand, this matrix is the contract:
- Decide whether it has a remote side effect.
- If yes: add a workflow under
.github/workflows/<command>.ymlfollowing the conventions above. Pin every action to a SHA. - If no: add it to the "Local-only commands" table here with a reason.
- Update this document. CI doesn't enforce the matrix today, but keeping it in sync is a code-review expectation.