feat(smartgroup): pro smart-group templates — 23 curated smart groups across 5 categories#205
feat(smartgroup): pro smart-group templates — 23 curated smart groups across 5 categories#205ktn-jamf wants to merge 25 commits into
Conversation
…All/ByCategory/Categories/FuzzyMatch
… categories, criterion-name registry)
…ub subcommands Wires the new `pro smart-group` (alias `sg`) namespace into the CLI under the Computer Management help group. Four stub subcommands (templates, preview, apply, verify-templates) compile and resolve correctly; full implementations follow in Tasks 12-15. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…gory, table/json output) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…aram flags Replaces the preview stub with a real RunE that looks up the template, collects param flags (union of all template params registered via registerTemplateParamFlags), resolves defaults/required checks via ResolveOpts, and prints the POST header + indented JSON body without making an API call. Also introduces collectParamValues, unknownTemplateError (with fuzzy-match suggestions), and the flagReader interface — reused by apply (Task 14). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…based create/update and post-apply membership check
…tenant smoke test Adds RunOneVerification to internal/smartgroup (create temp group, recalc, count members, optional cleanup) and wires it into the verify-templates subcommand under pro smart-group with --category, --no-cleanup, and --json flags. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Wrap fmt.Fprintf/Fprintln calls to io.Writer with _, _ = per project convention - Convert untagged switch to tagged switch existingID per staticcheck QF1002 - Wrap resp.Body.Close() in defer func closures to satisfy errcheck - Apply gofumpt formatting across touched and pre-existing files Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ed' (verified via live tenant)
…er 'Off', MDM cert searchType
…d uses correct ADE Boolean criterion
Captures the design rationale and step-by-step implementation plan for the pro smart-group namespace. Spec documents the 23-template inventory, verified criterion-name registry sourced from JSS, and the verify- templates safety net. Plan documents the 17-task TDD breakdown. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Review —
|
| Profile | Category | Result |
|---|---|---|
pro-nmartin |
encryption | 4 OK, 2 zero-match (irk-only-deprecated, not-encrypted — expected on healthy fleet) |
pro-nmartin |
mdm | 5/5 OK |
platform-nmartin |
mdm | 5/5 OK (Pro API routed through gateway) |
platform-nmartin |
updates | 3 OK, 1 zero-match (beta-os — expected) |
| Create + update + delete cycle | — | All working |
Idempotency guard (missing --yes) |
— | Correct error returned |
Criterion strings are clean. The two zero-match encryption results match the author's own test report for a healthy fleet.
Verdict: Fix the four items above (three must-fix + JSON tags), then this is good to merge.
…rs, docs placement Five fixes from the PR #205 principal-engineer review: (1) Reject unknown template param flags. `registerTemplateParamFlags` declares the union of every template's params on every preview/apply command, so `apply --template encryption/not-encrypted --stalled-after 14` was silently dropping the bogus flag. Now `collectParamValues` walks the command's full flag set and errors on any Changed param-style flag that isn't in the chosen template's ParamSpec list. Tests cover apply, preview, and the "this param does belong to this template" happy path. (2) Reject smart-group names containing `"` or `\`. `lookupSmartGroupByName` built `name=="<name>"` as an RSQL filter without escaping internal quotes; a name with `"` produced a malformed filter the server may parse unpredictably. Validate at the top of `runApplyFlow` with a clear error rather than relying on RSQL escaping rules. (3) `deleteGroup` (verify.go) now checks HTTP status and returns errors. Previously returned nil regardless of status, so verify-templates could leave orphaned `__verify_*` smart groups silently. `RunOneVerification` surfaces the cleanup error via the result's Error field — the verify itself still passes; only the cleanup failed. (4) `verify.go:createTempGroup` now checks the json.Marshal error, matching the pattern used in `pro_smartgroup.go:createSmartGroup`. Style consistency fix; the error path is essentially impossible for the struct shape but the inconsistency was a real code smell. (5) Move spec + plan docs from `docs/solutions/design-patterns/` to `docs/plans/`. Per `docs/solutions/README.md`, solutions are postmortems and reusable patterns; these are implementation plans for one feature and belong with the other plans. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Prepares the JamfReports app for the `pro smart-group` namespace landing in jamf-cli v1.17 (upstream PR Jamf-Concepts/jamf-cli#205, OPEN at commit time). Pure additive Swift: no UI changes, no destructive operations yet, no version-floor bump. Adds four CLICommand cases mirroring the upstream contract: - proSmartGroupTemplates — lists the 23 curated templates - proSmartGroupPreview — renders criteria JSON for a template + params - proSmartGroupApply — destructive create-or-update (Stage 2 wraps it) - proSmartGroupVerifyTemplates — diagnostic smoke-test runner Argv construction sorts params alphabetically for deterministic testing, and `apply` always emits `--yes` because the GUI has no TTY for interactive confirmation prompts. Caller-supplied user consent is the only gate (collected by Stage 2's UI flow). Adds SnapshotKind.smartGroupTemplates so the template list can be cached on disk (it's stable per jamf-cli version). Preview, apply, and verify outputs are intentionally NOT cached — they're either parameterized per call or destructive. Adds SmartGroupTemplateService for read-only template+preview: - listTemplates: decodes the 23-template registry, sorts by category then slug so the UI gets a deterministic order - preview: tolerates both `{body, estimated_match_count}` envelope shape and the bare-body fallback (PR contract isn't fully locked) - Translates CLIExecutorError into typed service errors: * unknown command / binary not found → featureNotAvailable (so dashboards can hide buttons on older jamf-cli) * unknown template → unknownTemplate (so the GUI can show a meaningful error rather than a generic "exit 2") * everything else → executionFailed(code, stderr) 24 new tests: - 11 argv-shape tests across the four new cases (including param sorting determinism and the --yes / --dry-run / --recalculate combinations) - 4 snapshotKind cacheability tests - 9 service-level tests using a MockCLIExecutor that's keyed on argv (not on CLICommand-as-Hashable — Swift 6 rejects retroactive Hashable conformance across the test boundary) Total Swift tests: 1213 → 1237. ruff: clean. Build: clean. Source: planning for upstream PR Jamf-Concepts/jamf-cli#205. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the first write-to-Jamf-tenant surface in the app, gated by an always-preview-always-confirm UX (operator-chosen at Stage 2 kickoff). Stage 1 landed the read-only foundation; this commit adds the destructive apply path plus the reusable SwiftUI sheet that dashboards will consume in Stage 3. SmartGroupApplyService: - Wraps `pro sg apply` via the CLICommand from Stage 1 - Decodes the apply result envelope (id, name, member_count, created), tolerating both snake_case and camelCase since PR #205's JSON serializer convention isn't fully locked - Classifies errors into typed cases: featureNotAvailable (unknown command), apiError(httpStatus, message) extracted from cliCtx.Client.Do "HTTP <code>: <message>" patterns, networkFailure for connection-refused / i/o-timeout / no-such-host / TLS, and executionFailed as the unclassified fallback SmartGroupApplySheetViewModel: - @observable @mainactor state machine: loadingPreview → previewReady → applying → applied | applyFailed (or previewFailed at any point) - canApply gate: requires non-empty name AND every required param populated AND phase ∈ {previewReady, applyFailed} — protects the destructive call from racing or fat-fingered submits - applyFailed preserves the preview the user just approved so the retry screen doesn't lose context - defaultName generator: slug-cased title with " (Jamf Reports)" suffix (user-editable in the sheet) SmartGroupApplySheet: - 560×520 modal sheet bound to the view-model - Phases: spinner during preview/apply, name+params form + readable criteria JSON + estimated match count during previewReady, success card with "Reveal in Jamf Pro" link during applied, inline error banner with preserved preview during applyFailed - Action bar adapts: Cancel/Create during preview phases, Done after apply, Close after preview-fatal-failure Tests: 31 new (1237 → 1268). Coverage includes: - Apply argv shape and result-decoder (snake/camel/missing fields) - Error classification (HTTP 401/403/429 extraction, network patterns, unknown-command, fallthrough) - View-model state transitions on success and failure - canApply gate behavior under all four blocking conditions - Preview preservation across apply failures - Default-name generation for single-word and multi-word slugs Stage 3 (dashboard wiring) is the next commit. Each dashboard gets its own commit so the integration can be reviewed and rolled back per-view. Source: planning for upstream PR Jamf-Concepts/jamf-cli#205. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the first dashboard entry point to the SmartGroupApplySheet landed in Stage 2. Operators viewing the Inactive (91-180d) or Dormant (180d+) tier now get a "Create smart group" button next to "Copy email list" that opens the apply sheet pre-filled with the `stale-checkin` template and a "Stale Macs 90+ days (Jamf Reports)" suggested name. Feature-detect on view appear: lists templates once per profile via SmartGroupTemplateService and stores the stale-checkin entry. Any failure (jamf-cli < v1.17, network, missing) leaves the state nil and hides the button — older jamf-cli installs see no UI regression. Button is hidden on the Offline (31-90d) tier because the stale-checkin template's threshold is hardcoded to 90 days upstream — surfacing it there would mislead operators into thinking they were targeting the 31-90d bucket. Source: planning for upstream PR Jamf-Concepts/jamf-cli#205. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a "Create smart group" action card under the kpiGrid that opens a template-driven sheet for each of the four update-category templates: - os-version-below (devices below a threshold OS version) - major-version-behind (a full major version behind) - rsr-not-applied (Rapid Security Response missing) - beta-os (running a beta OS) The dashboard cards (planStateCard / statusSummaryCard / failedPlans / errorDevices) don't break down by OS version, so per-row buttons would have no anchor. A single menu picker fits the read flow: operator sees a problem in a card, then picks the matching template from the menu. Templates are loaded once per profile via SmartGroupTemplateService. Failure on load (jamf-cli < v1.17, network, missing) leaves the template list empty, which hides the action bar — older installs see no regression. Source: planning for upstream PR Jamf-Concepts/jamf-cli#205. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds an "Encryption remediation" action card between kpiGrid and actionItemsCard with a menu picker exposing the three encryption templates from jamf-cli `pro sg`: - not-encrypted (devices with FileVault off) - invalid-recovery-key (FV on but IRK is invalid) - escrow-missing (FV on but recovery key not escrowed) Sits between the KPI grid and the existing action-items card so the operator can scan from "FV coverage is N%" KPI → "Create smart group for the gap" → action items without scrolling. Same load-on-appear feature-detect pattern as the OutreachView and UpdatesView wirings; older jamf-cli installs see no UI regression. Source: planning for upstream PR Jamf-Concepts/jamf-cli#205. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the final dashboard wiring for jamf-cli `pro sg`. A "Compliance remediation" action card sits between the controlCoverageCard and the per-OS breakdown so the operator can scan "which control is failing" → "create a smart group for that control" without leaving the page. Menu picker exposes all four compliance templates: - gatekeeper-disabled - sip-disabled - firewall-disabled - non-compliant-baseline (mSCP baseline rollup) Same menu-picker shape as SecurityPostureView and UpdatesView for visual consistency across the new dashboard family. Originally the plan called for per-control inline buttons in controlBar, but the controlBar already renders a progress-bar + percentage layout and an inline button would conflict with that shape — a single menu picker keeps the chrome focused. Stage 3 complete. All four target dashboards (Outreach, Updates, SecurityPosture, CompliancePosture) now expose smart-group creation via the reusable SmartGroupApplySheet from Stage 2, with feature detection that hides the UI on jamf-cli < v1.17. Source: planning for upstream PR Jamf-Concepts/jamf-cli#205. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Adds a new
pro smart-groupnamespace (aliaspro sg) with 4 subcommands and a curated library of 23 smart-group templates across 5 operational categories: encryption, software updates, MDM health, compliance basics, and lifecycle hygiene.Admins go from "I need a smart group for [common operational case]" to a working smart group in one command. Templates encode operational knowledge sourced and verified byte-for-byte against the JSS server source for criterion-name strings.
New commands
pro sg templates [--category X]pro sg preview --template <slug> [params]pro sg apply --template <slug> --name <NAME> [params] [--recalculate] [--dry-run] [--yes]pro sg verify-templates [--category X] [--no-cleanup]Template library (23 templates, 5 categories)
encryption (6): not-encrypted, invalid-recovery-key, escrow-missing, irk-only-deprecated, encryption-stalled, fv-ineligible.
updates (4): os-version-below, major-version-behind, rsr-not-applied, beta-os.
mdm (5): bootstrap-token-missing, user-approved-mdm-no, stale-checkin, mdm-cert-expiring, declarative-management-disabled.
compliance (4): gatekeeper-disabled, sip-disabled, firewall-disabled, non-compliant-baseline.
lifecycle (4): unsupervised, ade-enrolled, jamf-binary-outdated, fv-ineligible-hardware.
Architecture
internal/smartgroup/package — Template + ParamSpec types, criterion-name constants (JSS-cited), library registry with Register/Lookup/All/ByCategory/FuzzyMatch, membership-count helper, verify-runner. Tests in isolation: ~42 tests covering type validation, criterion uniqueness, registry behavior, per-template golden JSON, library integration (23 subtests).internal/commands/pro_smartgroup.go— cobra namespace + 4 subcommands. Tests cover all flags + happy/error paths with mocked HTTP.pro.go(+1 line),groups.go(+1 entry under Computer Management),aliases.go(+1 entrypro sg).cliCtx.Client.DoHTTP path and existing/v2/computer-groups/smart-groupsendpoints.Design docs
docs/solutions/design-patterns/pro-smart-group-templates-spec-2026-05-12.md— full design with criterion-name registry citationsdocs/solutions/design-patterns/pro-smart-group-templates-plan-2026-05-12.md— 17-task TDD implementation planLive test results (ktn tenant)
Round 1 caught 4 string bugs. Round 2 caught 2 more. All 6 fixed (separate fix commits with JSS source citations in commit messages):
encryption/not-encryptedvalue:"Not Encrypted"→"No Partitions Encrypted"(perComputerFileVault2Statusenum)CriterionJamfBinaryVersion:"Jamf Binary Version"→"JAMF Binary Version"(perJamfBinaryVersionMatcher.java@Componentannotation)compliance/gatekeeper-disabledvalue:"Disabled"→"Off"(perComputerGatekeeperStatus)mdm/mdm-cert-expiringsearchType:"less than x days from now"→"in less than x days"(perSearchCriteriaOperator.IN_LESS_THAN_DAYS)lifecycle/jamf-binary-outdated: parameterized"less than"operator was invalid → zero-param using"not current"operator (operationally better — no version-threshold maintenance burden)lifecycle/ade-enrolled: wrong criterion (PreStage profile name) → correct Boolean criterion"Enrolled via Automated Device Enrollment"Final live-test result on ktn: 0 errors, 18 OK, 5 zero-match warnings — and the 5 remaining warnings are characteristic of a healthy fleet (Gatekeeper enabled, SIP enabled, no legacy IRK, no MDM certs expiring soon, no beta OS).
Test plan
internal/smartgroup/(types, criteria, library, 23 template goldens, library integration, membership, verify)internal/commands/(templates / preview / apply / verify-templates with mocked HTTP)go test ./...— all 29 packages greenmake lint— 0 issuesmake build— cleanbin/jamf-cli pro sg verify-templatesagainst ktn tenant — 0 errors, 18 OKapply --dry-runof a few templatespro sg verify-templates --category encryptionagainst their own tenant🤖 Generated with Claude Code