Description
Migrate bumblebee-intune’s task surface to just
This task applies the frozen fleet JUST-FLEET-STANDARD.md (§1–§13) to this repo’s actual
inventory. Do not re-litigate the standard — every rule below is a direct application of it, not a
local variant.
1. Outcome
A top-level justfile exists with all seven mandatory recipes (default, setup, fmt,
fmt-check, lint, test, check) plus a docs-check recipe that replaces the inline Python
heredoc currently living inside .github/workflows/ci.yml, and three optional [group('infra')]
recipes wrapping the Grafana/Intune deployment tools that need external credentials. just check
is the complete local gate and is exactly what ci.yml runs (today it is not — see §5). There is
no Makefile in this repo, so there is nothing to delete on that front. All five shipped shell
scripts and all six Python programs stay exactly where they are (git mv none of them) — every one
of them is either a shipped runtime artifact that runs on a managed device/target host, or a real
program invoked with credentials this gate cannot assume. AGENTS.md’s “Gate” section and
backlog/config.yml’s definition_of_done point at just check instead of raw commands. README.md’s
test-run snippet points at just test.
No files may be migrated (absorbed) into the justfile. This repo’s classification comes out 100% KEEP: there is nothing that is “just a few commands sequenced” — every script is either a target-machine artifact or a substantial program (90–492 lines) with real logic.
2. The complete justfile
Drop this in as justfile at the repo root.
set shell := ["bash", "-euo", "pipefail", "-c"]
# show the task surface
default:
@just --list
# verify the local toolchain is present -- nothing to install, no lockfile in this repo
setup:
@command -v python3 >/dev/null || (echo "python3 not found -- install Python 3.x" && exit 1)
@command -v shellcheck >/dev/null || (echo "shellcheck not found -- brew install shellcheck (macOS) or apt install shellcheck (Linux CI)" && exit 1)
@echo "toolchain present: python3, shellcheck"
# format source in place -- the justfile is the only thing here with a formatter
[group('check')]
fmt:
@just --fmt
@echo "no formatter configured for src/ -- shell (shellcheck-only) and stdlib python, hand-formatted"
# verify formatting -- never mutates
[group('check')]
fmt-check:
just --fmt --check
# shellcheck every shipped shell script (deviceShellScript payloads + LaunchDaemon wrapper)
[group('check')]
[no-exit-message]
lint:
shellcheck src/endpoint/*.sh src/intune/*.sh
# run the endpoint unit test suite; filter narrows to test_<filter>*.py
[group('check')]
[no-exit-message]
test filter="":
python3 -m unittest discover -s src/endpoint -p 'test_{{ filter }}*.py'
# validate docs.toml nav matches docs/*.md -- mirrors the m7kni.io hub's strict build
[group('check')]
[script('python3')]
docs-check:
import pathlib
import sys
import tomllib
manifest = pathlib.Path("docs.toml")
docs = pathlib.Path("docs")
errors = []
with manifest.open("rb") as fh:
cfg = tomllib.load(fh)
def targets(value):
if isinstance(value, str):
yield value
elif isinstance(value, list):
for item in value:
yield from targets(item)
elif isinstance(value, dict):
for item in value.values():
yield from targets(item)
seen = set()
for entry in cfg.get("site", {}).get("nav", []):
for target in targets(entry):
seen.add(target)
if not (docs / target).is_file():
errors.append(f"nav points at docs/{target}, which does not exist")
for page in sorted(docs.rglob("*.md")):
rel = page.relative_to(docs).as_posix()
if rel.startswith(("includes/", "superpowers/")) or rel == "404.md":
continue
if rel not in seen:
errors.append(f"docs/{rel} exists but is not in the nav")
for key in ("name", "description", "author"):
if not cfg.get("site", {}).get(key):
errors.append(f"[site].{key} is missing or empty")
if errors:
print("\n".join(f" - {e}" for e in errors))
sys.exit(1)
print(f"docs.toml OK - {len(seen)} pages in nav")
# the full gate -- exactly what CI enforces
[group('check')]
check: fmt-check lint test docs-check
# assert the declared-only source_type list matches across alerts/recording-rules/dashboard.
# Pass --dashboard <path> -- the script's own default path is a placeholder and will not resolve here.
[group('check')]
tier-check *args:
python3 src/grafana/check-tier-consistency.py {{ args }}
# dry-run diff of Grafana alert rules against the deployed state (gcx auth required; pass --context)
[group('infra')]
alerts-plan context:
python3 src/grafana/alert-rules.py --context {{ context }}
# apply Grafana alert rule changes -- mutates the Bumblebee Alerts folder (gcx auth required)
[group('infra')]
[confirm('apply Grafana alert rule changes now?')]
alerts-apply context:
python3 src/grafana/alert-rules.py --context {{ context }} --apply
# show current Full Disk Access PPPC profile status via Microsoft Graph (creds required)
[group('infra')]
pppc-status:
python3 src/intune/deploy-pppc.py --status
# create/update the Full Disk Access PPPC profile and assign it (Graph creds required)
[group('infra')]
pppc-deploy:
python3 src/intune/deploy-pppc.py
# delete the Full Disk Access PPPC profile from Intune (Graph creds required)
[group('infra')]
[confirm('delete the PPPC profile from Intune now?')]
pppc-delete:
python3 src/intune/deploy-pppc.py --delete
Notes on this justfile:
tier-check,alerts-plan,alerts-apply,pppc-status,pppc-deploy,pppc-deleteare not dependencies ofcheck— they need credentials/network (gcxauth, Microsoft Graph) or a--dashboardpath that has no working default in this repo, so they cannot run in an unattended gate. They exist sojust --listis still the complete answer to “what can I do here.”deploy-pppc.pycurrently doesfrom graph import Graphagainst agraph.pymodule that does not exist anywhere in this repo (verified:find . -iname graph.pyreturns nothing). This is a pre-existing gap, not something this task fixes. Wrap it as written; do not try to makepppc-*recipes pass — they cannot, today, regardless ofjust.check-tier-consistency.pyloadsbumblebee_alerts.pyviaimportlib(HERE / "bumblebee_alerts.py"atsrc/grafana/check-tier-consistency.py:22) but the real file isalert-rules.py. Also pre-existing, also not this task’s problem to fix — flagged again in §9 Traps.
3. Makefile disposition
No Makefile or GNUmakefile exists anywhere in this repo (verified:
find . -iname Makefile -o -iname GNUmakefile outside vendor/node_modules/third_party/.venv,
zero results). Nothing to delete, no git rm step for this section.
4. Script disposition
| Script | Classification | Recipe | Reason |
|---|---|---|---|
src/endpoint/bumblebee-run.sh |
KEEP | none (shellcheck via lint only) |
LaunchDaemon wrapper script, embedded verbatim into installer.sh at deploy time (per its own header comment), executes on the managed macOS endpoint. Never invoked by a developer or CI directly. |
src/intune/attribute-exposure.sh |
KEEP | none (shellcheck via lint only) |
Intune macOS custom-attribute script, runs on-device via Intune’s attribute collection, not developer/CI-invoked. |
src/intune/attribute-scan-summary.sh |
KEEP | none (shellcheck via lint only) |
Same as above — device-side Intune custom attribute. |
src/intune/installer.sh |
KEEP | none (shellcheck via lint only) |
Intune deviceShellScript payload, runs as root on the managed device. Ships @@EMBED:catalog_select.py@@ / @@EMBED:loki_push.py@@ placeholder markers (installer.sh:264,276) with no embedding tool present in this repo — pre-existing, see §9. |
src/intune/uninstall.sh |
KEEP | none (shellcheck via lint only) |
Intune deviceShellScript payload, runs as root on the managed device, assigned as a separate uninstall policy. |
src/endpoint/catalog-select.py |
KEEP | none (real program, 160 lines) | Schema-coherence catalog stager, embedded into installer.sh at deploy time (per header). Not a dev/CI task. |
src/endpoint/loki-push.py |
KEEP | none (real program, 106 lines) | Loki NDJSON forwarder, embedded into installer.sh at deploy time (per header). Not a dev/CI task. |
src/endpoint/test_catalog_select.py |
KEEP | covered by test |
Real unittest module; discovered by python3 -m unittest discover -s src/endpoint -p 'test_*.py', unchanged from current invocation. |
src/grafana/alert-rules.py |
KEEP | alerts-plan / alerts-apply |
492-line Grafana alert-rule reconciler needing gcx CLI auth and an explicit --context. Real program with substantial logic — never absorb. |
src/grafana/check-tier-consistency.py |
KEEP | tier-check |
110-line drift checker across three duplicated source lists. Real program; kept as a file, wrapped in a recipe. |
src/intune/deploy-pppc.py |
KEEP | pppc-status / pppc-deploy / pppc-delete |
258-line Microsoft Graph PPPC profile deployer. Real program needing Graph credentials — never absorb. |
No script in this repo is a thin sequencing wrapper (plan.sh/setup.sh-shaped). There is nothing
to ABSORB. git rm nothing under src/.
5. CI changes
Current state, and the gap this task closes: .github/workflows/ci.yml today runs only the
docs-manifest validation. It does not run shellcheck or the unittest suite, even though
AGENTS.md’s “Gate” section and backlog/config.yml’s definition_of_done both name them as
required. Verified live: shellcheck src/endpoint/*.sh src/intune/*.sh currently exits 1 —
SC2086 on installer.sh:322 and installer.sh:326 ($PROJECT_ROOTS unquoted). This has never
been caught because CI never runs shellcheck. Fixing CI to call just check will immediately turn
this workflow red unless the quoting is fixed first — see the required pre-step in §8 Order of work.
Edit .github/workflows/ci.yml:
- Rename the
validate-docsjob (or repurpose it) to run the full gate. Insert asetup-juststep after checkout and before the run step:
- uses: extractions/setup-just@<pin-to-current-fleet-sha> # v4
with:
just-version: '1.58.0'
Use whatever exact pinned SHA the fleet’s other repos are currently using for
extractions/setup-just (check a sibling repo already migrated, or resolve the SHA for the
current v4 tag at migration time — do not invent a SHA).
- Replace the entire inline
python - <<'PYEOF' ... PYEOFheredoc step with:
- name: just check
run: just check
(The heredoc’s exact logic is now docs-check inside the justfile — see §2. Delete the heredoc
from the workflow file entirely; it is fully migrated, not duplicated.)
-
The job still needs
actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7(withpython-version: '3.x') —just checkneedspython3on PATH, andshellcheckneeds to be present too.ubuntu-latestrunners shipshellcheckpreinstalled (Actions runner image default tool cache) — verify this holds at migration time; if not, add- run: sudo apt-get install -y shellcheckbefore thejust checkstep, or switch to a shellcheck-installing action. -
Rename the job (e.g.
validate-docs→ciorgate) if the job id changes meaningfully, butci-success’sneeds: [validate-docs]must be updated to match the new job id — do not leave it pointing at a job name that no longer exists, and do not add a second job.ci-successitself, itspermissions:, itsif: always(), and its pass/fail logic must not change. -
Do not touch
harden-runner,actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1withpersist-credentials: false, or the workflow’spermissions: contents: readblock.
Leave untouched, entirely: actionlint.yml, codeql.yml, scorecard.yml, zizmor.yml (all
uses: rknightion/.github/.github/workflows/...@0228d8b9f1e36ff3a1d0906574d70fa174ddc7bf # v1.5.1
reusable calls — GitHub-native, never fold into just) and trigger-docs-sync.yml (OpenBao broker
token mint + repository-dispatch, GitHub-native, never fold into just).
6. Docs and agent-contract changes
AGENTS.md — replace the “Gate” section body:
Current (AGENTS.md:16-24):
## Gate
Run all three before calling anything done. They need no credentials and make no network calls.
```bash
python3 -m unittest discover -s src/endpoint -p 'test_*.py'
shellcheck src/endpoint/*.sh src/intune/*.sh
```
Plus the docs manifest check in `.github/workflows/ci.yml` — every `docs.toml` nav target must
exist and every `docs/*.md` must appear in nav. The hub builds with `strict = true`, so a nav entry
pointing at a missing file fails the whole fleet build, not just this repo.
These are `definition_of_done` in `backlog/config.yml`, so every new task inherits them.
Replace with:
## Task interface
This repo's task surface is a `justfile`. Discover it, don't guess it:
just --list # human-readable
just --dump --dump-format json # machine-readable
just --show <recipe> # what a recipe actually runs
- `just check` is the full gate and is exactly what CI enforces. It must pass before you commit.
- Prefer `just <recipe>` over the underlying tool. If you are typing `pytest` or `shellcheck`, you
want `just test` or `just lint`.
- Run `just` with stdin from /dev/null. Recipes marked `[confirm]` are destructive — stop and ask
before running one; never pass `--yes` or `JUST_YES=1`.
- If a task you need does not exist, add a recipe with a `#` doc comment and a `[group(...)]`
rather than running a bare command.
This is `definition_of_done` in `backlog/config.yml`, so every new task inherits it.
(Verbatim from fleet standard §9, plus the repo-specific closing line pointing at
backlog/config.yml.)
README.md:64-68 — replace:
Run the endpoint tests:
```sh
cd src/endpoint && python3 -m unittest test_catalog_select
```
with:
Run the endpoint tests:
```sh
just test
```
CLAUDE.md — unchanged. It only does @AGENTS.md and carries no gate text of its own.
No other file under docs/ references make or a script path as a run instruction (verified:
grep -rn "make \|\./scripts" docs/*.md — no hits beyond unrelated prose already checked).
7. backlog/config.yml
Current:
definition_of_done:
- "docs manifest: every docs.toml nav target exists and every docs/*.md is in nav (the check in .github/workflows/ci.yml)"
- "python3 -m unittest discover -s src/endpoint -p 'test_*.py'"
- "shellcheck src/endpoint/*.sh src/intune/*.sh"
New:
definition_of_done:
- "just check"
just check runs fmt-check, lint, test, and docs-check — a strict superset of the three
lines it replaces (it additionally verifies justfile formatting, which is new and harmless). Do
not hand-edit this file — backlog/config.yml is the one deliberate exception the repo’s own
AGENTS.md names for list-valued keys that backlog config set cannot reach, so a direct edit here
is correct and matches existing repo convention; every other backlog/ file must go through the CLI.
8. Order of work
- Fix the pre-existing shellcheck finding first, before wiring CI to enforce it: quote
$PROJECT_ROOTSonsrc/intune/installer.sh:322and:326("$PROJECT_ROOTS"). Re-runshellcheck src/endpoint/*.sh src/intune/*.shlocally and confirm exit 0. This is unrelated tojustmechanically, but is required so step 3 below doesn’t ship broken CI. - Add the
justfilefrom §2 at the repo root. Runjust --fmt --check(should already pass — it was authored fmt-clean), thenjust checklocally end to end. Confirmtestandlintanddocs-checkall pass (they will, once step 1 is done) andfmt-checkpasses. - Edit
.github/workflows/ci.ymlper §5. Push and confirm the workflow run is green, includingci-success. - Edit
AGENTS.md,README.mdper §6. - Edit
backlog/config.ymlper §7 (direct file edit, not the CLI — see §7’s note). - Nothing to delete (§3 — no Makefile; §4 — no ABSORB scripts). Skip the deletion step entirely;
there is no “last” step here beyond confirming nothing still references the old raw commands
(
grep -rn "python3 -m unittest\|shellcheck src/" --include=*.md --include=*.yml .should return only this task’s own history, not live docs/CI).
9. Traps specific to this repo
shellcheckcurrently fails (SC2086atinstaller.sh:322,326) and nothing catches it today because CI never runs shellcheck. Fix it as step 1 of §8, not as an afterthought — otherwise wiringci-successtojust checkships a red CI on the very commit that adds it.deploy-pppc.pyimports agraphmodule that does not exist in this repo.pppc-status/pppc-deploy/pppc-deletewill fail withModuleNotFoundErrortoday regardless ofjust. Do not treat this as ajust-migration bug; wrap the script as-is and leave the underlying gap alone unless a separate task is filed for it.check-tier-consistency.py:22loadsbumblebee_alerts.pyviaimportlib, but the real file isalert-rules.py.just tier-checkwill fail withFileNotFoundErroron the module load, independent of any--dashboardargument. Same as above: pre-existing, not in scope, wrap as-is.check-tier-consistency.py’s default--dashboardpath is a literal placeholder (Path.home() / "repos/<your-git-sync-repo>/bumblebee/bumblebee-fleet.json",check-tier-consistency.py:16) — it will never resolve on any real machine.tier-checkmust stay out ofcheck’s dependency list for this reason alone, independent of theimportlibbug above.installer.shembedscatalog-select.pyandloki-push.pyvia@@EMBED:...@@markers with no embedding script anywhere in this repo. This looks like a manual, currently-undocumented step. Do not invent an ABSORB target for it and do not add agen/gen-checkrecipe pretending this is automated — it isn’t, today.docs-checkuses[script('python3')], not a line-based recipe — the original CI step has afor/nested-function body that would hit just’s “extra leading whitespace” failure mode in a plain recipe (§10 of the fleet standard). Keep it as a script block; do not try to flatten it to one-liners.ubuntu-latest’s shellcheck availability is not verified as part of this task — GitHub-hosted runner images have historically preinstalledshellcheckin the tool cache, but confirm this at migration time (shellcheck --versionas a debug step, or check the runner image manifest) before assuming the CI job needs no extra install step.- No lockfile, no
pyproject.toml, nothing touv syncorpip install.setupis a toolchain-presence check, not a dependency install — do not invent a Python dependency manifest that doesn’t exist. If a future task adds one (e.g. fordeploy-pppc.py’s Graph SDK dependency),setupshould then gain a real install step; that’s out of scope here.
10. Out of scope
- Every script marked KEEP in §4 — do not move, rename, or absorb any of them.
.github/workflows/actionlint.yml,codeql.yml,scorecard.yml,zizmor.yml— GitHub-native reusable-workflow calls intorknightion/.github. Never convertuses:intorun: just..github/workflows/trigger-docs-sync.yml— OpenBao broker token mint +repository-dispatch. GitHub-native, no shell logic to migrate.docs/,docs.toml, and thezensical/m7kni.io hub build pipeline itself — this repo only owns the manifest and the markdown; the actual site build happens inm7kni/m7kni-net-site.- Fixing
deploy-pppc.py’s missinggraphmodule orcheck-tier-consistency.py’s stalebumblebee_alerts.pyreference — flagged in §9 as pre-existing, left for a separate task. backlog/task/doc/decision markdown — never hand-edited;backlog/config.ymlis the sole named exception (§7).- Renovate config (
renovate.json) andLICENSE— untouched, unrelated.
Acceptance Criteria
- #1 A top-level justfile exists defining default, setup, fmt, fmt-check, lint, test and check, each with a # doc comment and a [group(…)]
- #2 just check runs fmt-check, lint, test and docs-check and passes locally with no credentials or network access
- #3 just –fmt –check passes on the justfile
- #4 just –list shows a doc comment and group for every public recipe, including alerts-plan, alerts-apply, tier-check, pppc-status, pppc-deploy and pppc-delete
- #5 src/intune/installer.sh:322 and :326 quote $PROJECT_ROOTS so shellcheck src/endpoint/.sh src/intune/.sh exits 0
- #6 no Makefile or GNUmakefile exists in the repo (none exists today; this criterion confirms none was introduced)
- #7 .github/workflows/ci.yml installs just via extractions/setup-just pinned to just-version 1.58.0 and runs just check in place of the inline docs-validation heredoc, with ci-success still gating on the renamed job
- #8 AGENTS.md’s Gate section is replaced with the Task interface section naming just check as the gate, and README.md’s test-run snippet reads just test
- #9 backlog/config.yml’s definition_of_done reads exactly just check
- #10 actionlint.yml, codeql.yml, scorecard.yml, zizmor.yml and trigger-docs-sync.yml are unchanged
Definition of Done
- #1 docs manifest: every docs.toml nav target exists and every docs/*.md is in nav (the check in .github/workflows/ci.yml)
- #2 python3 -m unittest discover -s src/endpoint -p ‘test_*.py’
- #3 shellcheck src/endpoint/.sh src/intune/.sh
Implementation Plan
- Inventory the current scripts, CI, hook surfaces, and references; preserve every KEEP-classified program and shared reusable workflow.
- Fix the pre-existing shellcheck defect, add the frozen justfile task surface and docs validator, then redirect CI and human-facing task instructions.
- Run the required local gates and reference/hook audits; commit named paths, push main, and prove the exact final SHA in GitHub Actions before atomic finalization.
Implementation Notes
Parked after hosted CI runs at the two migration commits failed in just check while the local gate passed. The GitHub-hosted ShellCheck 0.9.0 behavior was reproduced locally: it reports six pre-existing SC2015 findings across src/endpoint/bumblebee-run.sh and src/intune/installer.sh. The staged, uncommitted source fix rewrites those boolean chains as explicit if blocks without suppressing lint or changing the gate. Verified: sh -n on both scripts, exact ShellCheck 0.9.0, and just check (7 tests; docs manifest OK).
Resume boundary: wait for the CodeRabbit quota to become available, run coderabbit review --agent against the staged two-file source fix, address any findings, commit and push only those named files, then prove a green ci-success run at the resulting exact SHA before marking this task Done. CodeRabbit returned rate-limit responses on the permitted retries; the final response advertised a new four-minute hold.
Unparked and completed 2026-08-29. CodeRabbit returned zero findings. The staged ShellCheck SC2015 fixes landed. Migration at 05b254f; exact-head CI green.
Comments
author: campaign-ordering created: 2026-08-29 09:18
Fleet ordering — WAVE 2. Starts after the Wave 0 pilot (sf2loki / SFL-0073) and the Wave 1 hubs land.
Within Wave 2 the order is free — these repos do not depend on each other. Batching by language is worthwhile so one lane reuses its Makefile-to-recipe mapping across similar repos.
Do not start before the pilot reports. The standard may be amended off the back of it, and picking this up early risks coding against a superseded seam.
Provisioning just in CI. Which mechanism depends on the runner, and the two must not be mixed:
| Runner | Mechanism |
|---|---|
arc-arm64 (m7kni self-hosted) |
just is baked into the runner image by m7kni/ci-tools (runner-image/Dockerfile, ARG JUST_VERSION). Do not add extractions/setup-just, and delete the step if this repo already has one — it installs a second just earlier on PATH and turns the image pin into a lie. |
GitHub-hosted (all rknightion repos) |
extractions/setup-just, SHA-pinned, with an explicit just-version:. |
Both sides currently sit on 1.58.0 and are Renovate-managed. ci-tools’ Tool version drift workflow fails if the Dockerfile ARG and the published image ever disagree, and lists any repo still carrying a second pin.
While you are in the workflow files, check the hub pin. On 2026-08-29 Renovate was unfrozen for rknightion/.github in m7kni/renovate-config — it had been enabled: false on the mistaken belief that callers tracked @main, which froze the fleet across 19 different hub SHAs (v1.3.1 June → v1.9.7 August) so that no hub fix ever propagated. Bumps now arrive as one grouped, CI-gated, automerged PR per repo. A uses: whose comment is not a real # vX.Y.Z still cannot be bumped (it resolves to a digest-only update, which the fleet rules disable) — if you find one, repair the comment as part of this task.
author: campaign-ordering created: 2026-08-29 10:42
Standard amendment — ci is the sanctioned superset of check (RATIFIED)
This supersedes the frozen wording “check is the complete local gate and reproduces every CI job that can run off a GitHub runner”, which several lanes could not honour without making the pre-commit gate depend on a Docker daemon.
The definitions now are:
check— everything that runs with only the language toolchain installed. This is the pre-commit gate. A leg that runs on a bare toolchain belongs here however long it takes.ci—checkplus the legs CI gates that need a Docker daemon, a service container, or cross-compilation, and nothing else. Written asci: check <heavy legs>.
Every leg you put in ci must carry a comment naming which of those three it needs. That comment is the guard: without it ci becomes the bin for anything slow or awkward, check quietly stops meaning much, and the fleet is back to a per-repo gate.
Eleven of the 42 lanes arrived at this shape independently before it was ratified, which is why it won.