# Session Log: 2026-05-27

## User
- **User:** Mike Swanson (mike)
- **Machine:** GURU-5070
- **Role:** admin

## Session Summary

Continued from 2026-05-26 across the date boundary. Completed the identity.json Phase 2 migration on GURU-5070 (centralized Ollama/Python/platform config) directed by a coord message from the Mac session. `migrate-identity.sh` failed twice on Windows — it hardcoded `python3` instead of the detected `$PYTHON_CMD`, then passed a Git Bash POSIX path to native Windows Python. Fixed both (`$PYTHON_CMD` + `cygpath -m`), re-ran successfully, pushed the fix (251bb35), and sent Howard a heads-up to pull before running it on his Windows laptop. Pulled in Howard's GuruScan module refactor (GuruScan.psm1/.psd1, README.md, scanners.json, GURUSCAN_RESULT_JSON reporting) — it delivers on every gap and packaging suggestion from the prior coord thread. Saved a feedback memory to leave GuruScan alone until Howard requests review.

Ran a preemptive Valleywide health check (nothing reported by client). All six core hosts are UP: UDM, DC1, VWP-QBS (RDWeb 443 + RDP 3389 listening), HP iLO, ADSRVR, XenServer. The HP ProLiant — the recurring failure point (no UPS) — was confirmed powered ON via iLO. Key discovery: Tailscale silently hijacks VWP's `192.168.0.0/24` subnet (Tailscale route metric 5 beats the VWP VPN's 281), so `192.168.0.x` probes from any Tailscale-connected machine hit the wrong network; resolved the ambiguity with temporary `/32` routes via the VPN gateway. Valleywide has no GuruRMM agents (until an agent was deployed late in the session as a discovery/deployment testbed).

Investigated the GuruRMM "Network Deployment via discovery node" feature status: discovery (node designation + scanning + per-agent UI) is built, but deployment-to-discovered-devices is NOT (only a `deploying` status label exists; no push-install). The roadmap showed it as stale-unchecked — the same drift pattern as BUG-001.

That drift prompted the session's main work: making `FEATURE_ROADMAP.md` a living document. First added a roadmap-reconciliation pass (Agent F) to the `/rmm-audit` skill. Then, on Mike's decision, implemented three pieces: (1) a "Roadmap Is a Living Document" rule in GuruRMM's DESIGN.md + dev-principles memory making the roadmap update part of definition-of-done; (2) a one-time baseline reconcile flipping 44 verified-shipped core features `[ ]`→`[x]` (each proven against code by Agent F, conservative/end-to-end only); (3) flipped the audit's roadmap-pass default to reconcile-and-flip. The roadmap now reflects reality, dev work is the primary maintainer, and the audit is the backstop.

## Key Decisions

- **migrate-identity.sh: fixed both Windows bugs rather than just reporting** — they'd break every Windows machine in the fleet rollout; fix was unambiguous ($PYTHON_CMD + cygpath -m) and unblocks others.
- **Valleywide: used a scoped `/32` route override, not a routing-table reconfiguration** — minimal/reversible way to get a true reading of VWP's 192.168.0.x hosts past the Tailscale hijack; removed the routes immediately after.
- **GuruScan: hands-off until Howard asks** — declined to review his .psm1 refactor unprompted; saved the boundary to memory.
- **Roadmap convention = living status-and-plan tracker (Option B), maintained inline during dev.** The reconciliation revealed 0/705 feature lines were ever checked — the roadmap was a backlog. Mike chose to make it a true status doc maintained as part of definition-of-done, with the audit as backstop.
- **Baseline reconcile was conservative** — flipped only the 44 lines Agent F verified end-to-end; left ~661 (partials + genuinely-open) untouched. A wrongly-flipped line is worse than a missed one.
- **First roadmap pass run was annotate-only** (before the convention decision); the second run did the full flip after Mike chose Option B.

## Problems Encountered

- **migrate-identity.sh exit 127** (`python3: command not found`) then `FileNotFoundError` on `/d/...` path — Windows. Fixed with `$PYTHON_CMD` + `cygpath -m`; re-ran clean.
- **Valleywide 192.168.0.x hosts falsely showed DOWN** — Tailscale route for `192.168.0.0/24` (metric 5) overrides the VWP VPN route (metric 281), sending traffic to a different client's network. Disambiguated with `/32` routes via `192.168.4.1`; confirmed all hosts UP.
- **Misrouted an RMM bug to Howard earlier (BUG-001)** — corrected: RMM is Mike's; deleted the note; the GURU-KALI attribution-hardening pass (pulled this session) confirmed git history is clean (drift was reasoning-time inference).
- **Repeated push races** with concurrent GURU-KALI/Mac/HOWARD-HOME sessions — resolved by sync.sh rebase each time.

## Configuration Changes

- MODIFIED (gururmm repo) `docs/DESIGN.md` — new "The Roadmap Is a Living Document" rule (commit 3e114a0)
- MODIFIED (gururmm repo) `docs/FEATURE_ROADMAP.md` — 4 scope annotations on over-claiming lines (b6f7a49); baseline reconcile flipping 44 shipped lines `[ ]`→`[x]` + header note (3e114a0)
- CREATED (gururmm repo) `reports/2026-05-27-rmm-audit-roadmap.md` (b6f7a49)
- MODIFIED `.claude/skills/rmm-audit/SKILL.md` — Agent F roadmap-reconciliation pass + reconcile-and-flip default (14a6c09, a885b54)
- MODIFIED `.claude/memory/gururmm-development-principles.md` — "Living Roadmap (MANDATORY)" principle (a885b54)
- MODIFIED `.claude/memory/feedback_rmm_dev_is_mike.md` — added "leave GuruScan alone until Howard asks" (synced)
- MODIFIED `.claude/scripts/migrate-identity.sh` — Windows fixes (251bb35)
- MODIFIED (local, gitignored) `.claude/identity.json` — added python/ollama/platform/architecture fields (Phase 2 migration)
- PULLED: Howard's GuruScan module refactor; GURU-KALI attribution-hardening + identity Phase 2 (migrate-identity.sh, whoami-block.sh, sync.sh/syncro.md reading identity.json — no more Ollama curl probe on migrated machines)

## Credentials & Secrets

- **Valleywide HP iLO:** `clients/vwp/hp-ilo.sops.yaml` — host 172.16.9.125, Administrator / `EV2PBU6J` (iLO reset to factory 2026-04-22). SSH needs paramiko with `disabled_algorithms={'pubkeys':['rsa-sha2-256','rsa-sha2-512']}`.
- **Valleywide vault path is `clients/vwp/`** (NOT `clients/valleywide/` as the wiki states — wiki drift). Entries: adsrvr, dc1, udm, xenserver, hp-ilo, quickbooks-server-idrac, server2003, brother-mfc-l3780cdw.
- No other new secrets. identity.json (gitignored) now carries ollama.endpoint/prose_model + python.command.

## Infrastructure & Servers

- **Valleywide (VWP):** all UP as of 2026-05-27. UDM 172.16.9.1 (443 up), DC1 172.16.9.2, VWP-QBS 172.16.9.169 (RDWeb 443 + RDP 3389 listening), HP iLO 172.16.9.125 (ProLiant powered ON), ADSRVR 192.168.0.25, XenServer 192.168.0.104. OpenVPN client pool 192.168.4.0/24 (this machine got 192.168.4.3). **Tailscale hijacks 192.168.0.0/24** — use `/32` routes via 192.168.4.1 to reach VWP's 192.168.0.x reliably. No GuruRMM agents enrolled (1 deployed late as discovery/deployment testbed).
- **GuruRMM:** live main now 3e114a0; agent fleet 0.6.39/0.6.41. Discovery: node designation + scanning + per-agent DiscoveryTab built; fleet view + deployment-to-discovered-devices NOT built. `user_session` command context: migration 041, agent/src/watchdog/wts.rs.
- **Identity migration:** GURU-5070 + HOWARD-HOME both on Phase 2 (python.command=py, ollama.endpoint=localhost:11434, platform=windows, amd64; GURU-5070 prose_model qwen3:8b, HOWARD-HOME qwen3:14b).

## Commands & Outputs

- iLO power check (read-only): paramiko SSH to 172.16.9.125, `power` → "server power is currently: On"; `show /system1 enabledstate` → enabled.
- Scoped route workaround: `route add 192.168.0.25 mask 255.255.255.255 192.168.4.1` (+ .104), ping, then `route delete` — confirmed both UP, routes removed.
- Roadmap flip: exact-line-match Python script flipped 44 `- [ ]`→`- [x]` (each matched exactly 1x, 0 misses/dupes).
- migrate-identity fix: `"$PYTHON_CMD"` + `IDENTITY_PATH_PY=$(cygpath -m "$IDENTITY_PATH")`.

## Pending / Incomplete Tasks

- **VWP discovery/deployment testbed:** agent deployed; exercise discovery (designate node, scan LAN) and shake out the not-yet-built deployment path.
- **Roadmap convention now active** — going forward, RMM features must update FEATURE_ROADMAP.md in the same change (definition-of-done). Audit backstops.
- **Lonestar Apple MDM:** gather iPhone/iPad serials + iOS versions, choose APNs Apple ID, supervised-vs-unsupervised decision, targeted-invite enrollment.
- **Glabman wifi quote** (todo 1bf0cfef, due 2026-05-27).
- **GND-SERVER Datto alert:** confirm cleared (deletion synced).
- (Carried) quantumwms John Velez consent; 2x Business Premium before 2026-06-03; Autotask skill; Western Tire #32199; Kittle HIGH.

## Reference Information

- gururmm commits: b6f7a49 (roadmap annotations + report), 3e114a0 (living-roadmap principle + 44-flip reconcile).
- claudetools commits: a885b54 (living-roadmap memory + skill convention), 14a6c09 (rmm-audit Agent F pass), 251bb35 (migrate-identity Windows fix).
- Coord: Howard "Phase 2 migration done on HOWARD-HOME"; my replies 8618a252 (identity Phase 2), 5ab63a21 (migrate-identity heads-up to Howard). Deleted misrouted BUG-001 note (was 92468218).
- GuruScan (Howard's): projects/msp-tools/guru-scan/ — now GuruScan.psm1/.psd1 + README + scanners.json + GURUSCAN_RESULT_JSON. Hands-off until he asks (feedback_rmm_dev_is_mike.md).
- Report: projects/msp-tools/guru-rmm/reports/2026-05-27-rmm-audit-roadmap.md.

---

## Update: 08:40 PT — Vault-connectivity diagnosis, memory audit, RMM full audit + Phase 1 authz remediation (deployed)

### Session Summary

Diagnosed the reported external flap on `git.azcomputerguru.com`. SSHed IX (the ACG website host, unrelated) then traced the real path: the domain is served by **NPM (openresty) on Jupiter `172.16.3.20`** via the office Cox IP `72.194.62.10` — **not Cloudflare**. The flap was a transient NPM SSL-cert renewal (NPM log entry `14:14:36 UTC`). Corrected the machine-local auto-memory `reference_gitea_internal.md`, which wrongly claimed git.azcomputerguru.com sat behind Cloudflare and blocked curl.

Audited the shared in-repo memory (`.claude/memory/`): indexed 8 orphaned files into `MEMORY.md`, added frontmatter to 5 files, trimmed oversized index lines, de-duplicated, and fixed a broken backlink in the index (`../.claude/POWER_FAILURE_RUNBOOK` → `../POWER_FAILURE_RUNBOOK`).

Ran a full `/rmm-audit` pass (all six passes on Opus 4.7: parallel agents A–D + F, sequential E build-pipeline). **62 findings — 3 CRITICAL, 9 HIGH, 12 MEDIUM** + lows/info. Report: `projects/msp-tools/guru-rmm/reports/2026-05-27-rmm-audit.md`. The 3 CRITICALs are the same authorization class: handlers that take `_auth: AuthUser` (authenticate-only, **no** org-scope authorization) — a BOLA/IDOR hole on credentials, command dispatch, and script execution.

On Mike's "fix all → start Phase 1, TODO the rest" direction, implemented **Phase 1 (the 3 CRITICALs)** on branch `remediation/2026-05-27`, plus the create_credential gate that Code Review flagged. While building I discovered **main did not compile** — Howard's `3b19ff0` changed `db::logs::get_fleet_logs` to a 5-arg signature but left 4 stale callers in `logs.rs` (E0061 ×4). That compile break is exactly why Howard's server deploy was "stuck" (binary frozen at the May 25 build). Folded the caller fix into the same branch (`4961923`), so the deploy ships the build fix and the authz fixes together. Code Review returned **APPROVE-WITH-NITS** (caught create_credential ungated → HIGH → fixed). `cargo check` green at `bdefb1f`. Merged the branch to main (fast-forward), CI bumped to `de39e42` (v0.3.30), and deployed via `sudo /opt/gururmm/build-server.sh`. **Verified live:** release build 4m45s, systemd restarted `15:32 UTC`, `ExecStart=/opt/gururmm/gururmm-server` running the fresh binary. Phases 2–5 captured as coord TODOs. Notified Howard of the in-flight fix, the remediation task list, the living-roadmap definition-of-done expectation, and (post-deploy) that his fleet-log fix is now live.

### Key Decisions

- **Option B — merge the whole branch + deploy at once** (vs. cherry-picking just the build fix). Ships the get_fleet_logs fix and all Phase 1 authz together; Mike acknowledged the authz changes are behavior-changing (org-scoped 403s where before any authed user passed).
- **`authorize_agent_access` is fail-closed** — an agent with no site / orphaned client_id returns **403**, stricter than the reference `get_agent` handler which fails open. A credential/command/script path must never default-allow on missing scope.
- **`reveal_credential` gated dev_admin-only BEFORE the DB fetch** — don't even read the secret out of the DB if the caller isn't authorized.
- **New commit `bdefb1f` for the create_credential fix, not an amend** — keeps `4961923` (the build fix) byte-stable and cherry-pickable, after an earlier `--amend` mistake rewrote its SHA.
- **Roadmap-compliance verification of Howard's sessions = no violation** — his only post-rule commit (`3b19ff0`) was a bug fix to an already-`[x]` feature, which requires no roadmap flip. The rule is brand-new, so the action is forward-looking: confirm his sessions pulled the updated DESIGN.md + memory.

### Problems Encountered

- **main wouldn't compile (E0061 ×4 in logs.rs)** — pre-existing breakage from Howard's `3b19ff0` get_fleet_logs signature change; none of my authz files were in the errors. Root-caused, fixed callers to the 5-arg form (`&["ERROR"], None, since, 1000`), committed `4961923`.
- **Stale cargo check** — `git fetch origin <branch>` does NOT fast-forward the local branch, so checks ran old code. Fixed by checking out `origin/remediation/2026-05-27` detached.
- **`git commit --amend` mistake** — amended the build commit, folding in the credentials fix and changing the `4961923` SHA I'd told Howard to cherry-pick. Recovered with `git reset --hard origin/remediation/2026-05-27`, re-applied the one-liner as the new commit `bdefb1f`.
- **`internal_err` not in scope (E0425)** in credentials.rs create_credential gate — `internal_err` isn't imported there; switched to the inline `.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?` pattern the file already uses.
- **Deploy binary-path ambiguity** — post-deploy, `/opt/gururmm/gururmm-server` was fresh (May 27 15:32) but `/usr/local/bin/gururmm-server` was still May 25. Verified `systemctl cat` → `ExecStart=/opt/gururmm/gururmm-server`; the `/usr/local/bin` copy is vestigial and unused. No action needed (candidate cleanup item).

### Configuration Changes (gururmm repo, branch merged to main)

- MODIFIED `server/src/api/mod.rs` — new `pub async fn authorize_agent_access(state, auth, agent_id)` helper (admin bypass; agent→site→client_id→`can_access_org`; fail-closed 403). Added imports `AuthUser`, `db`, `uuid::Uuid`.
- MODIFIED `server/src/api/credentials.rs` — `authorize_credential_access(state, user, cred)` branching on scope_type (global→`is_dev_admin`; client→`is_admin`|`can_access_org`; site→resolve→`can_access_org`; unknown→403). Gated list_global/list_client/list_site/get_credential_meta/reveal_credential (dev_admin-only, pre-fetch)/update/delete AND create_credential.
- MODIFIED `server/src/api/commands.rs` — `send_command` calls `authorize_agent_access` before dispatch.
- MODIFIED `server/src/api/scripts.rs` — `run_script_on_agent` → `authorize_agent_access(req.agent_id)`; library CRUD → `is_admin()` gate.
- MODIFIED `server/src/api/logs.rs` — fixed 4 stale `get_fleet_logs` callers to 5-arg signature (build fix; was breaking main).
- Commits: `4961923` (build fix), `bdefb1f` (create_credential gate err-map fix). Merged FF to main; CI auto-bump → `de39e42` (v0.3.30).

### Configuration Changes (claudetools repo)

- MODIFIED `.claude/memory/MEMORY.md` — indexed 8 orphans, fixed POWER_FAILURE_RUNBOOK backlink, trimmed oversized lines, dedup.
- MODIFIED 5 memory files — added frontmatter.
- MODIFIED (machine-local auto-memory) `reference_gitea_internal.md` — corrected the Cloudflare claim (git.azcomputerguru.com = office Cox 72.194.62.10 → NPM/openresty on Jupiter 172.16.3.20).

### Infrastructure & Servers

- **git.azcomputerguru.com path:** office Cox IP `72.194.62.10` → **NPM (openresty) on Jupiter `172.16.3.20`** → Gitea `172.16.3.20:3000`. NOT Cloudflare. External flaps = NPM SSL renewal events.
- **GuruRMM server:** `172.16.3.30:3001`, systemd `gururmm-server`, `ExecStart=/opt/gururmm/gururmm-server` (NOT `/usr/local/bin/`). Now **v0.3.30 / de39e42**, restarted `2026-05-27 15:32:28 UTC`, MainPID 598071. Deploy is manual: `sudo /opt/gururmm/build-server.sh` (git reset --hard origin/main → cargo build --release → stop/cp/start). No Phase 1 migrations, so `.sqlx` cache untouched.

### Commands & Outputs

- Deploy verify: `systemctl cat gururmm-server | grep ExecStart` → `/opt/gururmm/gururmm-server`; `ActiveEnterTimestamp=Wed 2026-05-27 15:32:28 UTC` (== fresh binary mtime); `SubState=running`.
- cargo check (warm, origin/remediation/2026-05-27 @ bdefb1f): `CARGO_EXIT=0`, Finished in 25.53s, 0 errors.
- get_fleet_logs caller fix shape: `get_fleet_logs(&state.db, &["ERROR"], None, since, 1000)` (was 4-arg `"ERROR", since, 1000`).

### Pending / Incomplete Tasks (remediation Phases 2–5, coord TODOs)

- **Phase 2** (`9a1ed577`, HIGH authz/IDOR): org-scope checks.rs / inventory / user_inventory / commands reads / registry; auth on `/agents/status-stream` SSE.
- **Phase 3** (`54239760`, HIGH): `sqlx::query!`/`query_as!` → runtime (mspbackups, updates); build-linux.sh stray `n#` + duplicate beta block.
- **Phase 4** (`58c3fcad`, HIGH/MED): `internal_err` sweep (~127 sites); log redaction; MSPBackups mappings UI; React error boundary; AgentDetail client enrichment row.
- **Phase 5** (`fd677411`, MED/LOW): discovery IP validation, registry wire fields, defer_hours, ws api-key char-boundary, TS `any`, aria-labels, localhost fallback, /metrics+stats wiring.
- **Cleanup candidate:** remove the stale `/usr/local/bin/gururmm-server` (unused by systemd).
- (Carried) Lonestar Apple MDM enrollment; Glabman wifi quote (todo `1bf0cfef`, due 2026-05-27); quantumwms John Velez consent; 2× Business Premium before 2026-06-03; Western Tire #32199; Kittle HIGH; VWP discovery/deployment testbed.

### Reference Information

- gururmm: `4961923` (build fix), `bdefb1f` (create_credential gate), merged to main → `de39e42` (v0.3.30, deployed).
- Reports: `reports/2026-05-27-rmm-audit.md` (62 findings), `reports/2026-05-27-rmm-audit-roadmap.md`.
- Coord TODOs (gururmm, assigned mike): `9a1ed577` `54239760` `58c3fcad` `fd677411`.
- Coord messages to Howard: `114e6209` (fix in flight), `b14e1793` (task list + roadmap guidance + build-check nit), `44ac8984` (server deployed / log fix live). Component `gururmm/server` → `deployed` v0.3.30.

---

## Update: 10:36 PT — GuruRMM Phase 2 authz deploy + Autotask integration

### Session Summary

Implemented and deployed **Phase 2** of the RMM audit remediation (HIGH authz/IDOR cluster). Reused the Phase 1 `authorize_agent_access` helper to org-scope the agent-keyed read/lifecycle handlers across 5 files: `checks.rs` (all 7 handlers), `inventory.rs`, `user_inventory.rs` (incl. the privileged `send_user_action` write), `commands.rs` reads (`get`/`delete`/`cancel` via `command.agent_id`; `list_commands` unfiltered + `clear_command_history` → admin-only), and `registry.rs`. `send_command` (Phase 1) left untouched. Coding Agent (Opus) implemented on branch `remediation/2026-05-27-phase2`; Code Review **APPROVE** (no CRITICAL/HIGH; 2 LOW deferred). `cargo check` GREEN on the build server. FF-merged to gururmm main (`de39e42..87e5e73`) and deployed via `build-server.sh` → **v0.3.31 (`b346b7b`)**, service restarted 16:31:50 UTC, verified running `/opt/gururmm/gururmm-server`. Coord component → deployed; lock released; Phase 2 todo `9a1ed577` done; Howard notified (`4d1feeeb`). SSE `/agents/status-stream` auth **deferred** → new todo `06c16144` (can't add `AuthUser` directly — dashboard consumes it via `EventSource`, which can't send the `Authorization` header that `AuthUser` requires; needs a `?token=` path first).

Switched gears to **Autotask** (Mike: "get creds from Autotask API text file in Documents for testing ClaudeTools with Autotask"). Read `C:\Users\guru\Documents\Autotask API User.txt`, verified the creds against the live REST API: zone detection → **AW01 / webservices5**, `ThresholdInformation` 200 (auth works, 10k req/60min), Companies count 200 (~5,511). Found an **existing but incomplete** vault entry (`msp-tools/autotask.sops.yaml`) holding only a single legacy integration code (`HYTYY…`, no username/secret) — replaced it with the verified 3-value set (username/secret/`integration_code` = `DET4…`) via `sops -e -i`, verified round-trip, committed+pushed the **vault** (`99510c7`). Explored the data model (Companies/Tickets/Contacts/Resources fields + status/priority/queueID/issueType picklists). Scaffolded a `/autotask` command at `.claude/commands/autotask.md` (read-ops-first, modeled on `/syncro`, reads creds from vault) and smoke-tested it end-to-end. Per Mike, **Syncro stays the default PSA; `/autotask` is opt-in and kept LOCAL/undistributed** — saved as `feedback_psa_default_syncro.md` and intentionally NOT committed/pushed.

### Key Decisions

- **Phase 2: merge + deploy now** (Mike's choice) — bundled with the deploy; behavior change only affects non-admin tenant-scoped users (admins bypass via the helper).
- **`list_commands` unfiltered + `clear_command_history` → admin-only** — fail-closed; can't org-scope a cross-tenant query without new DB work (deferred).
- **SSE auth deferred, not force-fit** — adding `AuthUser` as-is would 401 the live dashboard fleet-status stream (EventSource, no header). Tracked as `06c16144`.
- **Autotask vault entry replaced, not appended** — the prior entry was incomplete and had a different integration code than the verified-working one; made the verified set authoritative, preserved the legacy code in notes.
- **`/autotask` kept local / not distributed; Syncro remains default PSA** — Mike's routing rule (`feedback_psa_default_syncro.md`). For this save, `autotask.md` was deliberately excluded from the commit.

### Problems Encountered

- **cargo check on build server failed twice before succeeding** — (1) the `/tmp/rmm-check` worktree's `origin` couldn't auth to Gitea over HTTP and didn't have the branch; (2) `cargo` not on the non-interactive SSH PATH. Fixed by fetching the branch into the authenticated build clone `/home/guru/gururmm`, creating a local branch there, fetching that into `/tmp/rmm-check`, and sourcing `~/.cargo/env`. Result: GREEN on `87e5e73`.
- **No Rust toolchain on the workstation** — the Coding Agent couldn't `cargo check` locally (builds run on the server); ran the authoritative check via SSH.

### Configuration Changes

- gururmm (deployed to main, v0.3.31): `server/src/api/{checks,commands,inventory,registry,user_inventory}.rs` — Phase 2 authz.
- CREATED `.claude/commands/autotask.md` — `/autotask` read-ops skill. **LOCAL ONLY — not committed/pushed** (Mike's "keep it local").
- CREATED `.claude/memory/feedback_psa_default_syncro.md` + MEMORY.md index line — Syncro-default / Autotask-opt-in routing rule.
- UPDATED (vault, pushed `99510c7`) `msp-tools/autotask.sops.yaml` — verified 3-value Autotask creds.

### Credentials & Secrets

- **Autotask API** — vault `msp-tools/autotask.sops.yaml`, fields `credentials.username` / `credentials.secret` / `credentials.integration_code`. Zone **AW01**, base `https://webservices5.autotask.net/ATServicesRest/V1.0/`, three-header auth (`ApiIntegrationCode`/`UserName`/`Secret`). Single shared integration account (no per-tech attribution). Legacy code `HYTYYZ6LA5HB5XK7IGNA7OAHQLH` superseded (in notes). Source file `C:\Users\guru\Documents\Autotask API User.txt` now redundant.

### Infrastructure & Servers

- **GuruRMM server:** now **v0.3.31 (`b346b7b`)**, systemd `gururmm-server` restarted 16:31:50 UTC, MainPID 603630, `ExecStart=/opt/gururmm/gururmm-server`. Build clone `/home/guru/gururmm` (remote `git@172.16.3.20:azcomputerguru/gururmm.git`); check worktree `/tmp/rmm-check`; cargo at `~/.cargo/bin/cargo`.
- **Autotask:** webservices5.autotask.net (zone AW01), ~5,511 companies, rate limit 10,000 req/60min.

### Commands & Outputs

- Phase 2 FF push: `git push origin remediation/2026-05-27-phase2:main` → `de39e42..87e5e73`. CI bump → `b346b7b` (v0.3.31).
- Deploy: `sudo /opt/gururmm/build-server.sh` → release build 4m40s, v0.3.31, restart verified.
- Autotask verify: zoneInformation 200 (AW01/webservices5), ThresholdInformation 200, Companies count 5511.
- Vault: `cd /d/vault && sops --encrypt --in-place msp-tools/autotask.sops.yaml` → committed `99510c7`.

### Pending / Incomplete Tasks

- **RMM Phases 3-5** (coord todos `54239760` / `58c3fcad` / `fd677411`).
- **SSE auth** follow-up `06c16144` — add `?token=` path to `AuthUser`, then lock down `/agents/status-stream`.
- **`/autotask` distribution deferred** — stays local until Mike opts to sync it.
- **Howard's RMM Log Analysis feature design answers** (coord, 2026-05-27T17:16) — captured; fold into the feature when picked up. (Couldn't programmatically mark read; hook may re-surface.)

### Reference Information

- gururmm: Phase 2 branch `remediation/2026-05-27-phase2` (commit `87e5e73`), merged main, deployed `b346b7b` / v0.3.31.
- Vault commit `99510c7` (Autotask creds).
- Coord: Howard msgs sent `4d1feeeb` (Phase 2 deployed); todos `9a1ed577` (done), `06c16144` (SSE), `54239760`/`58c3fcad`/`fd677411` (Phases 3-5).
- `/autotask` skill: `.claude/commands/autotask.md` (local). Memory: `feedback_psa_default_syncro.md`.

---

## Update: 11:04 PT — /mailbox skill (ACG M365 read + gated send-as)

### Session Summary

Built a new `/mailbox` command (`.claude/commands/mailbox.md`) for reading and sending ACG's own M365 mail. Discovered while pulling a client email (Quantum/Sheila — see `clients/quantumwms/`) that the existing **Claude-MSP-Access Graph app (`fabb3421`)** can read ACG's own mailboxes: a `client_credentials` token against the **azcomputerguru.com** tenant + `GET /users/<mbx>/messages` works (the app holds tenant-wide Mail.ReadWrite + Mail.Send). Codified that into `/mailbox`: defaults to the running user's mailbox (`identity.json` → mike@/howard@), read ops (`inbox`/`unread`/`search`/`from`/`read`) plus **hard-gated** send/reply (full To/Cc/Subject/Body preview + explicit confirm, external recipients flagged, no retries/bulk, saved to Sent). Smoke-tested the read path live (HTTP 200, token cache). Committed + pushed (`f8c00d3`) — distributed to the fleet (per-user scoped, so Howard gets it for his own mailbox). Also gitignored `.claude/commands/autotask.md` (`b22de6c`) so `/save`/`/sync`'s `git add -A` can't push it — making the earlier "keep /autotask local" decision stick.

### Key Decisions

- **Distributed `/mailbox`** (committed + pushed) — it defaults to each user's own mailbox, so it's per-user scoped and safe to share; send is gated for everyone.
- **Gitignored `autotask.md`** rather than relying on controlled commits each time — reliable way to keep `/autotask` local.
- **`/mailbox` is for ACG's OWN mailboxes; client-tenant mailbox reads stay in `/remediation-tool`** (same Graph app, different purpose) — documented the boundary in the skill.

### Problems Encountered

- **OData query params with spaces broke Python urllib** (`$orderby=receivedDateTime desc` → `InvalidURL: control characters`). Caught by the read smoke test; fixed by URL-encoding spaces in the Graph helper (`url.replace(" ", "%20")`) and re-verified HTTP 200.

### Configuration Changes

- CREATED `.claude/commands/mailbox.md` — `/mailbox` skill (committed + pushed `f8c00d3`).
- MODIFIED `.gitignore` — added `.claude/commands/autotask.md` (committed `b22de6c`).
- `.claude/tmp/mailbox-token.json` — token cache (gitignored).

### Credentials & Secrets

- **ACG's own email is Microsoft 365** (tenant `azcomputerguru.com`). Read/send via **Claude-MSP-Access Graph app `fabb3421`** — vault `msp-tools/claude-msp-access-graph-api.sops.yaml` → `credentials.credential`. Token: `client_credentials`, scope `https://graph.microsoft.com/.default`, endpoint `https://login.microsoftonline.com/azcomputerguru.com/oauth2/v2.0/token`. App has tenant-wide Mail.ReadWrite + Mail.Send (can read/send ANY ACG mailbox).

### Infrastructure & Servers

- Graph: `https://graph.microsoft.com/v1.0/users/<mbx>/messages` (read; `$search`/`$filter` mutually exclusive), `/sendMail` (POST, returns **202 empty**), `/messages/{id}/reply`.

### Commands & Outputs

- Verified: token (client_credentials) → `GET /users/mike@azcomputerguru.com/mailFolders/inbox/messages?$top=4&$orderby=receivedDateTime%20desc` → HTTP 200.

### Pending / Incomplete Tasks

- None for the skill. `/mailbox send` is available but always gated — no message leaves without explicit per-send confirmation.

### Reference Information

- Commits: `b22de6c` (gitignore autotask), `f8c00d3` (add /mailbox). Skill: `.claude/commands/mailbox.md`. Graph app `fabb3421` (see also `feedback_365_remediation_tool.md`).

---

## Update: 14:55 PT — Quantum M365 onboarding; IX autodiscover fix; Syncro emergency/labor rule overhaul

### Session Summary

Multi-client afternoon. **Michael Johnson #32329** (residential, prepaid=none): pulled the calendar-emergency ticket; emailed a hosting offer (his neptune-hosted mailbox has never been billed — product `45869` "Email - Exchange Hosted Email" $5/mo, or $50/yr) and **waived today's emergency fee** as a courtesy (noting declared emergencies normally carry a half-hour min). Noticed he was getting **Outlook cPanel redirect popups** and traced it to the `simplehost.email` DNS zone on **IX** (`172.16.3.10`, WHM/cPanel): `autodiscover`/`autoconfig` + a set of SRV records pointed at the cPanel box instead of the real mail host. Fixed `autodiscover` → CNAME `mail.acghosting.com` and removed all 6 SRV records (autodiscover/caldav/carddav); left `autoconfig` per Mike. Backed up the zone first. Emailed Michael that it's resolved.

**Quantum Wealth Management** M365 migration advanced substantially — full detail in `clients/quantumwms/session-logs/2026-05-27-session.md`. Summary: Jen Curry (IFG) approved the move; appointments + PST-backup TODO + an empty "365 Services" recurring template created; the GoDaddy-parked tenant was bypassed for a **fresh tenant `2fd0092b`**, onboarded with the full ComputerGuru app suite (Pax8 GDAP + `onboard-tenant.sh`); started the security baseline — break-glass GA, Conditional Access in report-only (programmatic), John's password set, office static-IP requested for a trusted-location policy.

**Cascades #32332** (prepaid) drove a Syncro rule overhaul. Howard had billed an emergency new-user setup with **made-up labor line names** ("Emergency Call Setup", "Onsite Computer Setup") on the wrong product. Corrected to a single line — `26184` "Labor - Emergency or After Hours Business" @ **2.25** (1.5 hrs × 1.5) — **via `update_line_item` (preserving Howard's `user_id=1750`** so his commission stayed intact). Posted an internal note for Winter; Winter resolved it / handled the invoice+QB re-sync.

That cascade produced several **rule changes** (all encoded in memory + the relevant skills): emergency billing (prepaid → `26184` @ hours×1.5 quantity, replacing the old `26118`×1.5; non-prepaid → `26184` with channel rate: Onsite $262.50, Remote/In-Shop $225); **never make up labor items** (existing product + real name; made-up items break the QuickBooks sync; description is free text); **corrections preserve the original tech's `user_id`** (commission); **Conditional Access may now be managed programmatically** (report-only first + exclude break-glass + confirm before enforce); and the **`fabb3421` app is deprecated** for customer-tenant onboarding (breaks AADSTS650052 on no-MDE tenants — use the tiered suite).

### Key Decisions

- **IX autodiscover fix via `whmapi1`, backup-first** — removed the cPanel proxy-subdomain hijack (autodiscover A→cPanel + SRVs) that caused Outlook redirect alerts; pointed autodiscover at the real Exchange (`mail.acghosting.com` = 67.206.163.124). Affects all `simplehost.email` hosted-mail clients, not just Michael.
- **#32332 corrected in place (`update_line_item`), not remove+add** — preserved Howard's `user_id`/commission. Codified as a rule: corrections are a debug action, don't reassign labor to the correcting tech.
- **Emergency rule: prepaid now uses `26184`** (was `26118`) at hours×1.5 quantity — keeps the line labeled emergency for QuickBooks; the dollar double-1.5 worry is moot for prepaid ($0 invoice).
- **Quantum: fresh tenant + CA over Security Defaults + programmatic CA** (see Quantum log).

### Problems Encountered

- **Wrong-tenant consent** for Quantum (pointed at GoDaddy `ddf3d2c9`; `sysadmin@` bounced) — re-discovery showed the domain had verified into the new `2fd0092b`; corrected. (Quantum log.)
- **`onboard-tenant.sh` replication-lag perm errors** — re-ran (idempotent) → clean.
- **#32332 prepaid gotcha** — Mike's "use the emergency item `26184`" would've been wrong for a prepaid customer under the OLD rule; the prepay check (27 hrs) caught it, then Mike clarified the rule (prepaid emergency = `26184` ×1.5 quantity).

### Configuration Changes

- IX `172.16.3.10`: `/var/named/simplehost.email.db` — `autodiscover` A→CNAME `mail.acghosting.com`, 6 SRV records removed, `autoconfig` left. Backup `simplehost.email.db.bak-claude-20260527`.
- Memory (new): `feedback_syncro_no_madeup_labor_items.md`, `feedback_syncro_corrections_preserve_tech.md`, `feedback_ca_programmatic_management.md`, `project_quantum_godaddy_m365_tenant.md`. (modified): `feedback_syncro_emergency_billing.md`, `feedback_365_remediation_tool.md`, `MEMORY.md`. (committed earlier this session): `feedback_psa_default_syncro.md`, `reference_coord_messages_api_shape.md`.
- Skills: `.claude/commands/syncro.md` (emergency-billing rules, 4 spots), `.claude/skills/remediation-tool/SKILL.md` (CA-manual boundary relaxed), `.claude/skills/remediation-tool/references/gotchas.md` (Quantum tenant row).
- Syncro: #32329 (Michael) hosting offer + waiver + DNS-fix notes, status Waiting on Customer; #32332 (Cascades) single corrected emergency line + internal note.

### Credentials & Secrets

- IX `simplehost.email` autodiscover now → `mail.acghosting.com` (neptune Exchange, `67.206.163.124`). IX = `172.16.3.10` (vault `infrastructure/ix-server.sops.yaml`).
- Michael Johnson hosted-email billing product: `45869` ("Email - Exchange Hosted Email", $5). Customer 152567.
- Quantum creds (tenant `2fd0092b`, break-glass, John's initial pw) — in the Quantum client log.

### Infrastructure & Servers

- IX (`172.16.3.10`, ix.azcomputerguru.com, ext 72.194.62.5): Rocky Linux WHM/cPanel, 80+ accounts. Hosts `simplehost.email` DNS zone (ACG hosted-email domain). `mail.acghosting.com` = neptune Exchange (`67.206.163.124`).

### Commands & Outputs

- IX: `whmapi1 removezonerecord/addzonerecord zone=simplehost.email ...` (autodiscover→CNAME, SRVs removed); verified via `dig +short autodiscover.simplehost.email`.
- #32332: `PUT /tickets/111233015/update_line_item` → `26184` @ 2.25, `user_id` preserved 1750.

### Pending / Incomplete Tasks

- **Michael #32329:** awaiting hosting choice ($5/mo vs $50/yr); ticket Waiting on Customer.
- **Cascades #32332:** Resolved; Winter verifying invoice/QB re-sync.
- **Quantum:** see Quantum log — Thu 5/28 1PM Jen DNS + mail cutover, PST backups, CA enforce, Defender, static IP.
- IX autodiscover may be recreated by cPanel proxy-subdomain feature — if Michael's popups return, disable that feature in WHM.

### Reference Information

- Tickets: #32329 (id 111214431, Michael Johnson), #32332 (id 111233015, Cascades), #32323 (id 111056440, Quantum).
- IX `172.16.3.10`; mail.acghosting.com `67.206.163.124`. Products: hosting `45869`, emergency `26184`, onsite `26118`, remote `1190473`. Tech user_ids: Mike 1735, Howard 1750, Winter 1737.
- Quantum tenant `2fd0092b`; detail in `clients/quantumwms/session-logs/2026-05-27-session.md`.

---

## Update: 16:06 PT — BEAST Discord bot: emergency billing test ticket


## User
- **User:** Mike Swanson (mike)
- **Machine:** GURU-BEAST-ROG
- **Role:** admin

---

## Session Summary

Mike requested a 1.5-hour emergency ticket be created in Syncro against the internal test client (Arizona Computer Guru, customer ID 15353550). The description and resolution were to be fabricated. The scenario chosen was an emergency NAS outage: a Synology DS923+ went offline after a UPS power event, causing all SMB shares to become inaccessible. Resolution involved SSH access to the NAS, fsck on the volume group, and re-enabling SMB service after the dirty-volume flag was cleared.

Ticket #32335 was created via the Syncro API with subject "Emergency - NAS device offline, share access lost for all workstations," status Resolved, and two comment blocks (description and resolution). A 1.5-hr emergency labor line item was then added using product 26184 (Labor - Emergency or After Hours Business) at the live rate of $262.50/hr, for a ticket total of $393.75.

During line item creation, a bug was discovered in the billing process documentation: the `add_line_item` API endpoint requires the field name `price_retail`, not `price`. Passing `price` silently succeeds (HTTP 200) but discards the value, billing $0.00. This required multiple attempts to isolate — a test line item and a zero-price line item were left on the ticket as artifacts of the troubleshooting. Both are zero-value and do not affect the total, but should be manually deleted in the Syncro UI.

The billing skill documentation at `.claude/commands/syncro-emergency-billing.md` was patched to replace `price` with `price_retail` in the example JSON body, add an explicit warning about the silent-discard behavior, and reference ticket #32335 as the discovery event. The corrected line item (ID 42611396) confirmed the fix works: `price_retail: 262.5` in the response and correct total on the ticket.

---

## Key Decisions

- Used "Arizona Computer Guru" (customer 15353550) as the internal test client — the only ACG-named customer in Syncro, the obvious choice for internal test billing.
- Fabricated a NAS outage scenario rather than a server/workstation scenario — NAS emergencies are common, the resolution steps are plausible and concise, and it doesn't reference any real client infrastructure.
- Applied the emergency premium (product 26184) directly rather than suggesting it, because Mike explicitly requested an "emergency ticket" — per billing rules, explicit request = apply the premium.
- Non-block customer path: single line item at $262.50/hr, no prepay split needed.
- Kept the two zero-value artifact line items on the ticket rather than pursuing further API workarounds — they net zero, the correct line item is present, and manual UI deletion is straightforward.

---

## Problems Encountered

- **`price` field silently discarded by add_line_item API.** Passing `"price": 262.5` returned HTTP 200 but the line item was billed at $0.00. Isolated through iterative testing: trying `update_line_item` (404), `PUT /tickets/{id}` with `line_items_attributes` (no-op on price), direct `PUT/PATCH` on line item (404), and finally re-adding with `"price_retail": 262.5` which succeeded. The `price_retail` field both set the value correctly and returned it in the response. **Resolution:** patched billing skill doc; added correct line item via `price_retail`.
- **`delete_line_item` endpoint returned 404.** Both `DELETE` with query param and `POST` with JSON body returned 404. The `_destroy` flag in `line_items_attributes` PUT also had no effect. No working delete path found via API — manual UI deletion is required for the two artifact line items.

---

## Configuration Changes

- **Modified:** `.claude/commands/syncro-emergency-billing.md`
  - Changed `"price": 0.0` to `"price_retail": 0.0` in the example JSON body
  - Added warning: "`price_retail` CRITICAL — use `price_retail`, NOT `price`. Using `price` silently discards the value and bills $0.00 even though the API returns HTTP 200. Confirmed broken 2026-05-27 (ticket #32335)."
  - Updated the `price` annotation to explain block vs non-block behavior using `price_retail`
  - Added instruction to verify `price_retail` in the response after adding a line item

---

## Credentials & Secrets

- **Syncro API key:** retrieved from vault path `msp-tools/syncro.sops.yaml` → `credentials.credential` (not logged here)

---

## Infrastructure & Servers

- **Syncro tenant:** computerguru.syncromsp.com
- **Syncro customer:** Arizona Computer Guru | ID: 15353550

---

## Commands & Outputs

```
# Customer search
GET /api/v1/customers?query=Arizona+Computer+Guru
→ ID 15353550, "Arizona Computer Guru", Michael Swanson

# Live rate check
GET /api/v1/products/26184
→ price_retail: 262.5

# Ticket creation
POST /api/v1/tickets
→ ticket id: 111265518, number: 32335, status: Resolved

# Correct line item (working)
POST /api/v1/tickets/111265518/add_line_item
  {"product_id": 26184, "name": "Labor - Emergency or After Hours Business",
   "description": "Emergency remote - NAS offline...",
   "quantity": 1.5, "price_retail": 262.5, "taxable": false}
→ id: 42611396, price_retail: 262.5, qty: 1.5

# Final ticket total: $393.75 (1.5 hrs x $262.50)
```

---

## Pending / Incomplete Tasks

- **Manual cleanup needed:** Delete two zero-value line items from ticket #32335 in the Syncro UI:
  - ID 42611371 — qty 1.5, price $0.00 (artifact from `price` field bug)
  - ID 42611384 — qty 0.0, price $262.50 (artifact from price field test)
  - Correct line item to keep: ID 42611396 — qty 1.5, price $262.50

---

## Reference Information

- **Syncro ticket:** #32335 | https://computerguru.syncromsp.com/tickets/111265518
- **Product 26184:** Labor - Emergency or After Hours Business | $262.50/hr
- **Billing skill doc:** `.claude/commands/syncro-emergency-billing.md`
- **Vault path accessed:** `msp-tools/syncro.sops.yaml`

---

## Update: 16:29 PT — Discord Bot: Emergency Test Ticket + Syncro Skill Fix

## User
- **User:** Mike Swanson (mike)
- **Machine:** GURU-BEAST-ROG
- **Role:** admin

## Session Summary

Mike requested a 1.5hr emergency ticket on the ACG internal test client (Arizona Computer Guru, customer_id 15353550) via the Discord bot, with fabricated description and solution. The ticket was created as a simulated after-hours RMM server outage scenario.

During the billing preview, the bot incorrectly assumed the delivery channel was Remote without being told. Mike flagged this as a gap in the skill — "emergency" is a billing modifier, not a delivery channel, and Remote vs Onsite vs In-Shop cannot be guessed since they carry different price_retail values ($225 vs $262.50). Mike confirmed the correct channel was Onsite before billing proceeded.

Before executing the ticket, Mike directed that the fix be baked into the syncro skill itself rather than relying on MEMORY.md. Two targeted edits were made to `.claude/commands/syncro.md`: one to the Hard Rules section and one to the Billing workflow Step 1 gather prompt. The change was committed and pushed so all machines pick it up via sync.

After the skill fix was committed and synced, the ticket was created and fully billed: Syncro ticket #32336 created for Arizona Computer Guru, resolution comment posted, emergency onsite line item added (26184, 1.5 hrs @ $262.50 = $393.75), invoice generated, ticket marked Invoiced, and bot alert posted to #bot-alerts.

## Key Decisions

- **Delivery channel must be asked, not inferred for emergency billing:** The existing rule said "ask for labor type" but did not distinguish between billing type (emergency/regular) and delivery channel (remote/onsite/in-shop). Since these map to different price_retail values and Syncro line items, the channel must always be confirmed explicitly.
- **Fix goes in the skill, not MEMORY.md:** Mike's explicit direction — MEMORY.md is per-machine ephemeral context; the skill file is the durable, cross-machine source of truth for billing rules.
- **Two edit points in syncro.md:** The Hard Rules section (authoritative rules) and the Billing workflow Step 1 gather prompt (operational checklist) both needed updating to ensure the rule is encountered at the right point during execution.

## Problems Encountered

- **Bot guessed delivery channel:** Bot assumed Remote for an emergency ticket without being told. Caught by Mike before any API call was made. Corrected by asking, then updating the skill.

## Configuration Changes

- `.claude/commands/syncro.md` — updated Hard Rules billing rule and Billing workflow Step 1 to explicitly require delivery channel confirmation for emergency billing (commit 58d424e)

## Credentials & Secrets

None accessed beyond standard Syncro API key (Mike's key, already in skill).

## Infrastructure & Servers

- Syncro: computerguru.syncromsp.com
- ACG internal test customer_id: 15353550

## Commands & Outputs

```
# Ticket created
Ticket ID: 111266587 | Number: 32336

# Invoice
Invoice ID: 1650438933 | Total: 393.75

# Bot alert
[OK] post-bot-alert: posted to #bot-alerts (message_id=1509337603525316671)

# Commit
58d424e  syncro: require delivery channel for emergency billing
```

## Pending / Incomplete Tasks

None.

## Reference Information

- Syncro ticket #32336: https://computerguru.syncromsp.com/tickets/111266587
- Invoice #1650438933: $393.75
- Commit: 58d424e (main, pushed to Gitea)
- syncro.md edited: `.claude/commands/syncro.md`

---

## Update: 19:40 PT — LHM Security Violation Discovery (Mac)

### User
- **User:** Mike Swanson (mike)
- **Machine:** Mac
- **Role:** admin

### Summary

Session focused on log analysis feature design and critical security discovery about LibreHardwareMonitor. Coordinated identity.json Phase 2 completion (GURU-5070, GURU-KALI, GURU-BEAST-ROG confirmed via coord). Updated sync.sh and syncro.md to read Python/Ollama config from identity.json, eliminating 2-second probe delays. Cleaned up CLAUDE.md redundant Ollama content.

Investigated why log analysis findings UI (committed May 27 07:18) wasn't visible—dashboard last built May 20 (7 days stale). While planning rebuild, user asked about LHM origins. Historical analysis revealed LHM added May 14, 2026 as "quick fix" when sysinfo couldn't collect Windows temps. User then revealed **LHM fails Windows Defender with kernel-level exploit detection**.

Critical discovery: LHM violates GuruRMM's founding "no external binaries" security principle. LHM is third-party .exe bundled in MSI that loads kernel driver (WinRing0x64.sys), creating supply chain attack surface GuruRMM was designed to avoid. Defender flags it as PUA. 64 agents deployed, unknown Defender impact.

User requested comprehensive interview for Howard about log analysis feature design (3-level system: platform/site/machine issues with different remediation strategies). Sent two coord messages to Howard: (1) 20-question interview about workflows and priorities, (2) high-priority LHM security violation analysis with emergency removal recommendation.

### Key Decisions

- **Dashboard rebuild paused** — Waiting for Howard's log analysis workflow requirements before implementing feature
- **LHM emergency removal recommended** — v0.6.28 with LHM stripped (temps unavailable but secure), then proper WMI solution in v0.6.29
- **ADR-007 documentation needed** — "No External Binaries" architecture decision to prevent future violations
- **Interview Howard first** — His field perspective critical for log analysis design (not just implementing Mike's proposal)

### Configuration Changes

- `.claude/identity.json`: Fixed hostname `Mikes-MacBook-Air` → `Mac`
- `.claude/scripts/sync.sh`: Read Python from identity.json (lines 119-133)
- `.claude/commands/syncro.md`: Read Ollama/Python from identity.json (lines 59-62, 138-191)
- `.claude/CLAUDE.md`: Removed Ollama table, condensed descriptions

### Coordination Messages Sent

- `38df069e`: Log analysis interview (20 questions, normal priority, to Howard-Home)
- `5b1f36e8`: LHM security violation (high priority, to Howard-Home)

### LHM Timeline

- Dec 21, 2025 (`dfc3be1`): Temperature feature added via sysinfo (Rust crate, acceptable)
- May 14, 2026 (`70c1fff`): LHM bundled as workaround (VIOLATED security principle)
- 6 months of bugs: Session 0 issues, WMI failures, complexity
- May 27, 2026 (`612c00a`): Analysis panel fix for LHM_RUNNING flag
- May 27, 2026 (today): Defender blocker discovered, violation recognized

### Pending

- Howard's interview response (log analysis workflows)
- Howard's LHM impact assessment (Defender blocks? Temp value?)
- Emergency patch decision (ship v0.6.28 this week?)
- ADR-007 documentation
- Dashboard rebuild (after feature design clear)


---

## Update: 20:49 PT — LHM/WinRing0 pulled from GuruRMM agent install (GURU-5070)

## User
- **User:** Mike Swanson (mike)
- **Machine:** GURU-5070
- **Role:** admin

### Session Summary

Microsoft Defender alerted on a managed Windows endpoint with `VulnerableDriver:WinNT/Winring0` quarantining `C:\Program Files\GuruRMM\lhm\LibreHardwareMonitor.sys` (Trojan / Severe). Connected to Howard's earlier LHM WMI crash report on Cascades MAINTENANCE-PC — same component, deeper root cause: LHM bundles WinRing0 (CVE-2020-14979 ring-0 arbitrary R/W, on Microsoft's vulnerable-driver blocklist). GuruRMM was effectively shipping a known-vulnerable kernel LPE primitive to every managed Windows endpoint, and on Win11 with HVCI/blocklist active the driver also fails to load.

Scoped blast radius: 58 of 64 agents are Windows (5 linux, 1 macos; versions mostly 0.6.39 with a tail to 0.6.2; 39 online / 19 offline at the time). All 58 carry the bundled LHM. Owner decision: pull LHM from the install now and defer the proper headless temperature-monitoring replacement. Spawned a Coding Agent which removed LHM bundling from `installer/gururmm-agent.wxs` (10 components — the Pluto-built WiX MSI), `installer/gururmm-agent-linux.wxs` (32 components — the Linux-built CI variant that also shipped LHM, surprise finding), and `scripts/setup-build-server.ps1` (no longer downloads/stages LHM 0.9.4 net472). `agent/src/ohw.rs::LhmGuard::start()` is now a clean no-op; the module + `LHM_RUNNING` flag are kept so re-enabling later is a small change. `metrics::collect_temps_from_lhm()` is already gated on `LHM_RUNNING` (now permanently false) — temps simply return None and no other agent code needed changing.

Push reconciled across parallel commits. The gururmm push initially rejected non-fast-forward against a parallel SPEC-011 spec commit from another machine; rebase (non-overlapping markdown) → `bc3c2bd` live. CI auto-bump pushed `6326ec6` on top. The ClaudeTools parent push then rejected against Howard's auto-sync that bumped the same submodule pointer to the earlier (parallel) SHA — Git auto-resolved by taking the newer pointer (transitive supersede: `fae47f2` is `bc3c2bd`'s direct parent, so the newer pointer includes Howard's transitively); `6902645` pushed. The webhook fired the agent build; v0.6.46 built clean on Pluto in 1126s, signed, marked beta, latest symlinks updated, old v0.6.45 removed.

Coordinated the change fleet-wide while the build was in flight: lock claimed on `gururmm/agents` (4h TTL), component state set to `building`, broadcast sent to ALL_SESSIONS describing the in-flight change plus a don't-edit-these-files ask. After build completed: component flipped to `deployed v0.6.46`, lock released, follow-up broadcast sent. Cleanup todo logged for the runtime-registered WinRing0 kernel service / extracted `.sys` on already-affected endpoints — MSI MajorUpgrade removes the bundled `lhm` folder when agents update but does NOT touch the runtime-registered kernel service (LHM extracts/registers it at runtime, not via MSI File components).

Side review: a new `check-messages.sh` arrived via sync (`a35b583`, from GURU-KALI). Substantive improvements — per-machine broadcast seen-tracking via `.claude/coord-broadcasts-seen` (eliminates the PUT `/read` clobber on broadcasts that share one server-side `read_at`), alias-merge query (catches `to=mike`/`to=howard`), JSON control-char sanitization, mode-file auto-init, Windows toast. Flagged one cross-platform concern: `sanitize_json` calls `python3` unconditionally; on Windows boxes without `python3` on PATH (per `feedback_python_windows`, ACG uses `py`) the function silently fails and unread messages get dropped. Fix awaiting owner go-ahead.

### Key Decisions

- **Pull LHM, do NOT add a Defender exclusion.** Excluding a genuinely-vulnerable ring-0 driver leaves an LPE primitive on every managed endpoint; unacceptable for an MSP. The Win11 vulnerable-driver blocklist also prevents driver load regardless of AV exclusion.
- **Keep `LhmGuard` module + `LHM_RUNNING` flag as a no-op rather than delete.** Re-enabling a future replacement temp path is a small change later; gutting the temp-monitoring scaffolding would mean rebuilding from scratch.
- **Trust Git's auto-resolution of the submodule-pointer collision** on the parent rebase: Howard's pointer `fae47f2` is `bc3c2bd`'s direct parent, so newer-wins is a strict supersede, not a divergence. Verified post-rebase via `git ls-tree HEAD projects/msp-tools/guru-rmm` = `bc3c2bd`.
- **Removed LHM from both `gururmm-agent.wxs` AND `gururmm-agent-linux.wxs`.** Surprise finding: the Linux-built CI MSI variant also bundled LHM (32 components vs the 10-component hand-curated Pluto WXS). Removing only one would leave the other build path producing a vulnerable MSI.
- **Coord broadcast + lock during the rollout.** Howard's parallel SPEC-010 work touches the same agent areas; a fleet-wide rollout warranted explicit "don't concurrently edit these files" coordination.

### Problems Encountered

- **gururmm push rejected non-fast-forward** against a parallel SPEC-011 spec commit (`fae47f2`). Resolved: verified non-overlapping (markdown only via `git log --stat fae47f2`), rebased, pushed `bc3c2bd`.
- **ClaudeTools parent push rejected non-fast-forward** against Howard's auto-sync `47d6519` which bumped the same submodule pointer to the parallel (earlier) SHA. Resolved: Git auto-resolved by taking the newer pointer on rebase; `git ls-tree` verified the resulting pointer = `bc3c2bd`.
- **Coord todo POST returned `"error parsing the body"` with the full ~1.5KB description**, despite the JSON validating locally with jq. A compressed ~700-char text body POSTed cleanly via the same code path. Likely an undocumented body-size limit or content-specific parser issue on `/api/coord/todos` somewhere between those sizes; the long version failed on both POST and PUT identically. Worth knowing for future long todos.
- **Coord todo POST validation missed required fields.** Schema (`api/schemas/coord_todo.py`) requires `created_by_user` + `created_by_machine`; first attempt missed both. Returned structured `Request validation failed` — added and retried.
- **Initial `/api/agents` response shape misparse.** Endpoint returns a bare array of 64, not `{agents:[...]}` or `{data:[...]}` — first jq filter silently returned empty. Re-parsed correctly after dumping the shape.
- **`cargo check` could not run locally** (no Rust toolchain on GURU-5070's Git Bash). Verified the change by inspection; CI builds the agent crate on Pluto, so a compile error would fail the build rather than deploy badly. v0.6.46 built clean, confirming.

### Configuration Changes

- `projects/msp-tools/guru-rmm` commit **`bc3c2bd`** ("fix(agent): remove LibreHardwareMonitor bundling — Defender flags WinRing0 driver"): 6 files, +57/-331.
  - `agent/src/ohw.rs` — `LhmGuard::start()` is now a clean no-op (no spawn, no `taskkill`, no 30s WMI poll). Module + `LHM_RUNNING` flag preserved.
  - `agent/src/main.rs`, `agent/src/service.rs` — stale "Start LibreHardwareMonitor / Drop kills the child" comments updated to reflect no-op.
  - `installer/gururmm-agent.wxs` — removed `ComponentGroupRef Id="LhmComponents"`, `LHM_DIR`, and the 10-File `ComponentGroup` (LibreHardwareMonitor.exe, .exe.config, LibreHardwareMonitorLib.dll, HidSharp.dll, Aga.Controls.dll, Newtonsoft.Json.dll, OxyPlot.dll, OxyPlot.WindowsForms.dll, Microsoft.Win32.TaskScheduler.dll, System.CodeDom.dll).
  - `installer/gururmm-agent-linux.wxs` — removed 32 `ComponentRef`/`<File>` entries + `LHM_DIR` (full LHM 0.9.4 net472 file set).
  - `scripts/setup-build-server.ps1` — LHM download/stage replaced with "delete stale `C:\gururmm\lhm` if present" cleanup step.
- ClaudeTools commit **`6902645`** ("chore(submodule): advance guru-rmm — LHM removed from agent install"): submodule pointer `495575d → bc3c2bd`.
- Coord state writes (all from session `GURU-5070/claude-main`):
  - PUT `components/gururmm/agents` → `building` → then `deployed v0.6.46`.
  - POST `locks` on `gururmm/agents` (id `04fb5c30`, ttl 4h) → DELETE after build.
  - POST `messages` to ALL_SESSIONS (`620af7f5` in-flight + `e032f029` done).
  - POST `todos` `42c08298` (project_key `gururmm`, assigned_to_user `mike`).

### Credentials & Secrets

None new.

### Infrastructure & Servers

- **GuruRMM build server:** `guru@172.16.3.30` (Linux). Build logs `/var/log/gururmm-build-{windows,linux}.log`; last-built-commit at `/opt/gururmm/last-built-commit-{windows,linux,mac}`; artifacts under `/var/www/gururmm/downloads/`; webhook handler `localhost:9000` (PID 518498, ~3.2 days uptime, `ok`).
- **Pluto build VM:** `172.16.3.36` (Windows Server 2019 VM on Jupiter) — does the actual Windows MSI/EXE build via `build-windows.sh` driven from `guru@172.16.3.30`.
- **Coord API:** `http://172.16.3.30:8001/api/coord` — messages, locks, components, todos.
- **GuruRMM control API:** `http://172.16.3.30:3001` — `claude-api@azcomputerguru.com / ClaudeAPI2026!@#`.
- **Fleet at action time:** 64 agents total = 58 Windows + 5 Linux + 1 macOS; Windows 39 online / 19 offline.

### Commands & Outputs

- Fleet count: `curl /api/agents -H "Authorization: Bearer $TOKEN" | jq -r 'group_by(.os_type)[] | "\(.[0].os_type): \(length)"'` → linux 5, macos 1, windows 58.
- Build complete log line: `2026-05-28 03:07:42 [WINDOWS] === Windows build complete: v0.6.46 in 1126s ===`.
- last-built-commit post-build: windows = linux = `6326ec6ca90e853021ae59fc75c6d031307cdca7` (includes `bc3c2bd`).
- Coord todo POST error sample (long text): `{"detail":"There was an error parsing the body"}`. Validation-error sample (minimal POST): `{"error":"Request validation failed","details":{"validation_errors":[{"field":"body.created_by_user","message":"Field required","type":"missing"},{"field":"body.created_by_machine","message":"Field required","type":"missing"}]}}`.
- Coord todo schema (`api/schemas/coord_todo.py`): `text: str` (required, no max_length declared), `project_key: Optional[str] <=100`, `assigned_to_user: <=50`, `created_by_user: str <=50` (required), `created_by_machine: str <=100` (required), `auto_created: bool`, `source_context: Optional[str]`, `due_at: Optional[datetime]`, `parent_id: Optional[UUID]`.

### Pending / Incomplete Tasks

- **Endpoint WinRing0 service cleanup** (coord todo `42c08298`, gururmm): runtime-registered kernel service + any non-quarantined `.sys` on machines that ran the old agent are not MSI-tracked. Options: extend `installer/cleanup/gururmm-cleanup.exe` to stop+delete the WinRing0 service, or push a one-shot RMM `sc.exe stop/delete WinRing0_1_2_0; rm <leftover .sys>` command across the 58 affected Windows endpoints. Ties to deferred temperature-monitoring replacement decision.
- **Proper headless temperature monitoring** (deferred — "revisit later"): replace LHM with a non-driver path (ACPI `MSAcpi_ThermalZoneTemperature` / vendor WMI) or accept no detailed temps fleet-wide.
- **`check-messages.sh` Windows `python3` compatibility** (open question to owner at end of /sync review): hook calls `python3` unconditionally inside `sanitize_json`; on Windows boxes without `python3` on PATH (most of ACG uses `py` per `feedback_python_windows`) the function silently fails and unread messages get dropped through the empty-result fallback. Proposed fix: `command -v python3 || command -v py || command -v python` or read `.python.command` from `identity.json` like the other scripts.
- Beta-channel rollout: agents pick up v0.6.46 on next check-in; verify a couple of online endpoints actually update and the `lhm` folder gets removed by MajorUpgrade.

### Reference Information

- Commits: gururmm **`bc3c2bd`** (LHM removal), gururmm **`6326ec6`** (CI auto-bump), ClaudeTools **`6902645`** (submodule advance).
- Coord IDs: lock `04fb5c30-34ff-4dd0-9696-70b380f93def`, todo `42c08298-be75-4f6d-954c-c9a9fd1138ee`, broadcast messages `620af7f5-f238-469b-a595-16f86e861458` (in-flight) + `e032f029-4aa2-4a3e-985f-f668ea174d61` (done).
- Build artifacts (v0.6.46, beta, signed): `gururmm-agent-base-0.6.46.msi`, `gururmm-agent-windows-amd64-0.6.46.exe`, `gururmm-agent-windows-x86-0.6.46.exe`, `gururmm-agent-windows-legacy-{amd64,x86}-0.6.46.exe`. `*-latest.{exe,msi}` symlinks all point at 0.6.46.
- Driver context: WinRing0 / CVE-2020-14979 (ring-0 arbitrary R/W LPE) / Microsoft vulnerable-driver blocklist / Defender signature `VulnerableDriver:WinNT/Winring0`.
- Related earlier session log on this calendar day: `clients/peaceful-spirit/session-logs/2026-05-27-session.md` (Bridgette VPN deployment + Syncro #32271 customer-visible completion note — different work, captured separately).
- New hook reviewed: `.claude/scripts/check-messages.sh` (commit `a35b583`, authored from GURU-KALI).
