Compare commits

...

381 Commits

Author SHA1 Message Date
bbb19db2f6 dataforth: preserve fork operational context as clients/dataforth/CLAUDE.dataforth.md
Relocated verbatim from .claude/CLAUDE.md so the ad2 fork stops editing the
shared fleet harness doc. After rebase onto main, .claude/CLAUDE.md = the lean
fleet version; Dataforth ops context lives here. Keeps future ad2 syncs clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 13:45:36 -07:00
49c9eb50aa sync: auto-sync from AD2 at 2026-06-17 13:35:55
Author: Mike Swanson
Machine: AD2
Timestamp: 2026-06-17 13:35:55
2026-06-17 13:45:32 -07:00
sysadmin
53ba8e5f4e Add AD scripts and stage import instructions
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-17 13:45:28 -07:00
sysadmin
f8d9e3e3ec Session log 2026-04-03: WO import, 7B support, PG migration started
- 33K work orders imported, 2.27M records linked
- 7B exact-match formatter added (31 params, 120VAC, Packing Check List)
- TXT formatting refined to match QB TAB positions exactly
- PostgreSQL 18 installed on AD2, database created
- SQL Server Express uninstalled
- Full Dataforth audit document generated

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-17 13:45:28 -07:00
595bf0fa52 sync: auto-sync from HOWARD-HOME at 2026-06-17 13:26:13
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-17 13:26:13
2026-06-17 13:26:21 -07:00
af72948944 wiki: compile cascades-tucson (full) — catalog reporting SaaS + proposed KPI dashboard (Ashley Jensen) 2026-06-17 13:01:41 -07:00
289bf9f675 sync: auto-sync from HOWARD-HOME at 2026-06-17 12:34:44
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-17 12:34:44
2026-06-17 12:35:36 -07:00
Winter Williams
deedf86671 sync: auto-sync from GURU-BEAST-ROG at 2026-06-17 12:18:02
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-06-17 12:18:02
2026-06-17 12:18:09 -07:00
1fbc196e28 agy(gemini): fix false auth-abort in retry loop + add quota fallback to default model
While using the new 3-retry gemini path for live VPN research, two bugs surfaced:
- emit_or_fail checked auth_failed INSIDE the retry loop; a benign mid-run token-refresh line
  matched the over-broad auth regex (bare login|credential|authenticat|oauth|401) and aborted the
  retries with a false "auth error" - even though `gemini -p` auth tested fine. Moved auth-classify
  to AFTER the retries (it only picks the final error message now) and tightened auth_failed to real
  signatures (invalid_grant, not authenticated, login with google, token expired, ...).
- Added quota_exhausted() + a QUOTA FALLBACK: the pinned strong model (gemini-3.1-pro-preview) hit
  "exhausted your capacity on this model" mid-session; emit_or_fail now retries once on the default
  (lighter) model by stripping -m (separate quota). Validated: capped pro run -> fell back -> 2.9KB answer.

CT_THOUGHTS Thought 2 Resolution updated with both. (Search-bot reliability hardening continues.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 12:09:58 -07:00
Winter Williams
ec3dcbb2de sync: auto-sync from GURU-BEAST-ROG at 2026-06-17 11:53:00
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-06-17 11:53:00
2026-06-17 11:53:14 -07:00
837562baa3 sync: auto-sync from GURU-5070 at 2026-06-17 11:44:54
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-17 11:44:54
2026-06-17 11:45:09 -07:00
9b5531793b errorlog: search-bot fallback entries from this session's e2e validation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 10:41:22 -07:00
972bade0d7 search-bots: fix reliability (diagnosed) - gemini 3-retry + grok xsearch auto-fallback to gemini
Mike's must-fix. Diagnosed from RAW output of failing queries (not guessed):
- grok xsearch = TIMEOUT: grok-4.20-multi-agent web_search runs past budget on multi-part queries
  (286s/280s, rc=124, still searching - 183 thoughts, only progress-noise text); buffered json => total loss.
- gemini search = INTERMITTENT empty turn (a clean re-run gave a real 2.6KB answer in 122s); the wrapper
  retried only once, so two empties in a row failed spuriously.

Fixes:
- ask-gemini.sh emit_or_fail: retry up to 3x with 3s/6s backoff (was 1).
- ask-grok.sh xsearch: --output-format streaming-json (salvage partials) + AUTO-FALLBACK to
  ask-gemini.sh search when grok doesn't finish (rc!=0 or empty). Validated e2e: grok timed out
  (rc=124) -> fell back -> gemini returned a real sourced answer (UniFi Teleport invite-link API).

grok's own multi-agent timeout is an xAI-side limitation; the fallback makes xsearch reliable regardless.
Docs: grok SKILL.md xsearch row + CT_THOUGHTS Thought 2 Resolution.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 10:38:44 -07:00
0207b7c4cf sync: auto-sync from HOWARD-HOME at 2026-06-17 10:16:13
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-17 10:16:13
2026-06-17 10:16:21 -07:00
49eada483e sync: auto-sync from HOWARD-HOME at 2026-06-17 10:10:48
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-17 10:10:48
2026-06-17 10:10:59 -07:00
9971b49613 wiki: compile lens-auto-brokerage (seed)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 09:57:12 -07:00
0c7c4197dc sync: auto-sync from HOWARD-HOME at 2026-06-17 09:52:59
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-17 09:52:59
2026-06-17 09:53:09 -07:00
ca3589f258 ct-thoughts: web-search bots reliability = MUST FIX (Mike) + research-method correction
Mike's correction: web search (grok xsearch + gemini search) carries at least as much weight as
live API probing - the searches gave the real leads this session (connector proxy, teleport setting
path); blind endpoint-probing is "highly suspect" (mostly 404s). And the search bots MUST be properly
fixed - both returned empty repeatedly on UniFi research despite the same-day partial grok fix.

- docs/CT_THOUGHTS.md: Thought 2 (HIGH PRIORITY) - web-search reliability must-fix, with the observed
  failures + a proper-fix investigation plan (capture failing-query JSON; max-turns/streaming-json/
  retry; cross-fallback grok<->gemini; 5/5 acceptance).
- memory feedback_web_search_over_probing: lead with web search/docs; probe only to CONFIRM a
  hypothesis, never as primary discovery. Reading our own config is fine; guessing paths is not.
- errorlog correction logged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 09:36:36 -07:00
2941b104b0 sync: auto-sync from HOWARD-HOME at 2026-06-17 09:35:47
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-17 09:35:47
2026-06-17 09:35:58 -07:00
9b19c6e34c unifi-wifi: correct the Teleport finding - config API IS reachable via the connector
Earlier "no usable Teleport API" was wrong (probed /rest/teleport, /stat/teleport, /v1/teleport).
Gemini research + live verification: Teleport config lives at /api/s/<site>/rest/setting/teleport
(GET/PUT, also under /get/setting key 'teleport') - reachable via the connector. Brooklyn confirmed
enabled, subnet 192.168.1.1/24. Invite generate/revoke is reportedly POST /api/s/<site>/cmd/teleport
{"cmd":"generate-invite"|"revoke-invite"} (untested - it creates a live VPN access link; gate as a
write). Invites are WiFiman-app-only. Proxy path is /v1/connector/consoles/{id}/proxy/... (Gemini's
/v1/hosts/{id}/proxy form 404s). Doc updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 09:22:05 -07:00
7e435e3128 unifi-wifi: neighbor-collect connector-capable (remote disables) + document VPN/Teleport reach
neighbor-collect.sh: add `--console <name> [--site <short>]` so the AP name/BSSID/IP map can come
from the cloud connector (/v1/connector/.../stat/device) instead of a UOS direct-login -- lets the
disable-analysis collector run against ANY console we have AP-VLAN reach to (the AP SSH harvest of
/proc/ui_neighbor is unchanged and still needs L3 reach). UOS path untouched. Validated against
Cascades via connector: source=CONNECTOR, built 77-mac + 450-bssid map for the 75 online APs.

This completes the hybrid (don't-lose-functionality): connector for airtime everywhere + neighbor-
collect (any source) for the SNR matrix -> NEIGHBOR_JSON -> optimize-radios disables on remote sites.

Documented (references/site-manager-api.md): the neighbor-collect --console flow, and the gateway
VPN/Teleport reach -- connector reaches /rest/networkconf (VPN servers: wireguard-server/openvpn-
server, site-to-site) read+writable in principle (gate writes like gw-control); Teleport has no
usable API (v1/ea/teleport 404, per-console /teleport 403).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 09:05:50 -07:00
47b31dcd3c unifi-wifi: validate connector RF analysis vs UOS (Cascades) - macs[] fix + --site passthrough
Validated the cloud-connector analysis against a KNOWN entity (Cascades, normally UOS-Mongo).
The connector reaches the self-hosted "UOS Server" host; Cascades is its site `va6iba3v`.

Two fixes from the validation:
- rf-analyze.py: pass macs:[<all uap macs>] to /stat/report/*.ap. The UniFi report endpoint
  returns only a small DEFAULT subset otherwise -- Cascades came back as 10 of 77 APs until the
  MAC list was supplied. Now profiles all 75 (uaps with 2.4 radios), matching the UOS path.
- model-rank.sh / optimize-radios.sh: --console now accepts --site <name> (internal short name
  from /api/self/sites) for multi-site controllers like the UOS Server (Cascades = va6iba3v).

Result lines up with the known UOS-Mongo figures: 75 APs, 2.4GHz util 65-90% / interf 53-78% /
~1 client each, all power-down, 0 disables (roam graph absent via connector -> same coverage-safe
degradation; disables still need NEIGHBOR_JSON). Apples-to-apples confirmed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 08:53:36 -07:00
f987812fe5 unifi-wifi: model-rank + optimize-radios run on cloud-connector data (non-UOS consoles)
Both analyses now accept `--console "<name>"` and run against the UniFi cloud connector
instead of the UOS Mongo server, so RF airtime tuning works on standalone/non-UOS consoles
(e.g. Brooklyn/Skybar). The UOS Mongo path is unchanged.

- New shared analyzer scripts/rf-analyze.py: pulls per-AP/band airtime history via the
  connector POST /stat/report/hourly.ap (SAME schema as ace_stat.stat_hourly) + /stat/device
  for names/zones, derives cu_interf = cu_total - cu_self_rx - cu_self_tx, and runs the SAME
  model-rank ranking and optimize-radios greedy power-down/disable logic (ported faithfully).
- Roam graph (/stat/event) is usually empty on small/stationary sites -> graceful degrade:
  model-rank ranks by airtime pressure; optimize-radios returns power-down candidates + 0
  disables (coverage-safe). NEIGHBOR_JSON (SNR matrix) still enables disables, as on UOS.
- model-rank.sh / optimize-radios.sh: added the `--console` route (resolves the key from
  vault services/unifi-site-manager, execs rf-analyze.py). Validated on Brooklyn/Skybar:
  2.4GHz saturated (Yoga AP cu 63%/interf 55%), 5GHz idle (1-5%) - the expected pain-band split.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 08:43:09 -07:00
6fdc21d955 unifi-wifi: cloud Site Manager backend (gw-sitemanager.sh) + UOS-parity connector tier
New backend reaching ANY of the ~36 ACG UniFi consoles remotely via api.ui.com with the
account key (vault services/unifi-site-manager) - no UOS server, no LAN/VPN. Mapped the API
surface empirically (key live), corroborated by grok+gemini web search:

- Tier 1 (Site Manager): fleet/devices/sites/isp commands - inventory, site health (counts,
  IPS, ISP/ASN), and WAN/ISP time-series (latency/throughput/downtime).
- Tier 2 (CLOUD CONNECTOR -> console LOCAL Network API = UOS PARITY): the `net` command proxies
  /v1/connector/consoles/<id>/proxy/network/api/s/<site>/stat/{device,sta}, returning the SAME
  ace_stat depth as the UOS Mongo path - per-radio cu_total airtime/channel/bw/tx_power/num_sta/
  satisfaction and per-client rssi/signal/noise/satisfaction/rates. Verified live on Brooklyn/
  Skybar (standalone UDM, WAN-firewalled): `net brooklyn radios` + `net brooklyn clients` work.

This achieves parity with (and broader coverage than) the UOS server for non-UOS consoles.
Added references/site-manager-api.md (full catalog + 3 tiers), a Plane 3 note in SKILL.md, and
updated the reference memory. Read-only; POST actions (device restart, client block) exist, not wired.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 08:32:00 -07:00
29b33c686a acg-website: Phase 3B - radio show promotion, funnel, enhanced CTAs, layout density
Radio Show Promotion:
- Add LIVE badge to header phone with subtle pulse animation
- Add radio promo bar under hero (show name, time, call-in number)
- Radio ticker at bottom remains from Phase 3A

3-Step Visual Funnel:
- New funnel-steps section after Trust
- Progression: 1. Build estimate → 2. Talk it through → 3. Month-to-month start
- Interactive number badges with hover lift + color fill
- Mobile responsive (stacks vertically on small screens)

Strengthen Calculator CTAs:
- Pricing teaser: Make 'Build your exact price' primary button (was more-link)
- All 6 service cards: Add inline 'See what this costs →' calculator links
- Guides prospect directly to calculator from any service mention

Increase Vertical Rhythm:
- Section padding: clamp(2.75rem, 5.5vw, 4.25rem) for breathing room
- Service list: +1.5x base margin-bottom after dense grid
- Dispatch grid: +1.5x base margin-bottom after blog cards

All changes deployed to ww9.azcomputerguru.com and verified live.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-06-17 07:15:41 -07:00
4b631f681c acg-website: Phase 3A enhancements - premium polish + dark mode fix
- Add timing system: --t-fast/med/slow + cubic-bezier easing vars
- Enhanced button/card hover: subtle lift + box-shadow + filter
- Nav link underline wipe effect with accent color
- Form input focus: accent border + 25% opacity outline
- Micro-interactions: stepper scale, switch snap, FAQ rotation
- Reveal animations: opacity + translateY with 55ms stagger
- Radio promo bar CSS + pauseable ticker on hover
- Dispatch board: 1px grid + left accent rule + tighter cards
- CRITICAL FIX: orphaned CSS selector causing dark mode white boxes

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-06-17 07:04:11 -07:00
311a45abee acg-website-showcase: curate brand kit to forward-relevant assets
Per Mike: don't keep the full archive tracked. Trimmed the brand kit from 34M -> 14M,
keeping only forward-relevant assets: the StyleGuide PDF, current logos
(transwhite/flatwhite) + vector master (guru-vector.eps, moved out of Old/), Guru
Icons, the Lato family (+ OFL + README), Colors reference, social avatar, and the
2025 letterhead (png + docx). Removed legacy Old/ marks, business cards, raster
flat-icon sheets, the superseded 2021 letterhead, and generic stock images.
(Original archive remains recoverable from git history.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 06:19:01 -07:00
8e2d097761 sync: auto-sync from Mikes-MacBook-Air.local at 2026-06-17 06:18:23
Author: Mike Swanson
Machine: Mikes-MacBook-Air.local
Timestamp: 2026-06-17 06:18:23
2026-06-17 06:18:26 -07:00
41a30178e7 acg-website-showcase: add ACG master brand kit
Imported the canonical "Branding Stuff" archive (2026-06-15) into the website
project at projects/acg-website-showcase/brand-kit/: logos (transwhite/flatwhite/
vector EPS), full Lato font family (+ SIL OFL license), Guru Icons (16-512px),
brand Colors reference, social avatar, letterhead (2021/2025), business cards,
flat icon sheets, and stock images. 64 files. Added a README documenting contents
+ licensing (Lato = OFL; verify stock-image licenses before public reuse).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 06:15:01 -07:00
f6081a027b sync: auto-sync from HOWARD-HOME at 2026-06-17 00:09:45
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-17 00:09:45
2026-06-17 00:09:53 -07:00
063bc54bce sync: auto-sync from HOWARD-HOME at 2026-06-16 23:54:06
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-16 23:54:06
2026-06-16 23:54:14 -07:00
1c6b88a285 sync: auto-sync from HOWARD-HOME at 2026-06-16 23:33:45
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-16 23:33:45
2026-06-16 23:33:54 -07:00
5ad25d1b4c sync: auto-sync from HOWARD-HOME at 2026-06-16 21:34:19
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-16 21:34:19
2026-06-16 21:34:40 -07:00
16a8dfffd1 unifi-wifi: fix apply-wlan wlan_bands 6e->6g; add 5GHz + 6GHz phases to Cascades runbook
- apply-wlan.sh: wlan_bands token was "6e" but this controller stores "6g" (verified live on Cascades
  Guest SSID) -> setting 6 GHz membership would have failed. Fixed band values + option names (5g6g/6g/all).
- Cascades 2.4 runbook: folded in Phase 5 (5 GHz: width 80->40 on 76 radios; channel plan with the
  DFS decision flagged -- DFS empirically clean here, so including clean-DFS gives ~20 channels vs ~5
  non-DFS-only for 77 APs) and Phase 6 (6 GHz: root cause = production SSID CSCNet not on 6 GHz [bands
  2g,5g only]; add 6g + enable bss-transition; band-steering already on). Per Howard.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 21:34:40 -07:00
e6a0efa57c cascades 2.4 runbook: Floor 1 per-AP (skip 128/108) instead of zone — no pointless 128 re-enable 2026-06-16 21:34:39 -07:00
bcebae439a cascades 2.4 runbook: exclude Floors 5 & 6 per Howard; Floor 4 power-down done 2026-06-16 21:34:39 -07:00
7f80febecd acg-website: update skin switcher labels (remove Paper references)
Updated aria-label and title attributes on skin toggle button to reflect
current 3-skin configuration: Bold / Midnight / Verdigris

Deployed to ww9.azcomputerguru.com

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-06-16 20:34:41 -07:00
45f6676468 acg-website: remove ledger background ruling pattern
Removed repeating-linear-gradient background-image that creates horizontal
ledger lines. Bold design has no ledger rulings - clean backgrounds only.

Deployed to ww9.azcomputerguru.com

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-06-16 20:33:43 -07:00
7515af3309 acg-website: remove Paper/Ledger skin, set Bold as default
- CSS: Bold tokens now at :root (bone/near-black palette, signal orange)
- JS: skin switcher cycles Bold/Midnight/Verdigris (default: bold)
- HTML: all 7 pages default to bold skin
- Docs: README + DESIGN.md rewritten for Bold design language
- Deployment: live at ww9.azcomputerguru.com (IX hosting, grey-cloud DNS)

Paper/Sonoran Ledger skin dropped per user direction. Single-page ledger
version in root retained as archived reference pattern.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-06-16 20:30:10 -07:00
a073f66b92 skills(brainstorming): make request-only, not auto-trigger
Upstream description ("You MUST use this before any creative work...") would
auto-fire the brainstorming skill on routine feature/code work. Rewrote the
frontmatter description to invoke ONLY when the user explicitly asks to
brainstorm/design. Methodology body (incl. HARD-GATE) unchanged. Noted in SOURCE.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 20:22:59 -07:00
6122efe98c skills: harvest 4 MIT dev skills from obra/superpowers (awesome-claude-skills)
From the ComposioHQ/awesome-claude-skills list. Checked licenses BEFORE copying:
- threat-hunting-with-sigma-rules: repo is gone (GitHub 404) -- not harvested.
- forensics (mhattingpete): repo restructured, those skills no longer exist -- not harvested.
- pdf / mcp-builder (Anthropic official): LICENSE.txt FORBIDS copying out of the
  Service / derivatives / redistribution -- NOT harvestable into this repo (install via
  the official Claude Code marketplace instead if wanted).
- obra/superpowers: MIT -> the only legally harvestable set; imported with attribution.

Imported (each with its own MIT LICENSE copy + SOURCE.md provenance, commit a21956e48c13,
ASCII-normalized to house style, no emojis):
- using-git-worktrees
- test-driven-development (+ testing-anti-patterns.md)
- root-cause-tracing (+ find-polluter.sh helper, emojis -> ASCII markers)
- brainstorming (methodology only; upstream visual websocket server intentionally omitted)

Faithful imports -- content not reworded beyond ASCII typography/emoji normalization.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 20:22:59 -07:00
7590155499 ollama: fix broken endpoint auto-detect in OLLAMA.md one-liner (RTFM audit)
Audited the Ollama reference (no wrapper script — it's the OLLAMA.md doc + inline
HTTP-API call pattern) against the live server (Ollama 0.30.8 on GURU-5070):
- /api/chat + think:false + res['message']['content'] confirmed working (clean
  output, no thinking leak) -- the core call pattern is correct.
- All referenced models exist on the server (qwen3:8b, qwen3.6:latest, qwen3:14b,
  codestral:22b, nomic-embed-text).

Real bug found + fixed: the "Preferred one-liner" auto-detected the endpoint with
`urlopen(...)` used as a truthiness test. urlopen RAISES URLError on a down host
(proven), so the ternary's fallback branch was dead code -- it crashed on a down
localhost instead of failing over to Beast, and it did a per-call probe that
contradicts the doc's own "read endpoint from identity.json, no probe" rule 30 lines
above. Replaced with the identity.json endpoint+model pattern (also swaps the
hardcoded qwen3:14b for the per-machine prose_model). Validated verbatim end-to-end.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 20:22:59 -07:00
69638887ab wiki: compile cascades-tucson (full) — WiFi RF + network/pfSense + SSH backend, 55.75h 2026-06-16 20:08:02 -07:00
1b8ab26e87 sync: auto-sync from HOWARD-HOME at 2026-06-16 19:40:32
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-16 19:40:32
2026-06-16 19:40:40 -07:00
18a4884ee9 errorlog: wiki-compile --full subagent friction (32k output crash on large article) 2026-06-16 19:30:49 -07:00
bc0ceea26c wiki: compile cascades-tucson (full) - voice VLAN plan, CSCNet PPSK correction, CS-SERVER RAID/backup 2026-06-16 19:29:50 -07:00
2b123679f9 sync: auto-sync from HOWARD-HOME at 2026-06-16 19:29:08
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-16 19:29:08
2026-06-16 19:29:21 -07:00
e09ff5dc9b agy(gemini): RTFM audit — confirmed healthy, version + verified-date refresh
Audited the Gemini wrapper against the CLI's bundled help/README (gemini 0.45.2),
same pass as the grok skill. Unlike grok, found NO functional bug:
- All flags correct and real: -p, --skip-trust, -o json, --approval-mode plan|yolo,
  --include-directories, -m (verified against `gemini --help`).
- JSON schema {session_id, response, stats} -> .response confirmed via live probe.
- Pinned model gemini-3.1-pro-preview STILL VALID (live PONG); the GA-looking
  gemini-3.1-pro and gemini-3-pro both ModelNotFoundError -> keep the -preview suffix.
- Default text model is gemini-3.1-flash-lite (by design; verify/review/search/image
  pin pro). No thought-suppression flag exists in the CLI, so the gresponse() reasoning
  -leak scrub stays (justified, signature-gated, byte-exact otherwise).
- Live `search` re-validated end-to-end through the wrapper (58s, grounded sources).

Only change: version 0.45.1 -> 0.45.2 in SKILL.md + wrapper header, and refreshed the
verified-date notes with the 2026-06-17 re-validation findings.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 19:25:07 -07:00
a242797908 grok: fix xsearch (multi-agent web_search), pin grok-build, RTFM doc sweep
Root-caused the long-standing `ask-grok.sh xsearch` "no result (stopReason=)"
failure by reading Grok's bundled docs (~/.grok/docs/user-guide + README) instead
of probing:
- web_search runs a SEPARATE multi-agent model (grok-4.20-multi-agent), so the
  wrapper's blanket --no-subagents strangled it -> indefinite hang, 0 bytes. Scoped
  --no-subagents OFF xsearch; use --yolo (documented headless tool-run posture).
- xsearch prompt mandated X/Twitter search on every call (slow multi-agent) and the
  budget was 240s -> still timed out. Now web-primary (X only when relevant), 300s.
  Validated end-to-end through the wrapper: 23s, correct answer + 3 sources.

Model: pin -m grok-build (xAI flagship, 512k, the documented default) for the
reasoning modes (text/verify/review*) so quality is deterministic and not at the
mercy of the runtime default (this machine drifted to grok-composer-2.5-fast, a fast
Cursor coding model). xsearch + image/video keep the runtime default. Validated text
mode on grok-build (13s).

Doc accuracy (SKILL.md): corrected the model facts (default, the separate web_search
model, --effort unsupported on grok-build per supports_reasoning_effort:false);
documented the xsearch subagent exception. Fixed a stale in-script comment claiming
--rules/--disallowed-tools "tripped the CLI" (both are valid headless flags).

memory: add feedback_interview_ai_read_docs (read bundled docs / interview the model
before probing) + index; errorlog correction.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 19:25:07 -07:00
58ecc5ad40 unifi-wifi: pfSense gateway access via SSH (pfSense-ssh.sh) + pfSense health section; layer OFF HOLD
DECISION (Mike, 2026-06-16): drop the RESTAPI package — VPN + SSH shell reads the same data and makes
changes. Confirmed Cascades pfSense is Plus 25.07-RELEASE (current; the "too old" premise was wrong) and
admin SSH = real shell (no menu). The upgrade/package blocker is moot; compat layer is off hold.

- NEW scripts/pfsense-ssh.sh: audit (version/WAN-media/gateway-events/DHCP-exhaustion/states/DNS/load/NIC),
  dhcp (pool utilization + no-free-leases), run "<cmd>" (arbitrary, incl changes; operator-gated). Cred
  from clients/<slug>/pfsense-firewall; system OpenSSH via askpass. Validated live on Cascades.
- audit report: added "pfSense health check (2026-06-16)" — DHCP NOT exhausted (192.168.0.0/22 pool 270/507,
  0 no-free-leases), DNS up, dual-WAN stable (no gateway flaps), states/load healthy => gateway is NOT a
  WiFi factor; the 2.4 GHz RF work is the sole fix. (Minor: igc3/WAN2 I225 2.5G counter quirk, not a fault.)
- ROADMAP §E + SKILL.md updated to the SSH backend decision; REST pfsense-backend.sh kept dormant/optional.
- Remaining: named gated CONTROL verbs over SSH (easyrule block-ips, pf/fw toggles) + optional gw-* dispatch.
- Closed obsolete coord todo (upgrade-pfSense-for-RESTAPI).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 18:42:54 -07:00
e42ad8f163 sync: auto-sync from HOWARD-HOME at 2026-06-16 18:23:40
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-16 18:23:40
2026-06-16 18:23:49 -07:00
32dd949d7f sync: auto-sync from GURU-5070 at 2026-06-16 18:13:39
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-16 18:13:39
2026-06-16 18:13:57 -07:00
718bce3525 sync: auto-sync from HOWARD-HOME at 2026-06-16 18:10:13
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-16 18:10:13
2026-06-16 18:10:43 -07:00
54c7f9940d harness: PS2 guard for onboarding probe + Windows quote-stripping memory
onboarding-diagnostic.ps1: add a PowerShell-version guard. The probe is PS3+ by
design (Get-CimInstance, [ordered], ConvertTo-Json); on stock PS2 (Win7 SP1 /
2008 R2 without WMF) it crashed with cryptic [ordered] errors and emitted empty
DIAG-JSON (first hit: AMT-PC). Now on PS<3 it emits a legible, parseable result
inside the DIAG-JSON markers (hand-built JSON) with a WMF 5.1 / KB3191566
remediation hint instead. Parses clean. True PS2-native probe stays an RMM Thought.

memory: add feedback_windows_quote_stripping (+ index) consolidating the two
recent embedded-double-quote incidents (PowerShell->curl.exe CommandLineToArgvW,
RMM->cmd.exe shutdown /c) into one root cause + fix, so future ref= entries land.

errorlog: the two self-logged entries from #32333 (preview-skip friction,
AMT-PC/Scileppi conflation correction).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 18:10:11 -07:00
08fcafa0a4 sync: auto-sync from HOWARD-HOME at 2026-06-16 18:09:18
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-16 18:09:18
2026-06-16 18:09:27 -07:00
d6cbfb3e50 sync: auto-sync from HOWARD-HOME at 2026-06-16 17:03:02
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-16 17:03:02
2026-06-16 17:03:10 -07:00
8c048c3d51 cascades: 2.4 GHz remediation runbook for tonight (mesh-safe power-down + per-floor disable, gated)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 16:46:17 -07:00
3374483cd6 unifi-wifi: coverage-thin mesh-awareness — never disable wireless-mesh APs or their parents
Howard caught a real hazard: coverage-thin was mesh-blind. At Cascades, 2nd Floor Atrium is the
wireless-mesh PARENT for CC Bridge + salon (backhaul ch36/5GHz), and 206 U7 Pro carries 108. The tool
had listed 2nd Floor Atrium / CC Bridge / 206 as 2.4 disable targets. Although the backhaul is 5GHz
(so a 2.4-radio disable wouldn't drop it), touching infra APs that feed others is needless risk.

Fix: fetch live uplink topology (stat/device); build the mesh set = wireless-uplink APs UNION their
parents; exclude them from disable (kept as coverers if their 2.4 is on); print MESH-PROTECTED line.
Falls back with a clear WARNING if no controller cred. Cascades now auto-excludes 108/206/2nd Atrium/
CC Bridge/salon; resilient plan 34->33. Also verified SSIDs are not AP-pinned (broadcasting_aps off),
so no client is orphaned by a radio disable.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 16:43:08 -07:00
6f77222bcb unifi-wifi: coverage-thin apply hint -> per --ap (was --zone, which would disable a whole floor)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 16:28:35 -07:00
f936224de8 unifi-wifi: add coverage-thin.sh — 2.4 coverage-redundancy disable planner (active-2.4 aware)
Answers "which 2.4 radios can we turn OFF given over-coverage, based on AP proximity." Greedy
dominating-set on the AP-to-AP 2.4 SNR layer: disables radios whose area stays covered by a nearby
ACTIVE-2.4 neighbor, maximizing interference-airtime removed without opening a 2.4 hole. Caps per-zone,
guards coverer capacity, flags single-coverer (low-resilience) disables, reports co-channel before/after.

Why separate from optimize-radios: optimize uses band-AGNOSTIC physical adjacency, so it counts an AP
whose ng radio is DISABLED as a "coverer" via its 5/6 GHz (observed: it proposed disabling 127/229/330/428
"covered by 128" — but 128's 2.4 is already disabled => those would be 2.4 holes). coverage-thin uses the
2.4 SNR layer specifically and only counts neighbors whose 2.4 stays ON.

Cascades (live): aggressive MINCOV=1 -> disable 36/76; resilient MINCOV=2 -> disable 34/76 with >=2 active
2.4 coverers each; co-channel ch6 28->13, ch11 25->13, ch1 20->13; ~2400 interference-airtime pts removed.
Read-only; needs NEIGHBOR_JSON. SKILL.md step 3b.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 16:17:54 -07:00
80384a79f4 unifi-wifi: radio-usage.sh --ap mode — per-device 2.4 history + steerable-vs-legacy tagging
Adds `radio-usage.sh <site> <band> --ap "<AP name>"`: lists the devices on one AP's band by merging
live clients (stat/sta) with recent association events (wifi_connectivity_event, band-aware), enriched
from ace.user identity. Tags each device steerable vs legacy:
  - from events: DUAL (also seen on 5/6 GHz -> steerable) vs NG-ONLY (2.4-only -> legacy/IoT)
  - fallback when no event in the (short ~1d) retention window: randomized MAC = modern phone/laptop
    (likely 5G/steerable) vs fixed vendor OUI = likely IoT/legacy.
Decision value: steerable -> fix via band-steering/min-RSSI; a legacy/IoT device present argues AGAINST
disabling that 2.4 radio. Needs controller cred for the live BSSID (vap_table) map; honest about the
short event retention. Validated live on Cascades (347, Dining Room).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 15:51:13 -07:00
659c3a2bda unifi-wifi: add radio-usage.sh — per-AP band client-usage history (disable-safe vs power-down)
Answers "is this 2.4 radio actually used?" from accumulated controller stats (ace_stat.stat_daily,
~77d). Reports per-AP time-avg concurrent users (<radio>-num_sta_avg) + peak station snapshot
(<band>-num_sta), distinguishing avg~0/peak>0 (takes bursts -> POWER-DOWN) from peak==0 (genuinely
unused -> disable-safe). With NEIGHBOR_JSON it crosses low-use APs against the AP-to-AP SNR matrix to
emit a defensible safe-to-disable shortlist (low-use AP + strong overlapping neighbor with headroom),
noting mutual-coverage conflicts and deferring conflict-free selection to optimize-radios.

Validated live on Cascades: of 76 APs only 1 has peak==0 over 77d (the offline AP 108); every other
2.4 radio takes real client bursts (peaks 5-58) at very low avg (12 APs <0.5 concurrent). I.e. the
usage history independently CONFIRMS the conservative power-down-not-disable call. Read-only (Mongo
plane). Uses var-assignment to avoid the legacy-mongo REPL echo. SKILL.md documents it as step 2b.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 15:38:26 -07:00
e1031ae91a unifi-wifi: skill health pass — fix optimize-radios stray REPL echo + ASCII-clean all output
Full verification of the skill against Cascades (live):
- All 19 scripts syntax-clean.
- Controller-side read-only validated live: sites, audit-site, switch-audit, live-stats, model-rank,
  optimize-radios, monitor-run, gw-audit. Dry-run apply paths validated: apply-radio, apply-wlan,
  client-control, device-control. AP-side mechanism validated: SSH auth + /proc/ui_neighbor read on a
  sample AP; full neighbor-collect (74-AP SNR sweep) -> channel-plan end-to-end produced a 1/6/11 plan.

Fixes:
- optimize-radios.sh: the `for(k in prof)` loop's numeric completion value was REPL-echoed by the legacy
  mongo shell (stray "94.56..." line in output). Terminated the loop body with `void 0` to suppress it.
- ASCII-clean printed output (CLAUDE.md no-non-ASCII): replaced em-dashes / Unicode arrows / § that
  reached stdout and rendered as `?`/mojibake on the Windows console, across optimize-radios,
  neighbor-collect, survey-collect, dfs-check, audit-site, sites, monitor-run, apply-radio, apply-wlan,
  pfsense-backend. (Comment-only non-ASCII left as-is; never printed.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 15:08:35 -07:00
7e2f49020a sync: auto-sync from HOWARD-HOME at 2026-06-16 14:11:33
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-16 14:11:33
2026-06-16 14:11:42 -07:00
e3bb7d3f95 unifi-wifi: pfSense compat layer ON HOLD — Cascades pfSense too old for RESTAPI pkg, needs upgrade first
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 14:11:33 -07:00
1118594cd2 unifi-wifi: pfSense gateway compat layer (§E) — REST backend + dispatch inside gw-audit/gw-control
Per Howard's decision (2026-06-16, "try what Mike wanted"): Mike's §E open decisions resolved as
REST API package backend + dispatch INSIDE the existing gateway verbs (his lean), not sibling scripts.

- NEW scripts/pfsense-backend.sh: pfSense REST API (pfSense-pkg-RESTAPI v2, X-API-Key) driver exposing
  the same verbs as gw-control (audit, pf-list/disable/enable/delete/set-ports, fw-list/disable/enable,
  block-ips) + a `setup` helper. Writes --apply-gated with per-object rollback to .claude/tmp + firewall/apply.
- gw-audit.sh: when num_gw=0 and a clients/<slug>/pfsense-api cred is vaulted (or --pfsense <slug>),
  appends the pfSense WAN/DHCP/firewall audit; else prints the setup hint. (captures num_gw to gate.)
- gw-control.sh: same-verb auto-dispatch to pfsense-backend when a pfSense cred resolves for the site.
- SKILL.md [PROPOSED]->[SCAFFOLDED]; ROADMAP §E open decisions marked resolved.

STATUS: scaffolded. BLOCKED/setup/no-cred paths tested; gw-audit dispatch validated live (Cascades
num_gw=0 -> hint). Live REST calls pending a reachable pfSense with the API pkg + a vaulted key; v2
endpoint paths must be verified against the installed API version on first live run.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 13:51:10 -07:00
708379732e sync: auto-sync from HOWARD-HOME at 2026-06-16 13:43:14
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-16 13:43:14
2026-06-16 13:43:23 -07:00
0956f76cb2 sync: auto-sync from HOWARD-HOME at 2026-06-16 13:30:26
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-16 13:30:26
2026-06-16 13:30:35 -07:00
2f6057518d sync: auto-sync from HOWARD-HOME at 2026-06-16 13:12:16
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-16 13:12:16
2026-06-16 13:12:26 -07:00
34091500ee sync: auto-sync from GURU-5070 at 2026-06-16 09:02:24
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-16 09:02:24
2026-06-16 09:02:39 -07:00
a32dfc33fa syncro: invoice-note policy — block hours remaining, low-block (<4hr) renew + Winter tag, recurring sweep
Extends the invoice Message (note) automation into a single reusable helper
set_invoice_note <invoice_id> <customer_id> [pre_billing_prepay]:
  - no block (prepay_hours==0)  -> "Interested in discounted labor? Ask us about block-rate pricing."
  - block, >=4 hrs left         -> "Block hours remaining: N."
  - block, <4 hrs left          -> remaining + renew line, AND tags Winter (<@624666486362996755>)
                                   in #bot-alerts (low-block heads-up; mentions ping, no allowed_mentions)
Pre-billing prepay arg keeps a just-depleted block counted as a block customer (shows renew, not upsell).
Never clobbers a non-empty note.

Wired into billing Step 3 (set_invoice_note "$INVOICE_ID" "$CUST_ID" "$PREPAY"), and a new
"Recurring invoice note sweep" applies the same policy to Syncro's auto-generated recurring invoices
(schedule_id != null, recent, current balance) — idempotent, run after each recurring run.

Branch logic + a real e2e note set/restore validated on the ACG internal test account (#67741); the
<4hr Winter alert was stubbed in testing so no real ping fired.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 08:56:42 -07:00
864f4d0a33 syncro: document Invoice Message (note field) + auto block-rate hint for non-block customers
The on-screen "Invoice Message" text block IS the invoice `note` field, editable via
PUT /invoices/{id} {"note": "..."} (response {"invoice": {...}}). Verified on the ACG
internal test account (#67741: set/verify/restore).

Billing flow now sets a one-line upsell hint on the invoice note — "Interested in
discounted labor? Ask us about block-rate pricing." — ONLY for customers with no prepaid
block (prepay_hours == 0). Block customers (prepay_hours > 0) get no hint; never clobber
a non-empty note.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 08:48:25 -07:00
313fd0ac3d onboarding-diagnostic: fix two Server-SKU false positives
Both surfaced on GND-SERVER (Server 2019 DC), would mis-grade every Windows Server:

1. OS EOL: build numbers are SHARED between client and server SKUs (17763 = Win10
   1809 AND Server 2019; 14393 = 1607/Server2016; 26100 = 24H2/Server2025). The map
   only had client dates, so Server 2019 (supported to 2029) was flagged EOL-2020 =
   false critical. Now branch on SKU ($caption -match 'Server') with a Server EOL map.

2. Stability disk errors: ids 7/51/153 are shared across providers; provider 'disk'
   = real I/O error, but 'Microsoft-Windows-Kernel-Boot' id 153 = "VBS disabled" boot
   noise. The unfiltered fallback counted that noise as disk errors (false warning on
   healthy boxes). Now count only true storage providers, no unfiltered fallback.

Parses clean. Re-run on GND-SERVER should drop from RED to AMBER (both false findings gone).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 08:18:27 -07:00
a33bc423f6 grabb-durando: GND-SERVER full health/security baseline (RED)
First onboarding-diagnostic baseline for GND-SERVER (Grabb & Durando DC/file/RRAS box,
gd.local, 192.168.242.200). Grade RED: 2 critical (host firewall OFF on all profiles;
OS-EOL flag — false positive, build 17763 is Server 2019, supported to 2029), 6 warning
(Defender/AV unconfirmed, built-in Administrator enabled, 1 pending update, 2 disk errors
/14d, pending reboot, 2 stopped auto services), plus tempadmin local admin + no confirmed
BitLocker. Immutable JSON + report under onboarding-baselines/.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 08:11:35 -07:00
76d006c08b sync: auto-sync from HOWARD-HOME at 2026-06-16 07:49:26
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-16 07:49:26
2026-06-16 07:49:34 -07:00
5f347dcf79 memory: AAD Connect AdminSDHolder writeback-permission pattern
Reference memory + index entry: diagnosing/fixing AAD Connect "completed-export-errors"
(8344 INSUFF_ACCESS_RIGHTS) where AdminSDHolder strips the connector account's write
permission on a protected admin object. Covers msDS-KeyCredentialLink (Russo) and
msExchSafeSendersHash (Glaztech); csexport /f:x diagnosis + dsacls AdminSDHolder grant.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 07:45:32 -07:00
d4d526ae26 sync: auto-sync from HOWARD-HOME at 2026-06-16 07:44:03
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-16 07:44:03
2026-06-16 07:44:15 -07:00
69987190fc unifi-wifi: roadmap — pfSense gateway compatibility layer (§ E)
Capture the "UniFi APs/switches behind a pfSense gateway" topology (Cascades, our
office, several clients) as a first-class roadmap item: make the gateway verbs
(gw-audit / gw-control / VPN) work against pfSense via a thin driver behind the
same verbs (gw-audit already detects num_gw=0 = third-party firewall).

Includes the verb->pfSense mapping (NAT port-forwards, filter rules,
easyrule block-ips, native OpenVPN/IPsec/WireGuard), ranked backend options
(REST-API pkg vs stock SSH easyrule/pfSsh.php vs diag_command.php vs config.xml),
existing vaulted pfSense creds (Cascades + office), and open decisions. SKILL.md
status block notes the proposed layer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 07:39:53 -07:00
eb87710b9a unifi-wifi: add gw-control.sh — gateway router actions (port-forward + WAN firewall)
The write companion to gw-audit. Closes/scopes internet-facing port-forwards and
toggles WAN firewall rules at the USG/UXG/UDM via the RW controller REST admin.

Actions: pf-list / pf-disable / pf-enable / pf-delete / pf-set-ports / pf-set-src,
fw-list / fw-disable / fw-enable, block-ips (WAN address-group + WAN_IN drop rule).
Reads via Mongo (no cred); writes via login->CSRF->REST (rest/portforward,
rest/firewallrule, rest/firewallgroup). DRY-RUN default, --apply gated on
infrastructure/uos-server-network-api-rw, rollback saved to .claude/tmp.

Dry-run validated on Grabb & Durando (USG-3P): identifies the live "VPN" forward
(80,443,1723 -> 192.168.242.200) + the "GRE" WAN_IN accept that back an
internet-exposed, brute-forced PPTP. Closes the ROADMAP firewall/port-forward item.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 07:35:55 -07:00
48592bd16b sync: auto-sync from HOWARD-HOME at 2026-06-16 07:26:57
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-16 07:26:57
2026-06-16 07:27:06 -07:00
03b429d10a sync: auto-sync from HOWARD-HOME at 2026-06-16 01:20:51
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-16 01:20:51
2026-06-16 01:21:00 -07:00
4e797dbf61 sync: auto-sync from HOWARD-HOME at 2026-06-16 01:13:51
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-16 01:13:51
2026-06-16 01:13:59 -07:00
6557cdb5bb sync: auto-sync from HOWARD-HOME at 2026-06-16 01:09:44
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-16 01:09:44
2026-06-16 01:09:53 -07:00
af2feb1dfb sync: auto-sync from HOWARD-HOME at 2026-06-16 00:58:45
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-16 00:58:45
2026-06-16 00:58:54 -07:00
60b763df0c sync: auto-sync from HOWARD-HOME at 2026-06-16 00:40:03
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-16 00:40:03
2026-06-16 00:40:12 -07:00
ae3d760c37 sync: auto-sync from HOWARD-HOME at 2026-06-16 00:24:18
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-16 00:24:18
2026-06-16 00:24:27 -07:00
35a0cca2c6 sync: auto-sync from HOWARD-HOME at 2026-06-16 00:03:10
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-16 00:03:10
2026-06-16 00:03:21 -07:00
a62bd65be7 sync: auto-sync from HOWARD-HOME at 2026-06-15 23:43:51
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-15 23:43:51
2026-06-15 23:44:00 -07:00
e5f07afd90 sync: auto-sync from HOWARD-HOME at 2026-06-15 23:34:02
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-15 23:34:02
2026-06-15 23:34:11 -07:00
60d0a2bf87 sync: auto-sync from HOWARD-HOME at 2026-06-15 23:24:56
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-15 23:24:56
2026-06-15 23:25:05 -07:00
d9c7f3cd7d sync: auto-sync from HOWARD-HOME at 2026-06-15 23:04:23
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-15 23:04:23
2026-06-15 23:04:31 -07:00
cb421c44fb sync: auto-sync from HOWARD-HOME at 2026-06-15 22:06:13
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-15 22:06:13
2026-06-15 22:06:20 -07:00
bf74f967eb sync: auto-sync from HOWARD-HOME at 2026-06-15 21:59:42
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-15 21:59:42
2026-06-15 21:59:51 -07:00
6532476e7f wiki: compile cascades-tucson (full) — integrate UniFi RF audit (77 APs, 2.4GHz pain band, DFS resilience, 6GHz untapped); Syncro 55.75h/0 open 2026-06-15 21:03:03 -07:00
b83057f6e2 sync: auto-sync from HOWARD-HOME at 2026-06-15 20:56:36
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-15 20:56:36
2026-06-15 20:56:45 -07:00
3a7b252caf sync: auto-sync from GURU-5070 at 2026-06-15 20:49:22
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-15 20:49:22
2026-06-15 20:49:36 -07:00
c99615df7e sync: auto-sync from HOWARD-HOME at 2026-06-15 20:40:48
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-15 20:40:48
2026-06-15 20:40:59 -07:00
f341ee9398 sync: auto-sync from Mikes-MacBook-Air.local at 2026-06-15 20:33:42
Author: Mike Swanson
Machine: Mikes-MacBook-Air.local
Timestamp: 2026-06-15 20:33:42
2026-06-15 20:33:43 -07:00
6772c96fef sync: auto-sync from GURU-5070 at 2026-06-15 20:03:03
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-15 20:03:03
2026-06-15 20:03:17 -07:00
7eab5ed030 sync: auto-sync from GURU-5070 at 2026-06-15 19:38:20
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-15 19:38:20
2026-06-15 19:38:35 -07:00
710db4c311 sync: auto-sync from HOWARD-HOME at 2026-06-15 19:25:09
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-15 19:25:09
2026-06-15 19:25:17 -07:00
b36b882568 sync: auto-sync from GURU-5070 at 2026-06-15 18:57:26
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-15 18:57:26
2026-06-15 18:57:39 -07:00
94ff0d61ee sync: auto-sync from GURU-5070 at 2026-06-15 18:45:55
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-15 18:45:55
2026-06-15 18:46:11 -07:00
3f01efb6bf sync: auto-sync from GURU-5070 at 2026-06-15 18:32:17
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-15 18:32:17
2026-06-15 18:32:32 -07:00
9a2f806c67 sync: auto-sync from GURU-5070 at 2026-06-15 18:28:49
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-15 18:28:49
2026-06-15 18:29:02 -07:00
ca548687e7 sync: auto-sync from GURU-5070 at 2026-06-15 18:24:52
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-15 18:24:52
2026-06-15 18:25:08 -07:00
00de88fd38 sync: auto-sync from GURU-5070 at 2026-06-15 18:09:05
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-15 18:09:05
2026-06-15 18:09:29 -07:00
f6bd451d52 sync: auto-sync from GURU-5070 at 2026-06-15 18:03:38
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-15 18:03:38
2026-06-15 18:03:55 -07:00
236604924a sync: auto-sync from GURU-5070 at 2026-06-15 17:58:51
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-15 17:58:51
2026-06-15 17:59:06 -07:00
4ef6a9a3b0 sync: auto-sync from GURU-5070 at 2026-06-15 17:49:06
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-15 17:49:06
2026-06-15 17:49:23 -07:00
9b4e86cdfc sync: auto-sync from GURU-5070 at 2026-06-15 14:43:03
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-15 14:43:03
2026-06-15 14:43:19 -07:00
20d8b29a36 sync: auto-sync from GURU-5070 at 2026-06-15 14:35:26
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-15 14:35:26
2026-06-15 14:35:41 -07:00
b6af3fe3a2 sync: auto-sync from GURU-5070 at 2026-06-15 14:01:36
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-15 14:01:36
2026-06-15 14:01:52 -07:00
9581d87589 harness: scratch graduation pipeline (push side + spec) + flarum first test case
- graduation-push.sh: tar+scp scratch -> BEAST graduation-inbox over Tailscale (decoupled
  from /save, soft-fail if BEAST off). Tested: 241 files -> BEAST.
- docs/graduation-pipeline.md: full spec (push -> Ollama triage on BEAST GPU via API ->
  reviewed sanitize+git-mv). Secrets never enter git; ride the encrypted link to BEAST only.
- tmp-promotion-check.sh: rewritten pure-builtin (0.4s) after the per-file grep/fork loop
  hung /save for 4 min on Windows at ~240 scratch files. Deep triage moves to the pipeline.
- forum-post: GRADUATED the canonical flarum poster from scratch ->
  skills/forum-post/scripts/flarum-post.py (s9e markdown->XML + DB insert machinery), with
  the hardcoded IX SSH + Flarum DB passwords swapped to vault lookups. First pipeline test case.
- Vaulted the Flarum DB cred (services/flarum-community.sops.yaml) + sanitized the two
  plaintext copies in forum-post.md.
- errorlog: logged the WSL-stub correction + BEAST-Ollama-CPU(vram=0) finding + the
  promotion-check hang, all via the new log helper.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 12:55:48 -07:00
d9c710be31 sync: auto-sync from GURU-5070 at 2026-06-15 12:15:54
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-15 12:15:54
2026-06-15 12:16:10 -07:00
813d4cfa35 sync: auto-sync from GURU-5070 at 2026-06-15 11:55:44
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-15 11:55:44
2026-06-15 11:55:59 -07:00
55acc7f98a sync: auto-sync from GURU-5070 at 2026-06-15 11:54:35
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-15 11:54:35
2026-06-15 11:54:50 -07:00
9b9513f69e memory: dream cleanup — dedup DM memory, correct Mail.Send fact, fix index dup
- Merge duplicate DM memories into canonical feedback_dm_wrapping_commands_to_mike
  (points at the productized discord-dm skill; keeps UA/Cloudflare-1010 + 50109
  gotchas); git rm the session-created feedback_dm_wrapped_command_lines duplicate.
- feedback_365_remediation_tool: record that Exchange Operator HAS Graph Mail.Send/
  Mail.ReadWrite (corrects an earlier "suite has no Mail.Send") + the EXO-vs-Graph
  token-audience gotcha + Get-MessageTraceV2 + fresh-onboard EXO 401 propagation.
- Remove a duplicate MEMORY.md index line --apply-safe added from a false-orphan.
- Log the memory-dream false-orphan/dup-index defect to errorlog for skill linting.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 11:54:00 -07:00
9960da5f9a harness: fleet-wide functional-error + correction + friction logging
Add .claude/scripts/log-skill-error.sh — the canonical agent error log helper
(writes errorlog.md in DATE | MACHINE | skill | [type] error format, soft-fails).
Three categories: execution failures (default), user corrections (--correction),
and preventable self-inflicted friction (--friction; cite ref= when it repeats a
documented gotcha). Goal: stop paying tokens twice for the same avoidable mistake.

- CLAUDE.md: make logging mandatory for all skills + corrections + friction.
- skill-creator: new skills must wire in the helper (guidance + checklist).
- Retrofit every skill script's genuine failure branches to call the helper
  (b2/bitdefender/mailprotector/packetdial/coord python CLIs; remediation-tool
  + onboard365 bash; vault, rmm-auth, post-bot-alert, agy, grok, 1password,
  run-onboarding-diagnostic). Handled conditions + self-tests left alone.
- errorlog.md: broaden header to cover skills + harness + corrections; seed this
  session's corrections (INKY, Mail.Send token-audience, omnibox-strictness) and
  friction (git-bash /tmp, env-persistence, argv-limit, PowerShell var-case).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 11:40:25 -07:00
927a06a0cf sync: auto-sync from HOWARD-HOME at 2026-06-15 11:38:16
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-15 11:38:16
2026-06-15 11:38:24 -07:00
6e1c65877f sync: auto-sync from GURU-5070 at 2026-06-15 11:20:33
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-15 11:20:33
2026-06-15 11:20:56 -07:00
Winter Williams
5f4a2b1eca sync: auto-sync from GURU-BEAST-ROG at 2026-06-15 10:23:51
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-06-15 10:23:51
2026-06-15 10:23:56 -07:00
Winter Williams
2d1d85f58b sync: auto-sync from GURU-BEAST-ROG at 2026-06-15 10:11:17
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-06-15 10:11:17
2026-06-15 10:11:24 -07:00
dc5c09b40b sync: auto-sync from GURU-5070 at 2026-06-15 09:41:53
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-15 09:41:53
2026-06-15 09:42:17 -07:00
153be4abec sync: auto-sync from GURU-BEAST-ROG at 2026-06-15 06:28:46
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-06-15 06:28:46
2026-06-15 06:28:51 -07:00
b186856d6f sync: auto-sync from GURU-BEAST-ROG at 2026-06-15 06:25:30
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-06-15 06:25:30
2026-06-15 06:25:39 -07:00
57322c66f5 sync: auto-sync from GURU-5070 at 2026-06-15 06:07:00
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-15 06:07:00
2026-06-15 06:07:20 -07:00
c5d4d3527c sync: auto-sync from GURU-5070 at 2026-06-14 20:04:14
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-14 20:04:14
2026-06-14 20:05:02 -07:00
30933bd35d sync: auto-sync from GURU-5070 at 2026-06-14 10:33:33
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-14 10:33:33
2026-06-14 10:33:49 -07:00
9b1c5c391d harness: fix py-vs-python3 doc gap — add py.sh resolver, repoint skill/command docs
The skill/command DOCS instructed Claude to run a bare `py ...`, which is the
Windows py-launcher — absent on Linux/macOS (exit 127, hit on GURU-KALI). A blind
py->python3 swap is wrong too: python3 is a broken MS Store shim on some Windows
boxes where `py` is the correct launcher.

Fix mirrors the resolution the .sh skill scripts already do:
- New .claude/scripts/py.sh: picks the interpreter that actually RUNS —
  identity.json python.command first, then py -> python3 -> python, each
  validated with `-c 'import sys'` so the MS Store stub is skipped. exec's it.
- Repointed all DOC invocations (10 files, ~70 sites) from `py ...` to
  `bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ...` (incl. the `py -c` and
  `py -` heredoc forms in checkpoint.md / mailbox.md).
- Left the .sh skill scripts untouched — they already resolve py/python/python3.
- errorlog.md: marked the GURU-KALI entry RESOLVED.

Depends on CLAUDETOOLS_ROOT (seeded by ensure-settings-env.py); py.sh also
self-resolves the repo root via git/cwd as a fallback.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 09:48:54 -07:00
189de7a030 errorlog: GURU-KALI — coord skill 'py' invocation fails on Linux (use python3)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 09:30:54 -07:00
a6b6b4e80a sync: auto-sync from GURU-BEAST-ROG at 2026-06-14 09:07:13
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-06-14 09:07:13
2026-06-14 09:07:21 -07:00
4fd5688026 sync: auto-sync from Mikes-MacBook-Air.local at 2026-06-14 08:36:19
Author: Mike Swanson
Machine: Mikes-MacBook-Air.local
Timestamp: 2026-06-14 08:36:19
2026-06-14 08:36:21 -07:00
2539ca75ca wiki(valleywide): VWP ESXi cleanup — destroyed 3 decommissioned VMs (~3 TB, 87%->65%), consolidating WINFileSvr snapshots 2026-06-14 08:24:53 -07:00
46b5edfba1 wiki(valleywide): document VWP ESXi host (HP DL360, 192.168.3.24) + 12-VM inventory + storage findings 2026-06-14 07:56:39 -07:00
a34668fc5b valleywide: preserve Darv app-knowledge docs (ORDERS fixes/how-to/calc-logic/certified-payroll) from Darv.rar extract 2026-06-14 07:15:48 -07:00
f67db6f1f2 sync: auto-sync from GURU-BEAST-ROG at 2026-06-14 07:08:13
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-06-14 07:08:13
2026-06-14 07:08:23 -07:00
3b36ac7192 wiki: compile valleywide (full) — SERVER3 retirement + G: migration to VWP-FILES + Orders source recovered 2026-06-14 06:44:14 -07:00
65f2045385 sync: auto-sync from GURU-5070 at 2026-06-14 06:29:50
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-14 06:29:50
2026-06-14 06:30:07 -07:00
956a39b631 sync: auto-sync from GURU-5070 at 2026-06-14 05:40:26
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-14 05:40:26
2026-06-14 05:40:44 -07:00
fccf9f9468 sync: auto-sync from GURU-5070 at 2026-06-14 05:33:01
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-14 05:33:01
2026-06-14 05:34:46 -07:00
93eb2fb9bb sync: auto-sync from GURU-5070 at 2026-06-13 20:21:10
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-13 20:21:10
2026-06-13 20:21:37 -07:00
b7bc3f4d25 sync: auto-sync from GURU-5070 at 2026-06-13 15:49:09
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-13 15:49:09
2026-06-13 15:49:30 -07:00
6e5a389539 sync: auto-sync from GURU-5070 at 2026-06-13 12:10:56
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-13 12:10:56
2026-06-13 15:49:30 -07:00
db3edfdb82 wiki: compile cascades-tucson (full) — shared mailboxes, Edge UNC bug, cascadesDS lock pattern; live billing 55.75h
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 10:03:16 -07:00
f76be2e6e3 submodule: advance guru-rmm -> f38da05 (RMM_THOUGHTS Feature 5: server-side public-IP capture)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 06:23:28 -07:00
be8604b4fb sync: auto-sync from GURU-5070 at 2026-06-13 06:16:25
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-13 06:16:25
2026-06-13 06:16:44 -07:00
53e43deea7 sync: auto-sync from HOWARD-HOME at 2026-06-12 22:34:17
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-12 22:34:17
2026-06-12 22:34:27 -07:00
ac3dbbbec9 sync: auto-sync from GURU-5070 at 2026-06-12 17:44:04
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-12 17:44:04
2026-06-12 17:44:21 -07:00
aebf307a81 submodule: advance guru-rmm -> SPEC-029 legacy fleet RMM (multi-AI validated)
Win7 32-bit agent already ships (Rust 1.77 legacy); proxy redundant w/ userspace TLS;
2003 -> relay/jump-host; NSIS not MSI. Gemini + Grok converged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 17:44:21 -07:00
4648acbc4c sync: auto-sync from HOWARD-HOME at 2026-06-12 17:02:02
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-12 17:02:02
2026-06-12 17:02:16 -07:00
e34d4268bc sync: auto-sync from GURU-5070 at 2026-06-12 15:53:59
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-12 15:53:59
2026-06-12 15:54:17 -07:00
af529f953d sync: auto-sync from Mikes-MacBook-Air.local at 2026-06-12 13:52:32
Author: Mike Swanson
Machine: Mikes-MacBook-Air.local
Timestamp: 2026-06-12 13:52:32
2026-06-12 13:52:33 -07:00
401ecca9a2 sync: auto-sync from GURU-5070 at 2026-06-12 13:21:22
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-12 13:21:22
2026-06-12 13:21:39 -07:00
9b02a508d6 core: restore 'vault + document EVERY in-session credential' rule; memory: IX WHM API token method + feedback
Triggered by ~1h lost on 2026-06-12 when the IX WHM access method was forgotten and
password auth no longer worked. CLAUDE.md Key rules now mandates vaulting via the vault
skill + thorough documentation for any credential surfaced in a session.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 10:52:30 -07:00
adb8c492b8 submodule: advance guru-rmm -> 8d5bb9d (Feature 4a connectivity-signal refinement)
Alert-on-state design note from the 2026-06-12 log-analysis reconciliation:
severity reclassify for benign WS resets + device-class/business-hours offline
budgets + flapping/mass-drop trends. Folds into Feature 1 + Feature 4.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 09:17:04 -07:00
32ea783c31 sync: auto-sync from GURU-5070 at 2026-06-12 08:27:16
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-12 08:27:16
2026-06-12 08:27:31 -07:00
401ed7d4e0 wiki-compile: use fuzzy Syncro query= + fallback ladder (not exact name=)
Phase 2a used `customers?name=` which is near-exact and missed slug/name spelling
mismatches — e.g. slug gonzvar-tax-services vs Syncro "Gonzvar Tax Service"
(singular), causing a false "not in Syncro". Switch to `query=` (fuzzy) with a
fallback ladder (first word, then de-pluralized token) before concluding not-found.
2026-06-12 08:22:48 -07:00
ae0efb87ca wiki: seed guruconnect + fix Gonzvar Syncro, Golden Corral mail/colocation
- guruconnect: seeded wiki/projects/guruconnect.md (v0.3.0 production; artifact-based
  from guru-connect repo @ origin/main ded99c5 + session logs + project_guruconnect
  memory). [[guruconnect]] backlinks now resolve. Indexed.
- gonzvar-tax-services: found in Syncro via fuzzy `query=` — customer is "Gonzvar Tax
  Service" (singular), id 1830740, break-fix/~$175hr, 6 assets. Billing fields corrected.
- tucson-golden-corral: email platform set to Neptune Exchange (per owner/Mike); IX
  cPanel kept as a caveat to reconcile. TGC-SERVER documented as colocated at ACG main
  office (behind ACG office network, not a naked public box at the restaurant).
2026-06-12 08:21:58 -07:00
22a55d96d0 submodule: advance guru-rmm -> 2fc6ab4 (file 2 log-analysis bugs in RMM_THOUGHTS)
Inventory NUL/jsonb reject (7 Windows agents) + update scanner dropping
non-Windows binaries (macOS/Linux agents never offered updates). Both
ROOT-CAUSED from the 2026-06-12 fleet log-analysis reconciliation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 08:18:55 -07:00
70c496bb30 wiki: compile 5 missing articles + dedupe neptune queue entry
Seeded via /wiki-compile (parallel sub-agents):
- clients: gonzvar-tax-services, tohono-oodham-doit (Syncro 33069069),
  tucson-golden-corral (Syncro 3859123)
- projects: gururmm-agent (artifact-based, agent/ @ origin/main), msp-tools (umbrella)
Index rows added for all five. Deduped the duplicate system:neptune compile-queue
entry (merged the cert/DkimSigner note into one).

Left as-is (intentional, not duplicates/dead): wiki/projects/guru-rmm.md is a
redirect tombstone; the patterns/tailscale-client-enroll.ps1 index link is valid
(the .ps1 script exists).
2026-06-12 08:06:07 -07:00
33ba780ba6 wiki-lint: fix 2 consistency gaps missed in the VM/build-chain sweep
internal-infrastructure.md inventory + backlink still called .30 a "GuruRMM VM /
Linux VM on Jupiter" and Pluto the MSI build server; pluto.md backlink still said
Pluto was the "exclusive" build machine. Both corrected: .30 is a physical box,
Beast primary / Pluto fallback. Found by /wiki-lint.
2026-06-12 07:50:26 -07:00
0665e3a007 wiki/memory: retire GuruRMM 'VM' framing + correct Windows build chain
Two sweeps:
1. .30 is a PHYSICAL box (Lenovo ThinkCentre M83, Ubuntu 26.04), not a Jupiter
   VM — the VM was decommissioned 2026-06-12. Fixed inventory tables and the
   gururmm-build system page (overview, index, jupiter, gururmm-build,
   POWER_FAILURE_RUNBOOK).
2. Windows build chain: Beast (GURU-BEAST-ROG, tailnet 100.101.122.4, i9-14900K)
   is PRIMARY; Pluto (172.16.3.36) is FALLBACK. Verified against build-windows.sh
   (`attempt_build beast || attempt_build pluto`). Fixed overview, index,
   projects/gururmm (build-host table + flow + host detail), systems/pluto, and
   the reference_pluto_build_server memory.

Submodule advanced: build-pipeline doc comments corrected to match.
2026-06-12 07:46:15 -07:00
e9a58fa8e4 submodule: advance guru-rmm (runbook cleanup done); memory: old VM decommissioned + .47 dropped 2026-06-12 07:38:49 -07:00
93b52e8c29 submodule: advance guru-rmm -> 37c8593 (runbook: host migration marked COMPLETE) 2026-06-12 07:32:05 -07:00
95c96d5dec sync: auto-sync from GURU-5070 at 2026-06-12 07:28:38
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-12 07:28:38
2026-06-12 07:28:53 -07:00
7f06e47f09 memory: record GuruRMM log-analysis cutover to Claude Haiku (root cause + deploy shape) 2026-06-12 07:16:42 -07:00
9587e91b15 submodule: advance guru-rmm -> c869e4d (log analysis via Claude API, not Ollama-on-Beast) 2026-06-12 07:16:42 -07:00
5698d96b62 sync: auto-sync from Mikes-MacBook-Air.local at 2026-06-12 06:33:44
Author: Mike Swanson
Machine: Mikes-MacBook-Air.local
Timestamp: 2026-06-12 06:33:44
2026-06-12 06:33:45 -07:00
f495b08f42 harness: gitignore tmp/ + add promotion check to /save and /scc
- .gitignore: ignore root tmp/ (temp/ and .claude/tmp/ were already ignored;
  root tmp/ was not, which is how scratch got committed and needed cleanup).
- New .claude/scripts/tmp-promotion-check.sh: advisory, read-only, never blocks.
  Scans the gitignored scratch dirs (tmp/, temp/, .claude/tmp/) and flags files
  worth graduating (scripts, substantial docs, session-log-referenced) before
  they're lost to cleanup. Silent when scratch is empty.
- /save (Phase 4) and /scc (new step 2) run the check before sync.sh, pointing
  at .claude/TEMP_GRADUATION.md for the graduate-vs-delete decision.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 06:26:20 -07:00
c3282f8cf2 sync: auto-sync from GURU-KALI at 2026-06-12 06:13:12
Author: Mike Swanson
Machine: GURU-KALI
Timestamp: 2026-06-12 06:13:12
2026-06-12 06:13:13 -07:00
ec0d032eb1 chore: clean up tracked tmp/ scratch; graduate ix-server audit + scanner
Removed 44 scratch files that got committed into the tracked root tmp/
(grok/gemini second-opinion rounds r1-r7, rmm-diag-* dumps, ns*.out
captures, and throwaway helpers jssh.py/addnpmnat.php/delnpmnat.php/
cleanup.sh/fix_ws_agent.py) — all from the resolved RMM command_type
'cmd' investigation, already captured in session logs + the gururmm wiki.

Graduated the three non-scratch artifacts per TEMP_GRADUATION.md:
- tmp/site-scan.sh -> scripts/cpanel-wp-site-scan.sh (+ header)
- tmp/ix-site-audit.md -> clients/internal-infrastructure/reports/2026-03-16-ix-server-cpanel-wp-audit.md
- tmp/ix-scan-results.txt -> clients/internal-infrastructure/reports/2026-03-16-ix-server-scan-results.txt

tmp/ is now empty.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 06:13:07 -07:00
78d82f3f69 submodule: advance guru-rmm -> 5eca48d (session log: command_type 'cmd' mis-diagnosis + 0.6.66 fix) 2026-06-12 06:00:58 -07:00
0da356e0aa submodule: advance guru-rmm -> 33150af (session log: Beast parallel build) 2026-06-12 05:59:45 -07:00
fd99ee327c sync: auto-sync from GURU-5070 at 2026-06-12 05:57:38
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-12 05:57:38
2026-06-12 05:58:05 -07:00
d1e02293c5 memory: record Beast parallel Windows build (lever A) — 336s, target-dir + cargo-fetch gotchas 2026-06-11 21:11:40 -07:00
6a2267dd7c submodule: advance guru-rmm -> 80df458 (fix parallel Windows build: drop cargo-fetch, isolate target dirs) 2026-06-11 21:04:29 -07:00
6e7c64bae9 submodule: advance guru-rmm -> b5ea567 (parallelise Windows build variants on Beast) 2026-06-11 20:50:55 -07:00
fcaa3c0ed2 memory: Beast as primary GuruRMM Windows build host (Tailscale-on-.30, WiX 4.x, Pluto fallback)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 20:16:27 -07:00
be77738698 submodule: advance guru-rmm — Beast primary Windows build host + Pluto fallback (build-windows.sh)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 20:15:14 -07:00
3158351989 submodule: advance guru-rmm — policies backend-drift close (offline-alerting + scope-aware sweep + vss.auto_heal)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 18:04:56 -07:00
802ae9cc7c memory: GURU-5070 python3 is the MS Store shim — use python/py (coord+wiki tooling work; lock is claimable)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 17:32:39 -07:00
ad8d85651e wiki: compile gururmm (full) — agent-comms-durability Phase 1, channel/promotion model, webhook auto-deploys server, fleet/version refresh (0.6.63/0.3.68)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 17:29:26 -07:00
1e232998a1 submodule: re-point guru-rmm -> 6af5f7b (rebased deploy session log onto current main)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 16:04:43 -07:00
e3c44dd466 submodule: advance guru-rmm (comms-durability Phase 1 deploy + fleet rollout session log)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 16:03:50 -07:00
80c583e27c sync: auto-sync from GURU-5070 at 2026-06-11 14:58:44
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-11 14:58:44
2026-06-11 14:59:00 -07:00
adca51239b submodule: advance guru-rmm -> 5c0d004 (installer + CLI-logging robustness fixes)
Hardens the Windows install invocation (Start-Process + exit-code check) and
cleans up agent CLI logging (file-only for one-shot commands, ANSI off on
stdout). Prompted by the Tucson RED-LION-9255 install failure (root cause was a
transient post-migration download, server cache since purged). gururmm-remote
push of 45870b1/ca1657b/dd52b20/5c0d004 still pending from .47.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 14:54:09 -07:00
ed9f94cb34 submodule: advance guru-rmm -> dd52b20 (comms-durability Phase 1 slices B+C + session log)
Agent CommandAck+dedup (45870b1), server reaper re-delivery + heartbeat re-offer
(ca1657b), session log (dd52b20). gururmm-remote push pending from .47.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 14:15:33 -07:00
d8357772f0 submodule: advance guru-rmm (comms-durability spec + slice A + session log) 2026-06-11 13:18:06 -07:00
bf9f9aad8c rmm: bump guru-rmm pointer -> 08bf323 (file WS-flakiness bug — agents heartbeat+update but interactive commands time out; needs investigation) 2026-06-11 12:36:16 -07:00
184b90163f submodule: advance guru-rmm to cea51d6 (Task 1 + session log + spec) 2026-06-11 12:31:06 -07:00
55cbae3d51 submodule: advance guru-rmm to 8ff9baf (durable-agent-identity spec) 2026-06-11 12:01:57 -07:00
f90110d8e8 sync: auto-sync from GURU-5070 at 2026-06-11 11:20:07
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-11 11:20:07
2026-06-11 11:20:20 -07:00
e3459260ec rmm: bump guru-rmm pointer -> 4b5ed30 (chmod 644 published agent artifacts — fix post-migration download 403 / fleet self-update outage + runbook gap #4) 2026-06-11 11:13:39 -07:00
e25ea146e2 sync: auto-sync from GURU-5070 at 2026-06-11 11:10:31
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-11 11:10:31
2026-06-11 11:10:45 -07:00
e1b14968e7 submodule: advance guru-rmm to 197b843 (migration docs + session logs) 2026-06-11 11:09:09 -07:00
fe79ee5d39 sync: auto-sync from GURU-5070 at 2026-06-11 09:27:40
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-11 09:27:40
2026-06-11 09:27:54 -07:00
bf6ffa7da4 sync: auto-sync from GURU-5070 at 2026-06-11 08:57:45
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-11 08:57:45
2026-06-11 08:57:57 -07:00
24bf954aaf sync: auto-sync from GURU-5070 at 2026-06-11 08:41:42
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-11 08:41:42
2026-06-11 08:41:56 -07:00
25d2cf5148 sync: auto-sync from GURU-5070 at 2026-06-11 08:33:19
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-11 08:33:19
2026-06-11 08:33:32 -07:00
d0f90d4023 sync: auto-sync from GURU-5070 at 2026-06-11 08:29:58
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-11 08:29:58
2026-06-11 08:30:10 -07:00
65ad20ae0f sync: auto-sync from GURU-5070 at 2026-06-11 08:22:42
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-11 08:22:42
2026-06-11 08:22:55 -07:00
6ade6153bf sync: auto-sync from GURU-5070 at 2026-06-11 08:21:26
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-11 08:21:26
2026-06-11 08:21:38 -07:00
543228fdba sync: auto-sync from GURU-5070 at 2026-06-11 08:10:50
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-11 08:10:50
2026-06-11 08:11:03 -07:00
55445d78dc sync: auto-sync from GURU-5070 at 2026-06-11 08:02:42
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-11 08:02:42
2026-06-11 08:02:55 -07:00
6bd3210e21 sync: auto-sync from GURU-5070 at 2026-06-11 08:01:12
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-11 08:01:12
2026-06-11 08:01:27 -07:00
cfc065b097 sync: auto-sync from GURU-5070 at 2026-06-11 08:00:04
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-11 08:00:04
2026-06-11 08:00:19 -07:00
23299a661e sync: auto-sync from GURU-5070 at 2026-06-11 07:45:33
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-11 07:45:33
2026-06-11 07:45:46 -07:00
ee1eba5f4c sync: auto-sync from GURU-5070 at 2026-06-11 07:24:11
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-11 07:24:11
2026-06-11 07:24:27 -07:00
83133ddce3 sync: auto-sync from HOWARD-HOME at 2026-06-10 20:21:07
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-10 20:21:07
2026-06-10 20:21:23 -07:00
9c56690270 sync: auto-sync from GURU-5070 at 2026-06-10 20:18:48
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-10 20:18:48
2026-06-10 20:19:05 -07:00
df2b350cff rmm: bump pointer — migration Phase 0 staged + Workstream B done 2026-06-10 20:05:03 -07:00
470a8e7eb1 rmm: host-migration runbook + ratified architecture (memory + pointer)
Bump guru-rmm pointer (host-migration runbook). Record the migration architecture
decision in memory: physical box becomes .30 (all-but-Gitea-runner), VM retired,
MariaDB migrates (backs the coord claudetools DB per Gate-A).
2026-06-10 18:40:07 -07:00
0455472c70 rmm: bump guru-rmm pointer — batch agent_logs ingest (multi-row INSERT) 2026-06-10 16:36:13 -07:00
Winter Williams
670d5ad94c wiki: update putt-land-surveying with DKIM records + onmicrosoft domain
Added DKIM selector CNAMEs from Exchange Online (status: Valid), confirmed
onmicrosoft.com domain (puttsurveying.onmicrosoft.com), and expanded DNS wipe
section with full 6-record restoration checklist.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 16:27:25 -07:00
Winter Williams
eebcb0e397 wiki: compile putt-land-surveying (seed)
New client wiki article for PUTT LAND SURVEYING, INC. (Syncro 7180175).
Synthesized from 2026-06-10 DNS wipe investigation session log + live Syncro data.
Covers managed services contract, M365 direct tenant, DNS wipe incident, remediation
tool onboarding, device fleet, and contact/ownership transition to Paul Cote.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 16:13:24 -07:00
63f427a95f sync: auto-sync from GURU-5070 at 2026-06-10 16:02:59
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-10 16:02:59
2026-06-10 16:03:13 -07:00
Winter Williams
d573842ba2 sync: auto-sync from GURU-BEAST-ROG at 2026-06-10 15:47:04
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-06-10 15:47:04
2026-06-10 15:47:12 -07:00
c871ad8815 sync: auto-sync from GURU-5070 at 2026-06-10 15:18:03
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-10 15:18:03
2026-06-10 15:18:16 -07:00
4b0ae3448f rmm: bump guru-rmm pointer — remove LHM support from agent 2026-06-10 14:47:47 -07:00
81a321abc0 sync: auto-sync from HOWARD-HOME at 2026-06-10 14:34:34
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-10 14:34:34
2026-06-10 14:34:43 -07:00
1e988049b3 rmm: bump guru-rmm pointer — BSOD warn->debug + WS keepalive 30s 2026-06-10 14:30:02 -07:00
eb3d934785 rmm: bump guru-rmm pointer — server self-error capture + alert 2026-06-10 14:06:21 -07:00
35264f24e0 sync: auto-sync from HOWARD-HOME at 2026-06-10 14:04:01
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-10 14:04:01
2026-06-10 14:04:10 -07:00
e9c1bd8ff4 rmm: bump guru-rmm pointer — log-feedback backfill + ERROR/WARN panel filter
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 13:47:52 -07:00
06c2b191d7 sync: auto-sync from HOWARD-HOME at 2026-06-10 13:30:39
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-10 13:30:39
2026-06-10 13:30:54 -07:00
abbc185e02 sync: auto-sync from HOWARD-HOME at 2026-06-10 13:25:54
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-10 13:25:54
2026-06-10 13:26:10 -07:00
5eee825ecf wiki: compile universal-minerals (full)
Full recompile via Sonnet synthesis: enriched with Syncro billing history
(#100079, #67060, #67810), corrected ticket #32397 to Invoiced status,
added deferred items. Break-fix/no-RMM client.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 13:22:55 -07:00
bd5e977b6e sync: auto-sync from HOWARD-HOME at 2026-06-10 13:15:14
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-10 13:15:14
2026-06-10 13:15:27 -07:00
a0f62b4d40 rmm: bump guru-rmm pointer -> 56e1871 (log-feedback Phase 1 + normalizer v2 fix)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 12:53:26 -07:00
f4c53868fd rmm: bump guru-rmm pointer -> 18de5c7 (systemic-log-feedback Phase 1 complete)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 12:38:45 -07:00
e08a21702a sync: auto-sync from HOWARD-HOME at 2026-06-10 12:28:50
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-10 12:28:50
2026-06-10 12:29:01 -07:00
9153427c63 rmm: bump guru-rmm pointer -> da86aca (systemic-log-feedback spec + Phase 1 foundation, WIP)
Protects in-progress submodule work from submodule-update reverts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 12:25:34 -07:00
35847895ae sync: auto-sync from GURU-5070 at 2026-06-10 12:22:23
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-10 12:22:23
2026-06-10 12:22:34 -07:00
f7a1c2ecdc rmm: bump guru-rmm pointer — RMM_THOUGHTS Feature 4 (systemic log-feedback)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 11:59:15 -07:00
7cdc660bae rmm: bump guru-rmm pointer -> Event Log Watch management UI (e67dd82)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 11:47:16 -07:00
0edb0047c6 sync: auto-sync from Mikes-MacBook-Air.local at 2026-06-10 11:39:35
Author: Mike Swanson
Machine: Mikes-MacBook-Air.local
Timestamp: 2026-06-10 11:39:35
2026-06-10 11:39:38 -07:00
ddd146bef5 rmm: bump guru-rmm pointer -> 5260a0f (2026-06-09 audit fixes + tray pipeline, merged & shipped to prod)
Submodule now at the merge that shipped: status-stream auth, event-log
reconnect, credential-key fail-closed, coord proxy, sqlx runtime, internal_err
sweep, WS payload caps, credential-reveal audit log (migration 056), tray
build/sign/deploy pipeline (BUG-020). Deployed via pipeline: server v0.3.58,
dashboard beta v0.2.67, tray 0.6.57.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 10:52:27 -07:00
Winter Williams
da820d0a22 sync: auto-sync from GURU-BEAST-ROG at 2026-06-10 10:29:05
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-06-10 10:29:05
2026-06-10 10:29:11 -07:00
Winter Williams
c6c3cf92d1 wiki: refresh starr-pass — Syncro ID 153298, contacts, billing, assets 2026-06-10 10:26:01 -07:00
0e7a3faaba sync: auto-sync from GURU-5070 at 2026-06-10 10:23:06
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-10 10:23:06
2026-06-10 10:23:21 -07:00
f4528168f7 rmm: bump guru-rmm pointer — MEDIUM fixes (WS payload caps, Agent TS types, credential-reveal audit log)
Submodule 5cd11a3..ed92097:
- harden: bound agent-pushed WS payloads + fix Agent TS type drift
- feat: credential-reveal audit logging (audit_log table)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 10:23:20 -07:00
14dcd3beed rmm: bump guru-rmm pointer — 2026-06-09 audit HIGH fixes (cred key, coord proxy, sqlx, 500-leak sweep)
Submodule 4321e91..5cd11a3

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 10:23:20 -07:00
9702caf8c1 rmm: bump guru-rmm pointer — event-log watch CRUD full-config push
Submodule 557fa52..4321e91:
- fix: event-log watch CRUD push sends full policy + watches

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 10:23:20 -07:00
e90ff5d2f3 rmm: bump guru-rmm pointer — event-log watch reconnect re-push
Submodule f7750fa..557fa52:
- fix: re-push event-log watch rules on agent (re)connect

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 10:23:20 -07:00
599822b7f8 rmm: bump guru-rmm pointer — status-stream auth fix + 2026-06-09 audit
Submodule 226ba9f..f7750fa:
- fix: authenticate /api/agents/status-stream (SSE) + org-scope it
- docs: 2026-06-09 rmm-audit report + living-doc reconcile

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 10:23:20 -07:00
Winter Williams
94410944eb wiki: compile starr-pass (seed) — M365 onboarding, SPF cleanup, user audit 2026-06-10 2026-06-10 10:22:48 -07:00
Winter Williams
cf68d1c718 sync: auto-sync from GURU-BEAST-ROG at 2026-06-10 10:18:35
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-06-10 10:18:35
2026-06-10 10:18:40 -07:00
Winter Williams
7729874549 sync: auto-sync from GURU-BEAST-ROG at 2026-06-10 10:09:59
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-06-10 10:09:59
2026-06-10 10:10:08 -07:00
b75fb56574 sync: auto-sync from HOWARD-HOME at 2026-06-10 10:09:12
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-10 10:09:12
2026-06-10 10:09:23 -07:00
222849251f sync: auto-sync from GURU-5070 at 2026-06-09 18:41:07
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-09 18:41:07
2026-06-09 18:41:46 -07:00
2a006483f9 sync: auto-sync from GURU-5070 at 2026-06-09 18:18:03
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-09 18:18:03
2026-06-09 18:18:41 -07:00
6a961e06f4 sync: auto-sync from GURU-5070 at 2026-06-09 17:27:28
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-09 17:27:28
2026-06-09 17:28:17 -07:00
2625800885 wiki+memory: consolidate kittle-design -> kittle (redirect stub); add feedback memories (syncro preview, refresh-first, autonomy scope)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 17:28:17 -07:00
ac82e359a7 wiki: compile kittle (full) — BEC/ACH incident, entry-point root cause, CA hardening; mark kittle-design superseded
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 17:28:16 -07:00
4adf2c586c sync: auto-sync from HOWARD-HOME at 2026-06-09 17:08:26
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-09 17:08:26
2026-06-09 17:08:39 -07:00
67e0f8df20 sync: auto-sync from GURU-5070 at 2026-06-09 16:18:12
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-09 16:18:12
2026-06-09 16:18:52 -07:00
848ab69df5 sync: auto-sync from GURU-5070 at 2026-06-09 10:52:48
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-09 10:52:48
2026-06-09 10:53:34 -07:00
2029fa5429 sync: auto-sync from HOWARD-HOME at 2026-06-09 10:33:12
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-09 10:33:12
2026-06-09 10:33:25 -07:00
95b89c56a8 sync: auto-sync from GURU-5070 at 2026-06-09 10:13:37
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-09 10:13:37
2026-06-09 10:14:16 -07:00
53584e1497 report(kittle): IC3 complaint filed - submission ID aa2ef504... (2026-06-09)
IC3 filed 2026-06-09 12:46 EST. Stamped the submission ID on the report; bank freeze letters
(Truist/First State/Chase) updated with the IC3 # and real Kittle/ACG contacts - now turnkey to send.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 09:49:35 -07:00
4c580fe485 report(kittle): fraud PREVENTED - City stopped payment, Foam Factory confirmed mule
Per Kittle bookkeeper (2026-06-09): City of Tucson stopped the payment before any funds reached
the attacker (no completed loss; attempted $130k+). Kittle confirms no Foam Factory relationship,
confirming both receiving accounts are mules. Also: Ken un-restricted from sending (Outbox/Drafts
verified empty first); Lori was never restricted.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 09:15:07 -07:00
42135ed557 report(kittle): fold confirmed invoice amounts into IC3 report
Inv #31468 $123,776.75 (confirmed), Inv #31400 ~$8,818, Inv #31453 $41,231 (open);
total identified exposure $130,000+ since the ACH change redirects all City->Kittle payments.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 08:04:36 -07:00
c5a7c15cff report(kittle): IC3 BEC/ACH-fraud complaint package
Consolidated FBI IC3 report for the Kittle payment-redirection fraud: victim/payer info,
fraudulent mule accounts (Truist 053201607/1410020505238; Foam Factory First State + Chase),
targeted City of Tucson payments (Inv #31400 ~$8,818 6/9 EFT; Inv #31468 $123,776.75),
attacker IPs/domains/phone, full timeline, and evidence inventory. Evidence package assembled
to Downloads/Kittle-IC3-Package (report + 2 ACH form PDFs + recovered emails + 171-event audit CSV).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 07:52:24 -07:00
ce8401a093 sync: auto-sync from GURU-5070 at 2026-06-08 21:04:39
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-08 21:04:39
2026-06-08 21:05:24 -07:00
1cc03e9f23 verify(remediation): kittlearizona EXO persistence re-checked clean post role-fix
Double-checked the 2026-06-08 BEC remediation for missed EXO-dependent items now that
the Exchange role is confirmed. Findings: malicious inbox rules gone (cleanup stuck);
all 14 mailboxes clean of fwd/redirect/delete/move rules; no mailbox forwarding; no
transport rules; no rogue delegates. Open (need Ken): Christina-Micek StopProcessing rule
+ Ken FullAccess to Accounting. Corrected stale 'Exchange Admin NOT assigned' note (it IS).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 21:05:24 -07:00
2efd4a4fb3 discord-bot: fix "no response", serialize turns, attribution, mentions, post-at-bottom
client.py: send() falls back to ResultMessage.result when no TextBlock streams
(the "(no response)" bug) and reconnects+retries once on a closed SDK session.

message_handler.py: per-thread turn lock so messages arriving mid-turn or from a
second user queue in order (nothing dropped); per-session requester-attribution
env (discord_id -> users.json key), pinned to the thread opener; _USER_MAP caches
only on a successful load; final answer posts as a fresh message at the BOTTOM
(no edit-in-place); a <@id> tag goes out as a fresh send so it actually pings.

main.py: allowed_mentions permits user pings, blocks @everyone/@here/roles.

DISCORD_CLAUDE.md: no thread auto-delete; tiered close-out (Q&A -> one-line rolling
log, substantive -> /save); @mention guidance; opener-pinned attribution note.

whoami-block.sh / sync.sh: bot-context attribution (Executed by ClaudeTools Bot /
Requested by <person>; git author = mapped requester, committer = bot). Strict
no-op for interactive sessions.

users.json: discord_id for Mike/Howard; added Winter Williams (bot-only, full trust).

Reviewed by Code Review Agent + Grok + Gemini (Gemini's "malformed email" finding
verified as a false positive).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 21:00:34 -07:00
7fc29a7c5f fix(remediation): close the recurring Exchange-Admin-role gap fleet-wide
EXO email-cleanup tasks (Search-UnifiedAuditLog, Get-MessageTrace, inbox rules) kept
401/403-ing per tenant because the Exchange Operator SP was missing the Exchange Admin
directory role — admin consent grants Exchange.ManageAsApp but never the directory role.
onboard-tenant.sh assigns it, but tenants consented before that step / by hand never got
it, and nothing audited for it. Hence the recurring 'next onboarding will fix it' (false
for already-onboarded tenants).

- NEW assign-exchange-role.sh: idempotent role assignment via the authoritative
  roleManagement/directory/roleAssignments API (the legacy directoryRoles/members list
  reads back unreliably). <domain|--all> + --verify/--dry-run.
- Backfilled the whole fleet (--all): 13 stragglers ASSIGNED, 12 already OK, 20 skipped
  (tenant-admin not consented), 0 errors. Safe Site included.
- Standing audit documented (assign-exchange-role.sh --all --verify) + memory so no future
  session repeats the empty promise.
- Adds wiki/clients/safesite.md (tenant + 4-source endpoint inventory + investigation).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 20:07:28 -07:00
19b5ca299b sync: auto-sync from GURU-5070 at 2026-06-08 19:51:00
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-08 19:51:00
2026-06-08 19:51:46 -07:00
efb5bdfa77 sync: auto-sync from GURU-BEAST-ROG at 2026-06-08 19:11:27
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-06-08 19:11:27
2026-06-08 19:11:33 -07:00
a0e01c3d39 sync: auto-sync from GURU-5070 at 2026-06-08 19:04:33
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-08 19:04:33
2026-06-08 19:05:38 -07:00
d250086933 sync: auto-sync from GURU-BEAST-ROG at 2026-06-08 18:57:41
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-06-08 18:57:41
2026-06-08 18:57:46 -07:00
ef569dc84b sync: auto-sync from GURU-BEAST-ROG at 2026-06-08 16:57:04
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-06-08 16:57:04
2026-06-08 16:57:09 -07:00
31260814ee sync: auto-sync from GURU-BEAST-ROG at 2026-06-08 16:23:44
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-06-08 16:23:44
2026-06-08 16:23:48 -07:00
7f7f844eba sync: auto-sync from GURU-BEAST-ROG at 2026-06-08 15:55:24
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-06-08 15:55:24
2026-06-08 15:55:30 -07:00
c0ef73920c fix(remediation): Safe Site Utility Services marked onboarded (was stale NO)
Live-verified 2026-06-08: Security Investigator + User Manager + Tenant Admin Graph
tiers all consented and reading (subscribedSkus/organization HTTP 200) on
safesitellc.com (71b4e637-...). The reference's 'NO' was stale (last touched 2026-04-20).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 15:36:43 -07:00
7a84b30047 sync: auto-sync from HOWARD-HOME at 2026-06-08 15:25:56
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-08 15:25:56
2026-06-08 15:26:05 -07:00
f2474def5b sync: auto-sync from GURU-BEAST-ROG at 2026-06-08 10:50:37
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-06-08 10:50:37
2026-06-08 10:50:42 -07:00
eb5757d170 sync: auto-sync from GURU-BEAST-ROG at 2026-06-08 10:10:01
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-06-08 10:10:01
2026-06-08 10:10:06 -07:00
a14b723306 sync: auto-sync from GURU-BEAST-ROG at 2026-06-08 10:01:07
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-06-08 10:01:07
2026-06-08 10:01:14 -07:00
512ceb4727 feat(harness-guard): FATAL-promotion prerequisite — test matrix + pair-required conflict rule (VERSION 1.4.3)
Builds the false-positive/true-positive proof the plan requires before the guard can be
promoted to blocking, and fixes the one false-positive it surfaced.

- test-harness-guard.sh: 12-case matrix in a throwaway repo, runs the REAL guard, asserts
  WARN/clean for real conflicts/secrets/keys vs legit content (setext underlines, dividers,
  docs that mention a marker, encrypted sops, public keys, .example templates).
- harness-guard.sh: conflict rule now requires a real hunk (BOTH ^<<<<<<< AND ^>>>>>>>),
  dropping the lone =======$ trigger that false-positived on a 7-char setext underline /
  divider. Identical true-positive power (git writes all three markers); FP surface -> 0.
- /self-check: new harness.guard_selftest runs the matrix in an isolated temp repo (read-only
  vs the real tree) so guard correctness is continuously proven.

Verified 12/12 pass, true positives intact, real-tree FP surface = 0. FATAL flip (todo
f1c11d0d, on/after 2026-06-22) is now evidence-backed + one-step.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 08:41:58 -07:00
cfa264947b sync: auto-sync from GURU-BEAST-ROG at 2026-06-08 08:40:52
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-06-08 08:40:52
2026-06-08 08:40:58 -07:00
31e5cbd370 sync: auto-sync from GURU-5070 at 2026-06-08 08:34:06
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-08 08:34:06
2026-06-08 08:34:11 -07:00
e180a463e2 feat(self-check): command-restates-standard lint (consistency category, VERSION 1.4.2)
Task 3 leftover. Adds a 'consistency' category to /self-check that catches a standard
drifting back into restating/contradicting the command that owns the rule -- the Syncro
timers failure mode (standard said 'always timer' while /syncro said 'outlier only').

Deterministic half: each manifest.command_standard_links pair's standard must still carry
its defer-to-SSOT pointer (must_reference regex). Lost pointer = WARN. Seeded with
syncro-billing (time-entry-protocol.md -> /syncro). Semantic contradiction pass delegated
to the model in SKILL.md, mirroring check_memory. Verified PASS; negative-tested.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 08:29:58 -07:00
edcbc5f7ea feat(self-check): harness smoke tests lock in the 1.4.0 invariants (VERSION 1.4.1)
Adds a 'harness' category to /self-check (Task 12, self-check half) so the harness-
optimization gains can't silently regress. All read-only / non-invasive:
- VERSION marker present + not older than manifest.harness.min_version
- skill-registry description budget (sum of all SKILL.md description: fields under
  registry_desc_budget_chars) -- the metric that catches Task 5 bloating back
- global deploy targets ~/.claude/skills + ~/.claude/commands populated (Mac-wipe failure)
- harness-guard.sh present + wired into sync.sh
- core scripts parse (bash -n on sync/guard/now-phoenix); now-phoenix.sh emits a valid date

Tunables in baseline/manifest.json 'harness' block. Verified 9/9 PASS; budget WARN
negative-tested at a synthetic over-budget value.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 08:24:28 -07:00
d4d24b5afd docs(harness): reconcile remaining GrepAI-first refs with wiki-first hierarchy
The context-lookup standard + CODING_GUIDELINES still said 'GrepAI First' unconditionally.
Updated both to: wiki first for known-entity facts; GrepAI/Grep-before-read for code+discovery.
Keeps the search-before-read token discipline; removes the wiki overlap. Completes the
positioning fix started in e8a689b0 (all 4 sources now consistent: CORE, EXTENDED, standard,
guidelines).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 08:15:25 -07:00
e8a689b03e docs(harness): demote GrepAI below the wiki in recall hierarchy
Resolves the contradiction between CORE (wiki-first) and EXTENDED (which said
'use GrepAI first for any context lookup'). New order: wiki for known entities ->
GrepAI for code call-graphs / discovery / un-compiled detail -> raw reads. Keeps
GrepAI's irreplaceable code-search value; removes the redundant wiki overlap.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 08:14:20 -07:00
68ad1dbd40 feat(harness): P1+P2+P3 harness optimization complete (VERSION 1.4.0)
Task 5  one-line registry descriptions on the 8 biggest skills (remediation-tool,
        gc-audit, packetdial, memory-dream, human-flow, self-check, impeccable,
        mailprotector); skill-description injection ~3320 -> ~2123 tokens (~36%),
        keyword triggers preserved, frontmatter valid.
Task 7  thinned /save + /sync bodies to point at sync.sh (single source) instead of
        re-documenting internals; Phase 0 save-vs-sync, cross-user notes, exit-75
        reporting kept verbatim; mechanical sync never depends on an LLM step.
Task 10 session-logs/YYYY-MM/ forward convention for new logs (scoped-grep recall,
        no monolithic index); existing flat logs untouched (grep covers both).
Bash    now-phoenix.sh helper (fixed UTC-7 epoch math; replaces unreliable
        TZ=America/Phoenix date that silently returns UTC on Git-Bash).

P0 (1.2.0) + Task 6 CLAUDE split + Task 9 delegation (1.3.0) already shipped.
Spec: specs/claudetools-harness-optimization/plan.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 08:11:03 -07:00
6671a7a400 sync: auto-sync from HOWARD-HOME at 2026-06-08 08:10:17
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-08 08:10:17
2026-06-08 08:10:25 -07:00
60f1a844f3 sync: auto-sync from GURU-5070 at 2026-06-08 08:01:36
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-08 08:01:36
2026-06-08 08:01:41 -07:00
3973311beb sync: auto-sync from GURU-5070 at 2026-06-08 07:56:09
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-08 07:56:09
2026-06-08 07:56:14 -07:00
d2bb8d3c38 sync: auto-sync from GURU-5070 at 2026-06-08 07:55:26
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-08 07:55:26
2026-06-08 07:55:31 -07:00
e166e14284 sync: auto-sync from GURU-5070 at 2026-06-08 07:44:43
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-08 07:44:43
2026-06-08 07:44:47 -07:00
0318cab715 sync: auto-sync from GURU-5070 at 2026-06-08 07:42:44
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-08 07:42:44
2026-06-08 07:42:48 -07:00
4be5b07529 harness(p0): add VERSION marker + OOB recovery script (Tasks 0.5, 0.6)
Safety prerequisites for the P0 rollout, landed BEFORE any sync.sh change so a bad
harness change cannot strand a node. .claude/harness/VERSION (1.0.0) lets a session
detect partial rollout; .claude/scripts/force-pull-raw.sh is a hook-free git rescue
(dry-run by default; --confirm hard-resets to origin/main, saving prior HEAD to a
recovery branch).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 07:39:48 -07:00
f177f45657 fix(syncro): resolve billing SSOT — add_line_item is normal, timers outlier-only
Task 3/3a of the harness-optimization spec. Mike confirmed normal billing uses
add_line_item; timers stay available only for explicit outlier requests, never the
normal loop. Rewrote time-entry-protocol.md to defer to the /syncro command (SSOT for
billing mechanics) and state timers are outlier-only; aligned the command's two
absolute "no timers" lines. Contradiction removed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 07:37:36 -07:00
bb7dc147ca spec: ClaudeTools harness optimization (3-way reviewed)
Optimize the harness (not projects) for accuracy/completeness with context pressure
as a first-class constraint; token efficiency secondary. Authored as a Claude+Grok+
Gemini review (see review-3way.md): P0 reliability footguns (submodule-safe sync,
serialized/staged wiki synthesis, syncro SSOT, warn-only guard), P1 context diet
(one-line registry descriptions, CLAUDE CORE/EXTENDED, thin save/sync), P2 delegation
re-tune, P3 knowledge tiering. Adds harness VERSION marker + OOB recovery as rollout
safety. Python port split to a separate future spec.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 07:32:45 -07:00
0f02cae98c sync: auto-sync from GURU-BEAST-ROG at 2026-06-08 06:55:21
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-06-08 06:55:21
2026-06-08 06:55:27 -07:00
41450301dc sync: auto-sync from GURU-5070 at 2026-06-08 06:50:14
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-08 06:50:14
2026-06-08 06:50:19 -07:00
14362628a2 sync: auto-sync from GURU-BEAST-ROG at 2026-06-07 21:26:22
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-06-07 21:26:22
2026-06-07 21:26:26 -07:00
62fed03362 sync: auto-sync from GURU-5070 at 2026-06-07 20:52:31
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-07 20:52:31
2026-06-07 20:52:35 -07:00
6852714981 sync: auto-sync from Mikes-MacBook-Air.local at 2026-06-07 19:46:36
Author: Mike Swanson
Machine: Mikes-MacBook-Air.local
Timestamp: 2026-06-07 19:46:36
2026-06-07 19:46:38 -07:00
d0254b90ee sync: auto-sync from GURU-BEAST-ROG at 2026-06-07 19:45:04
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-06-07 19:45:04
2026-06-07 19:45:11 -07:00
b928fdb8f3 sync: auto-sync from GURU-5070 at 2026-06-07 17:45:03
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-07 17:45:03
2026-06-07 17:45:07 -07:00
05c17b476f sync: auto-sync from GURU-5070 at 2026-06-07 16:47:01
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-07 16:47:01
2026-06-07 16:53:22 -07:00
8b5a5ce983 sync: auto-sync from GURU-BEAST-ROG at 2026-06-07 15:55:01
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-06-07 15:55:01
2026-06-07 15:55:08 -07:00
0210d66b40 sync: auto-sync from Mikes-MacBook-Air.local at 2026-06-07 12:59:13
Author: Mike Swanson
Machine: Mikes-MacBook-Air.local
Timestamp: 2026-06-07 12:59:13
2026-06-07 12:59:46 -07:00
b848e34a8e sync: auto-sync from GURU-5070 at 2026-06-07 10:33:04
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-07 10:33:04
2026-06-07 10:33:10 -07:00
7ba2f26fde sync: auto-sync from Mikes-MacBook-Air.local at 2026-06-07 10:26:40
Author: Mike Swanson
Machine: Mikes-MacBook-Air.local
Timestamp: 2026-06-07 10:26:40
2026-06-07 10:26:43 -07:00
8f6f7cabb2 sync: auto-sync from GURU-5070 at 2026-06-07 08:15:08
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-07 08:15:08
2026-06-07 08:15:11 -07:00
261988956d docs(memory): vault git-auth fix — GCM shadows store token on git.azcomputerguru.com
Vault sync was failing with "remote: Failed to authenticate user" against
git.azcomputerguru.com. Root cause: Git Credential Manager (first in the
helper chain) shadowed the valid PAT in the store helper with a stale
cached OAUTH_USER JWT.

Fix (machine-local git config, already applied — not in the repo):
- Reset the vault repo credential.helper to store-only (drop inherited GCM).
- Pin azcomputerguru@ in the vault remote URL so store returns the durable
  PAT instead of a volatile OAUTH_USER JWT.

Repo change here is documentation only: a feedback memory capturing the
diagnosis + fix, plus an index line in MEMORY.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 08:07:13 -07:00
8b57a5c770 sync: auto-sync from GURU-5070 at 2026-06-07 07:54:09
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-07 07:54:09
2026-06-07 07:54:13 -07:00
faa7d7db81 sync: auto-sync from GURU-5070 at 2026-06-06 20:29:16
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-06 20:29:16
2026-06-06 20:29:20 -07:00
8a9759789f feat(scripts): add Firefox driver (ff.py) via Playwright; disable claude-in-chrome
Add .claude/scripts/ff.py, a Firefox browser driver built on Playwright and
the Firefox sibling of the existing cdp.py Chrome driver. It runs a small
background daemon holding one Playwright Firefox page on a persistent profile,
controlled over localhost:9333, with subcommands launch/status/nav/shot/click/
type/eval/console/network/stop. Verified end-to-end (real screenshot, network
and console capture). This is now the preferred browser-automation path because
Mike dislikes Chrome and the claude-in-chrome extension (that connector was
disabled in ~/.claude.json this session - not a repo change).

Add memory reference_ff_firefox_driver.md documenting the driver and an index
line in MEMORY.md. The MEMORY.md change also unavoidably includes a pre-existing
adjacent index line for reference_antigravity_agy_not_headless.md, so that memory
file is bundled in to keep the index consistent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 18:50:45 -07:00
5a9fe1bc6c sync: auto-sync from HOWARD-HOME at 2026-06-06 16:15:15
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-06 16:15:15
2026-06-06 16:15:28 -07:00
34fa93b361 sync: auto-sync from GURU-5070 at 2026-06-06 15:46:17
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-06 15:46:17
2026-06-06 15:46:22 -07:00
f75405506e docs(wiki): SMB files+printer over Tailscale (Windows) + Wolkin scope
Robert Wolkin use case is RSW-Laptop accessing file shares + a shared
printer on front. Add a reusable Windows files/printer section to the
pattern (SMB over the tailnet, the 445 firewall-on-Tailscale-interface
gotcha scoped to 100.64.0.0/10, local-account auth on Home, MagicDNS
FQDN, Point-and-Print via RMM, Taildrive alternative). Record the
concrete per-host post-connect config and the printer-type open item in
the client doc.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 15:41:14 -07:00
32e71a1300 docs(wiki): fill Robert Wolkin stub from GuruRMM scan + scope Tailscale
GuruRMM client Wolkin, Robert / site Main has 3 online Win11 Home agents
(DESKTOP-V1JT1SE, RSW-Laptop, front; agent v0.6.57, IDs recorded).
Tailscale scope is RSW-Laptop -> front only; DESKTOP-V1JT1SE is Bob's
personal machine, intentionally out of scope.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 15:37:00 -07:00
5c7e196b6c docs(wiki): add Robert Wolkin client stub for Tailscale rollout
Stub client article (two-machine, non-technical office) tracking the
dedicated-tailnet rollout per the Tailscale client-management pattern.
Indexed under wiki Clients; profile/Syncro fields marked unverified.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 15:33:09 -07:00
8d7e3805c7 docs(wiki): add Tailscale client-management pattern + GuruRMM enroll script
One tailnet per client (never merge into ACG own tailnet), MSP holds Admin,
devices enrolled as tagged nodes via pre-auth keys pushed from GuruRMM.
Includes tailscale-client-enroll.ps1 (idempotent unattended Windows MSI
install + tagged auth-key join), a see-each-other tag ACL, the Windows
subnet-routing reality (userspace/netstack, not the old IP-forward hack),
and a runbook. Indexed under wiki Patterns.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 15:26:15 -07:00
fd30af6aba fix(bootstrap): cover both python interpreters + grok PATH + git auth
Amend windows-bootstrap.ps1 with every gap the 2026-06-06 GURU-5070
reinstall exposed, so the next rebuild is clean:

- Phase 7: install python deps into BOTH interpreters (py/3.14 for vault
  + scripts, python/3.12 for the MCP servers). Single-interpreter installs
  left ticktick MCP (no httpx/mcp in 3.12) and vault get-field (no PyYAML
  in 3.14) dead. Add pyyaml + websocket-client to the baseline libs.
- Phase 3: persist ~\.grok\bin (+ ~\.local\bin, %APPDATA%\npm) to the User
  PATH; grok's installer leaves it session-only.
- Phase 6: prime non-interactive git auth (setup-git-auth.sh) so pushes
  never hang on a GCM prompt.
- Phase 8: expand to the real 5-model set and add the hydration gotcha so a
  populated D:\OllamaModels is never needlessly re-downloaded (~48 GB).

Document all four in machines/guru-5070.md known issues.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 15:11:55 -07:00
162145b559 feat(git-auth): fleet-wide non-interactive git auth
Add setup-git-auth.sh: idempotent, fail-silent script that primes the
git credential store from the vault Gitea token, scoped per-repo by the
actual origin host. Only seizes the helper from the prompting GCM
`manager` (leaves Mac osxkeychain alone); fast-path no-op once set.

Wire it into a backgrounded SessionStart hook and set
GIT_TERMINAL_PROMPT=0 / GCM_INTERACTIVE=Never in settings.json env so
no session on any machine can hang on a credential prompt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 15:02:09 -07:00
9ff5a9f04f docs(gitea): require non-interactive git auth on Windows
Mike's objection to Git for Windows is the constant GCM password
prompts that hang automation/background pushes, not the tool itself.
Document the working fix (repo-local credential.helper=store primed
with the azcomputerguru Gitea API token, GIT_TERMINAL_PROMPT=0) in the
Gitea Agent definition and shared memory.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 14:54:16 -07:00
f3a175e5d6 fix(ticktick-mcp): record httpx + mcp deps in requirements.txt
The ticktick local stdio MCP server crashed at startup with
"Connection closed" (surfaced by /doctor) because its Python 3.12
interpreter was missing the httpx and mcp packages. After installing
them, record the two third-party dependencies here so future machines
have them on record and can reproduce the working environment.
2026-06-06 14:43:47 -07:00
974fb97f10 feat(bootstrap): set hostname in Phase 0
Rename the machine to the name in the bundle's identity.json (default GURU-5070,
override with -Hostname) when run as admin, with an end-of-run reboot reminder.
Ensures scheduled tasks, coord session IDs, and log attribution line up. RESTORE.md
documents the step.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 12:17:11 -07:00
7342be1eaf feat(bootstrap): restore rescued GuruRMM/GuruConnect WIP on rebuild
Add restore-at-risk-work.ps1 and wire it into bootstrap Phase 6. Recreates
local-only WIP rescued to the recovery bundle's at-risk-work/: re-applies the
three guru-rmm stash patches back AS stashes (LIFO order preserved) and drops
the guru-connect tmp-spec018.diff back as its untracked working file. Patches
that won't apply cleanly are reported for manual git apply --3way. Updates
RESTORE.md and the session log with the rescue details.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 12:11:08 -07:00
6bb75e9320 feat(bootstrap): Windows recovery + reinstall toolkit for GURU-5070
Add .claude/bootstrap/ (windows-bootstrap.ps1, restore-secrets.ps1,
backup-to-bundle.ps1, RESTORE.md) plus machines/guru-5070.md. Idempotent
11-phase rebuild after a clean Windows reset: winget core tools + .NET/WiX,
protoc, Poppler, Tailscale; restore SOPS age key/SSH/tool-auth/identity from
the E:/F: recovery bundle; clone repos+submodules; set OLLAMA_MODELS/HOST/PROTOC;
detect existing D:\OllamaModels; register scheduled tasks. Includes session log.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 12:06:51 -07:00
5b9bb949a2 chore: auto-recover 1 unsaved session log(s)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 12:06:51 -07:00
34d34c610f sync: auto-sync from Mikes-MacBook-Air.local at 2026-06-06 11:32:15
Author: Mike Swanson
Machine: Mikes-MacBook-Air.local
Timestamp: 2026-06-06 11:32:15
2026-06-06 11:32:16 -07:00
84055d62e1 sync: auto-sync from GURU-5070 at 2026-06-06 08:27:44
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-06 08:27:44
2026-06-06 08:27:50 -07:00
d4abbff1d2 sync: auto-sync from GURU-5070 at 2026-06-06 07:25:41
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-06 07:25:41
2026-06-06 07:25:48 -07:00
60394a803e sync: auto-sync from Mikes-MacBook-Air.local at 2026-06-06 06:47:07
Author: Mike Swanson
Machine: Mikes-MacBook-Air.local
Timestamp: 2026-06-06 06:47:07
2026-06-06 06:47:08 -07:00
8885f0086d sync: auto-sync from HOWARD-HOME at 2026-06-05 21:51:31
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-05 21:51:31
2026-06-05 21:51:41 -07:00
81e3d885d0 chore: auto-recover 1 unsaved session log(s)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 21:00:55 -07:00
549110584d sync: auto-sync from GURU-5070 at 2026-06-05 20:02:53
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-05 20:02:53
2026-06-05 20:02:59 -07:00
f174d1b7fa feat(sync): best-effort coord visibility signal (git_sync_<machine> component)
sync.sh now posts a per-machine coord component
(claudetools/git_sync_<MACHINE>) flipped syncing -> idle/degraded around
each run, so the fleet can see who is mid-sync / last sync state. Fully
best-effort: a 3s-capped curl guarded with || true + return 0, emitted
only after the lock is acquired (contention/exit-75 emits nothing), and
finalize captures $? first and returns it so the signal can never change
the sync's real exit code. Reviewed (verified it cannot break sync).
2026-06-05 19:39:02 -07:00
353ba6363c refactor(sync): share the sync lock with /scc and /checkpoint
Extract the per-machine concurrency lock from sync.sh into a sourceable
lib (.claude/scripts/sync-lock.sh) plus a `run <cmd>` wrapper that locks
the current repo (same lock-dir basename, so it mutually excludes with
sync.sh in the ClaudeTools repo and self-scopes in any project repo).
sync.sh now sources it (behavior identical — verified by review). /scc
routes its commit+push through the locked, rebase-safe sync.sh (and drops
the bare YYYY-MM-DD-session.md filename for the per-session-unique one).
/checkpoint now stages+commits atomically under the repo lock so a
concurrent session in a shared worktree can't be swept in. Closes the
remaining commit paths that bypassed the lock shipped in 6b0ce9a.
2026-06-05 19:13:40 -07:00
6b0ce9aa04 feat(sync): serialize sync.sh with a per-machine lock; per-session log filenames
Multiple concurrent Claude sessions (and the scheduled-task sync) were
stepping on each other's git state. sync.sh now takes an atomic mkdir
lock in .git/ around the whole run (stage/commit/fetch/rebase/push +
vault), exits 75 (EX_TEMPFAIL = deferred) on contention instead of
racing, and reclaims stale/dead-owner locks with a re-verify-before-clear
guard (closes two TOCTOU races caught in review). /save now mandates
per-session-unique log filenames (never the bare YYYY-MM-DD-session.md).
Docs updated for the lock + deferred-exit semantics.

Note: git add -A is still the catch-all sweep; full per-session commit
isolation and routing /scc + /checkpoint through the lock are follow-ups.
2026-06-05 18:50:52 -07:00
7ff9dbc624 sync: auto-sync from HOWARD-HOME at 2026-06-05 18:26:57
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-05 18:26:57
2026-06-05 18:27:06 -07:00
7a7b4da75e sync: auto-sync from GURU-5070 at 2026-06-05 17:57:59
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-05 17:57:59
2026-06-05 17:58:10 -07:00
2402566782 feat(human-flow): add elevate (polish & redesign) heuristics layer
New `elevate` mode that goes beyond friction to make a UI top-notch and
flags when to redesign rather than patch. references/polish-and-redesign.md
holds 12 heuristics (hierarchy, signature moment, action gravity, narrative,
lonely states, density, rhythm, type, tokens, depth/finish, motion, redesign
triggers) synthesized from three independent model passes (Claude + Gemini +
Grok). Adds an Elevation Index (0-10), a Redesign Urgency score (>=4 leads
with a Structural Audit), and Opportunity-ranked Quick Wins / Elevations /
Redesign Candidates tiers. SKILL.md: command + mode section + extend note.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 17:58:10 -07:00
47496ac432 fix(radio): keyboard a11y — skip link, focus-visible, mobile-menu
human-flow P0-P1 fixes for radio.azcomputerguru.com:
- K1: skip-to-content link (first tab stop) + id/tabindex on <main>.
- K2: global :focus-visible ring (accent outline) across links, buttons,
  inputs and player controls; reveal the seek-bar handle on focus.
- K3: mobile menu a11y — aria-expanded/aria-controls, Escape closes and
  restores focus to the toggle, focus moves to first link on open.
All token-based, no emojis. Not built (node_modules absent on this host).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 17:58:10 -07:00
f98b111193 docs(wiki): full IX server inventory from live SSH
Expand wiki/systems/ix-server.md with a 2026-06-05 live SSH inventory:
- Host: CloudLinux 9.7, cPanel/WHM 134, 64-core Xeon Gold 6130, 62 GiB,
  4.4 T /home; Apache 2.4.67, MariaDB 10.11.16, ea-php 5.6-8.5,
  Exim 4.99.4, Dovecot 2.4.2, BIND 9.16.
- 72 cPanel accounts / 185 domains / 101 WordPress; full account ->
  primary-domain -> disk map (the "where does client X live" reference).
- ACG subdomain docroots (radio, community/Flarum, analytics/Matomo,
  portal, support, etc.) under the azcomputerguru account.
- GuruRMM agent enrolled (gururmm-agent.service).
- Backups appear unconfigured (/backup ~178M vs 1.6T /home) - flagged.
- SSH key auth from GURU-5070 now works; updated reference_ix_server_access
  memory (was stale: claimed key auth not set up) + index summary.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 17:58:10 -07:00
c9b9a3f479 docs(wiki): add IX hosting server system article + radio site infra
- New wiki/systems/ix-server.md: IX web host (172.16.3.10) facts, the
  ACG hosted sites table, and a full record of radio.azcomputerguru.com
  (Astro static + React 19 islands; source in projects/radio-show/website/;
  build npm run build -> dist -> rsync to cPanel doc root).
- index.md: list the new IX systems article.
- radio-show.md: fix the stale "ix-server.md may not exist" backlink.
- memory reference_radio_website.md: add stack detail (React islands,
  wavesurfer/fuse, node>=22) + pointer to the new wiki article.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 17:58:10 -07:00
d4741e447f feat(human-flow): AST-based scanner v2 + Friction Index rubric
Upgrade the human-flow skill (Gemini-assisted, Claude-reviewed):
- scan.mjs rewritten to AST-based (@babel/parser/traverse) with 4
  detectors: unlabeled-icon-button, tiny-target, missing-feedback-props,
  click-without-keyboard; regex fallback on parse failure.
- Objective Friction Index (Motor 3.0 / Cognitive 2.5 / Keyboard 2.5 /
  Feedback 2.0); 0-10 Human Workflow Score.
- New heuristics: State-Flow Audit, Precision Rail / Fumble Zones,
  Restraint-o-Meter (1-5) for the fancy pass.
- `fix` command DISABLED for now (advisory only): the AST generator
  reprints whole files and produces noisy diffs; agents apply surgical
  fixes from the report. To be revisited with a string-splice editor.
- Add @babel/* deps + package-lock.json.
- Memory: agy review/review-files is NOT actually read-only (wrote files
  + ran npm despite documented plan-mode) — diff after every agy review.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 17:58:10 -07:00
bf491354e3 sync: auto-sync from HOWARD-HOME at 2026-06-05 17:35:42
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-05 17:35:42
2026-06-05 17:35:53 -07:00
ec411f44bc docs(skills): document review path-resolution gotcha in agy + grok
review/review-files resolve relative paths only against CWD or
$CLAUDETOOLS_ROOT, never a submodule/subdir — so submodule-relative
paths fail with "file not found". Add a [!WARNING] callout to both
SKILL.md files, fix the misleading "absolute or repo-relative" table
wording, and add inline GOTCHA comments at each resolution site in
both scripts. Bitten us repeatedly (latest: GuruConnect review).
2026-06-05 16:55:56 -07:00
2fcdc5fb13 sync: auto-sync from GURU-5070 at 2026-06-05 16:44:08
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-05 16:44:08
2026-06-05 16:44:18 -07:00
f5bdec125a sync: auto-sync from HOWARD-HOME at 2026-06-05 16:17:06
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-05 16:17:06
2026-06-05 16:17:18 -07:00
fc36218960 sync: auto-sync from GURU-BEAST-ROG at 2026-06-05 15:42:37
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-06-05 15:42:37
2026-06-05 15:42:43 -07:00
fd0b0125e0 sync: auto-sync from GURU-BEAST-ROG at 2026-06-05 15:15:20
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-06-05 15:15:20
2026-06-05 15:15:26 -07:00
528bc9ce2f sync: auto-sync from GURU-5070 at 2026-06-05 15:07:30
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-05 15:07:30
2026-06-05 15:07:37 -07:00
59647ee666 sync: auto-sync from GURU-5070 at 2026-06-05 14:39:29
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-05 14:39:29
2026-06-05 14:39:36 -07:00
1aa9fcecad glaztech: Tom reply #2 (sent) + quo checklist + payroll/TimeForce answer logged
- 2026-06-05-tom-reply2-draft.md (SENT): web-DB rearchitecture ack, CVV-no-paper
  correction, key-backup/escrow guidance, least-priv sync-job note
- 2026-06-05-tom-quo-checklist.txt: clean 80-site quo() list sent to Tom
- session log: TimeForce 2005->2008->2016 payroll chain (load-bearing, preserve)
- guru-rmm submodule pointer -> dashboard redesign doc set (local)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 14:37:26 -07:00
68298c8b70 sync: auto-sync from HOWARD-HOME at 2026-06-05 14:06:17
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-05 14:06:17
2026-06-05 14:06:24 -07:00
3c071069c7 sync: auto-sync from HOWARD-HOME at 2026-06-05 14:04:58
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-05 14:04:58
2026-06-05 14:05:09 -07:00
47b71b7b3a rmm dashboard redesign (Gemini live review) + CDP Chrome driver
- .claude/scripts/cdp.py: drive Chrome via DevTools Protocol; screenshots to disk
  (so Gemini/Grok can see the live site). Fixes invisible-window + no-disk-screenshot.
- reference_cdp_chrome_driver.md (+ MEMORY index)
- gururmm submodule pointer -> dashboard redesign docs (local 3cef6ba)
- session log

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 13:10:37 -07:00
c4ec2ed4b0 memory: Syncro bot alerts must include ticket link
Feedback from Mike (Bardach #32387): every Syncro ticket bot-alert needs a
clickable link (https://computerguru.syncromsp.com/tickets/<internal_id>).
post-bot-alert.sh posts raw text, so the URL must be in the message.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 13:10:37 -07:00
b9474ff286 remediation-tool skill: enforce required Syncro ticket fields (priority, user_id, problem_type)
Adds explicit Syncro ticket creation section to remediation-tool.md.
Ticket #32387 was created without priority, assignee, or a valid issue type.
Now specifies required fields, valid problem_type values, and an enforcement
checklist to prevent null fields in any POST payload.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 12:20:46 -07:00
ef23753956 sync: auto-sync from HOWARD-HOME at 2026-06-05 12:18:49
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-05 12:18:49
2026-06-05 12:18:59 -07:00
08e194f592 bardach: M365 account investigation + Security Defaults MFA enforcement
Investigated barbara@bardach.net login issues (account-locked message, INKY SSL
errors). Finding: active distributed password-spray against the tenant (also
hitting admin@), NOT a breach — no successful attacker sign-in, no mailbox/rule/
forwarding changes. Root exposure: MFA not enforced (no Entra P1 -> no CA).

Remediation (Mike confirmed): enabled Security Defaults tenant-wide. Both active
accounts MFA-ready (Authenticator) -> no lockout; legacy auth now blocked.

- 2026-06-05-account-investigation-mfa-enforcement.md (full report)
- 2026-06-05-barbara-note-draft.md (client note, for Mike to send)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 11:52:46 -07:00
51b3d799f5 scc: Session save and push from GURU-5070 at 2026-06-05 10:35
glaztech: :3436 backup-job recon + Tom's architectural reply; session log update.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 11:35:16 -07:00
185a329770 glaztech: commit final Tom message + quo() fix-list
- 2026-06-05-tom-message-draft.md: Mike's final relief-framed wording
- 2026-06-05-quo-sql-fix-list.md: 80 live quo call sites across 15 files (C3)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 11:35:16 -07:00
9e98ca00cf sync: auto-sync from HOWARD-HOME at 2026-06-05 11:21:47
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-05 11:21:47
2026-06-05 11:21:58 -07:00
a8abe4a14b glaztech: staged-remediation pacing strategy + Steve approval + softened Tom message
Adds the "from emergency to deliberate staged objectives" pacing strategy
(severity unchanged, tempo deliberate - the depth of the Glaz tools estate makes
rushing the bigger risk) and records Steve's blanket approval (Tier A
execution-cleared). Softens the Tom outreach to a partnership / not-a-fire-drill
tone per Mike.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 10:40:14 -07:00
e18792ecf7 sync: auto-sync from HOWARD-HOME at 2026-06-05 10:26:08
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-05 10:26:08
2026-06-05 10:26:21 -07:00
21043d42bd glaztech: minimal-Tom remediation path (v0.2) + Tom outreach draft
Grok + Gemini consensus reframe of the way forward: ACG-owned containment
(E-bucket, DB de-privilege, WAF, SQL network segmentation) is the real C0
reduction; the audience/network split is real only for the employee surface.
Tom's one within-skill ask = parameterize the 59 quo() SQL queries (ACG hands
him the exact lines); tokenized payments is a deferred scaffolded sub-project.
Steve Eastman gave ACG blanket approval to proceed (Tier A execution-cleared).
Includes a relief-framed draft message to Tom.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 10:18:55 -07:00
1e957fa922 glaztech: least-privilege tom DB migration scope + 2026-06-05 session log
Scope (v0.3) for replacing the website's sysadmin login 'tom' with a
least-privilege login: two-phase plan (GTIware co-residency forces keeping
cc_file in Phase 1), Grok + Gemini independent review folded in, and live
RMM recon findings that materially changed the picture - the website is a
cross-office + Sage accounting + payroll + msdb hub on one sysadmin
credential, SQL is centralized on GTI-INV-SQL\GTISQL:3436 (not per-site).
PARKED pending a full network recon. Session log covers the website outage
fix (incomplete E1 ACL hardening) + the scoping + recon.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 10:01:18 -07:00
ac0106f254 feat(agy): add keyless image-analyze + search modes
image-analyze: independent second-model vision over OAuth (pins the
gemini-3.1-pro-preview vision model; the default flash-lite router
hallucinates image content) — reads an image via read_file and describes it.
search: Google-grounded live web results with citation URLs (google_web_search).
Both verified working on the keyless Google OAuth. Image GENERATION
(nano-banana) still needs an AI Studio key + extension and stays Grok's lane.
Includes a scoped best-effort output sanitizer for image-analyze (preview
model occasionally leaks reasoning tokens); text/verify/review/search
unchanged. migrate-identity.sh now upgrades the gemini capabilities array.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 09:03:21 -07:00
2d409a4e7a fix(grok): self-healing embed fallback for review modes
If a grok read_file-based review (review/review-files/review-diff) returns
empty (the 0.2.20-style headless tool-gating regression), retry once with
the file(s)/diff embedded inline via the no-tools text path, when content
is under 256KB; otherwise emit a clear skip note. Keeps grok-reads-files as
the default happy path (works on 0.2.22) and degrades gracefully instead of
returning silence. text/verify/raw unchanged; Windows path handling intact.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 08:32:28 -07:00
90e2cb2dd7 sync: auto-sync from GURU-5070 at 2026-06-05 08:06:47
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-05 08:06:47
2026-06-05 08:06:54 -07:00
ce9744832d feat(skills): add /mailprotector — CloudFilter held-mail search + release
Live Mailprotector CloudFilter REST client (emailservice.io/api/v1,
Bearer auth via vault msp-tools/mailprotector.sops.yaml). Lists mail-flow
logs and held/quarantined messages across client domains and releases them
(POST messages/{id}/deliver, deliver_many). Read-only by default; every
release/rule-add/config-change gated behind --confirm. Mirrors the
packetdial skill pattern. Built after diagnosing a Dataforth held-outbound
message that never reached ACG.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 07:03:47 -07:00
bf58675142 fix(remediation): URL-encode role_assigned() Graph $filter
role_assigned() sent an unencoded space in the OData $filter
(principalId eq '...'), so the query always failed and the function
always returned false -> onboard-tenant.sh always printed
"MISSING -> ASSIGNING" and relied on the conflict-tolerant POST for
idempotency. Fixed to %20; corrected the stale PIM-misdiagnosis comment.
Verified live against the ACG tenant. Roles still assign correctly;
PRESENT/MISSING reporting is now accurate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 07:03:27 -07:00
2cd0c3ddd0 feat(skills): add AGY — Google Gemini CLI second-opinion router
Sibling of the grok skill: routes text/verify/review (+ review-files,
review-diff, raw) to the official Google Gemini CLI (gemini, npm global,
v0.45.1) for an independent second model. ask-gemini.sh mirrors ask-grok.sh
(identity-aware gating, binary auto-locate, cygpath hardening, prompt-file
inputs, clean stdout/stderr separation, JSON .response extraction). review
modes copy targets into a temp dir + --include-directories to bypass
Gemini's gitignore/workspace sandbox. verify/review pinned to
gemini-3.1-pro-preview (GEMINI_MODEL overridable). migrate-identity.sh
auto-detects gemini and writes a per-machine identity.json gemini block.
Auth: Google OAuth (no key). Fleet Gemini host: GURU-5070.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 06:45:00 -07:00
a87cb66b32 sync: auto-sync from HOWARD-HOME at 2026-06-04 21:22:05
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-04 21:22:05
2026-06-04 21:22:16 -07:00
4ab272faab grok skill: cygpath path-hardening + review-files/review-diff modes
Fixes the two Windows pain points when routing code review to the Grok CLI
(native Windows grok.exe driven from Git Bash):

- winpath() (cygpath -w; no-op off Windows) on every path handed to grok.exe
  (--prompt-file, --cwd) -> deterministic, space-safe; removes reliance on
  MSYS's argv auto-conversion heuristic (the 'confounded by Windows paths').
- review mode resolves to an absolute Windows path (handles absolute/spaced paths).
- NEW review-files [-i instr] <f1> [f2...]: review a set of files together.
- NEW review-diff [-C <repo-dir>] [-i instr] <gitref> [-- <pathspec>]: review a
  git diff; -C targets submodules (e.g. guru-rmm). Diff goes via --prompt-file,
  not a shell arg -> no 'quote hell'.

Tested: text, review (spaced abs path), review-files (2 tray modules),
review-diff (self-review of these changes). SKILL.md updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 20:45:48 -07:00
fdec4b7772 sync: auto-sync from GURU-5070 at 2026-06-04 19:33:04
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-04 19:33:04
2026-06-04 19:33:08 -07:00
b93c9d9e94 sync: auto-sync from GURU-5070 at 2026-06-04 19:29:23
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-04 19:29:23
2026-06-04 19:29:28 -07:00
8389e64a02 sync: auto-sync from GURU-5070 at 2026-06-04 19:27:51
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-04 19:27:51
2026-06-04 19:27:56 -07:00
e08488ae5e sync: auto-sync from GURU-5070 at 2026-06-04 19:08:11
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-04 19:08:11
2026-06-04 19:08:18 -07:00
e95fa07cfe chore: auto-recover 1 unsaved session log(s)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 19:08:18 -07:00
1036 changed files with 434909 additions and 4519 deletions

View File

@@ -1,351 +1,85 @@
# ClaudeTools Project Context
# ClaudeTools — Core Operating Rules
## Multi-User Environment (CHECK FIRST)
> Lean CORE, always loaded. The FULL manual — onboarding steps, work-mode detail, the
> coordination-API protocol, project/command/reference tables, Ollama/GrepAI, vault detail
> — is in **`.claude/CLAUDE_EXTENDED.md`**. Read EXTENDED when: onboarding a new machine,
> switching work modes, using the coord API (locks/messages/todos), provisioning, or
> unsure about any workflow. Harness version: `.claude/harness/VERSION`.
This repo is shared across multiple team members. **At every session start, BEFORE doing anything else:**
## Identity & multi-user (check first)
Shared repo across the team. At session start read `.claude/identity.json` (gitignored,
per-machine) and greet by name. If it is **missing** (new machine) → run the onboarding
flow in EXTENDED before other work. Team: **Mike Swanson** (admin/owner), **Howard Enos**
(tech, full trust — same access). Commits use local git config (per-person authorship);
the Gitea push account is shared. Every session log needs a `## User` block (use
`.claude/scripts/whoami-block.sh`).
1. **Read `.claude/identity.json`** (local, gitignored). If it exists, greet the user by name and proceed.
2. **If identity.json does NOT exist** (first sync on a new machine):
- Read `.claude/users.json` for the known user list
- Ask: "This looks like a new machine. Are you **Mike Swanson** or **Howard Enos**? (Or someone new?)"
- Based on their answer, create `.claude/identity.json`:
```json
{
"user": "mike",
"full_name": "Mike Swanson",
"email": "mike@azcomputerguru.com",
"role": "admin",
"machine": "<HOSTNAME>",
"vault_path": "<absolute path to vault repo on this machine>",
"claudetools_root": "<absolute path to ClaudeTools repo on this machine>"
}
```
Ask the user where the vault repo is cloned (e.g., `D:/vault`, `~/vault`, `/Users/howard/vault`) and where ClaudeTools is cloned (e.g., `D:/claudetools`, `~/ClaudeTools`, `/Users/mike/ClaudeTools`).
- Set local git config: `git config user.name "<full_name>"` and `git config user.email "<email>"`
- Set git remote (read `gitea_username` from users.json): `git remote set-url origin https://<gitea_username>@git.azcomputerguru.com/azcomputerguru/claudetools.git`
- Add hostname to user's `known_machines` in users.json and commit.
- Run `.claude/scripts/migrate-identity.sh` to populate machine-specific config (ollama, python, platform, architecture).
- **Show the user `.claude/ONBOARDING.md`** — present section by section, explain the WHY, answer questions.
3. **If hostname doesn't match any known machine** for the identified user, update their `known_machines` in users.json.
## How you work — act directly, delegate deliberately
You are the main operator. **ACT DIRECTLY by default.** Delegate to a sub-agent ONLY when:
(a) the task produces high-volume tool output, (b) blast radius >3 files across layers,
(c) a genuine domain shift needs a specialized agent, or (d) independent work can run in
parallel. Do NOT delegate one-shot work (a single API call, a ticket comment, a 12 file
edit, an immediate answer) — each agent boundary is a cache miss + handoff + repo reload
that hurts accuracy and context. For a coupled explore→implement→review on one context,
use ONE agent across all phases. Agent defs: `.claude/agents/`.
### Session Log Attribution
## Model routing
Tier 0 Ollama (low-stakes prose/classify, output reviewed) · Tier 1 `haiku` · Tier 2
inherit (most code/db/test/git) · Tier 3 `opus` (architecture, security, ambiguous
failures, production risk). Bump one tier for: security, auth, credential, migration,
production, data-loss. Detail: EXTENDED + `.claude/OLLAMA.md`.
Every session log MUST include a `## User` section:
```markdown
## User
- **User:** Mike Swanson (mike)
- **Machine:** DESKTOP-0O8A1RL
- **Role:** admin
```
## Key rules (always)
- **NO EMOJIS.** Use ASCII markers: `[OK]` `[ERROR]` `[WARNING]` `[INFO]` `[CRITICAL]`.
- **Credentials — capture, vault, document (ALWAYS).** ANY credential that surfaces in a
session — one the user pastes, one you create/rotate, one you discover in a log/config — you
MUST immediately store it in the SOPS vault **via the `vault` skill** (the canonical path —
this is why the vault exists; do not improvise raw `sops`/`vault.sh`) AND document it
thoroughly in the entry: what it is, what it's for, and exactly how it's used (auth method,
endpoint, gotchas). Read with the skill too; `vault.sh get-field <path> <field>` is the
underlying read (1Password fallback). Never commit plaintext secrets (pre-commit
`harness-guard.sh` warns). Losing/forgetting infra credentials wastes real time — capturing
them is not optional.
- **SSH:** system OpenSSH (`C:\Windows\System32\OpenSSH\ssh.exe`), never Git-for-Windows SSH.
- **Data integrity:** never placeholder/fake data — check vault, wiki, or ask.
- **Hard-to-reverse or outward-facing actions:** confirm first (per-action, per-session).
- **Error logging (mandatory, all skills):** when a task or skill hits a GENUINE functional error during execution (failed command, API/auth failure, unexpected API response, tool call), record it to `errorlog.md` (repo root) via the canonical helper — never hand-format: `bash .claude/scripts/log-skill-error.sh "<skill/command>" "<brief error>" [--context "k=v ..."]`. It stamps UTC date + machine (from `identity.json`) and inserts in the standard `YYYY-MM-DD | MACHINE | skill | error` format (newest on top) for later skill **linting**, and soft-fails so it never breaks the caller. **Every skill MUST call it at its failure branches**; you (main loop) call it after any skill/command genuinely fails. Do NOT log expected/handled conditions (no search matches, no unread messages, a user declining a prompt) — only real failures worth spotting a pattern. Python skills shell out to the same helper.
- **Log user corrections too (`--correction`):** when the user CORRECTS an improper assumption or wrong approach you took (e.g. "don't use INKY unless onboarded", "EXO already has Mail.Send", "I don't need an exact match"), log it: `bash .claude/scripts/log-skill-error.sh "<skill/context>" "assumed X; correct is Y" --correction`. These are the highest-value entries — they surface recurring bad assumptions so we can train them out of the system. If the correction is a durable preference, ALSO save it as a `feedback` memory (the two are complementary: memory fixes future behavior, errorlog tracks the pattern for linting).
- **Log preventable friction too (`--friction`):** any time you waste tokens on a preventable, repeatable self-inflicted error — harness/env/tool misuse (Git-Bash `/tmp` path mismatch, shell env not persisting between Bash calls, passing huge args on the command line, PowerShell var case-collisions, etc.) — log it: `bash .claude/scripts/log-skill-error.sh "<context e.g. bash/env>" "what wasted tokens + the fix" --friction [--context "ref=<memory-or-rule>"]`. **If it repeats something already in memory or CLAUDE.md, that's the highest-value entry** — it means a rule/memory isn't working; cite the ref. This log is the corpus we lint to build better CLAUDE.md rules and to clean stale/misleading memory. Goal: stop paying twice for the same mistake.
- **Windows:** ensure `bash` resolves to Git-for-Windows MSYS bash, not the WSL stub; write
`.claude/current-mode` with a relative/forward-slash path only (never a backslash Windows
path). Detail + fixes: EXTENDED.
Commits use local git config (user.name / user.email). Gitea push account is shared (azcomputerguru) but commit authorship tracks the actual person.
## Coordination (live source of truth)
The coord API (`http://172.16.3.30:8001/api/coord`, no auth) holds live locks, messages,
todos, component state. **If a `system-reminder` contains "UNREAD COORD MESSAGES", you MUST
reproduce the full message block verbatim at the top of your response before anything else**
— the user cannot see system-reminders. Session-start checks, locks, inter-session
messaging, todos, softfail queue: EXTENDED (and the `coord` skill).
### Current Team
## Context loading (don't ask for what's recorded)
Before responding, load context when a trigger fires — a client/project/system/server is
named, or the user says continue/resume/back-to/finish: read **`wiki/`** FIRST (synthesized
knowledge; index `wiki/index.md`), then the relevant `CONTEXT.md` / session logs, then the
coord API. Never ask for infra or recent-work facts that live in the wiki or `CONTEXT.md`.
Full trigger table + recovery: EXTENDED; the `/context` command.
| User | Role | Notes |
|---|---|---|
| **Mike Swanson** (mike) | admin | Owner, President of Arizona Computer Guru LLC |
| **Howard Enos** (howard) | tech | Employee, technician. Full trust — same access as admin. |
## Work modes
Auto-detect mode (remediation / client / infra / dev / general) from each message. On
change: announce `[MODE -> x]`, tell the user to run `/color <c>`, and write the mode to
`.claude/current-mode`. Mode postures + triggers: EXTENDED.
## Memory & knowledge layers
Shared memory in `.claude/memory/` (index `MEMORY.md`, loaded each session) — write here
(repo-relative), NEVER `~/.claude/projects/*/memory/`. Wiki = synthesized truth (on-demand);
session-logs = archive; memory = small ephemeral facts + harness quirks. Save user
facts/feedback/project/reference per the memory format; one fact per file + an index line.
## RMM Thoughts
GuruRMM ideas from Mike/Howard go to `projects/msp-tools/guru-rmm/docs/RMM_THOUGHTS.md`
(Status: Raw) → discuss → `/shape-spec` → roadmap → build. Don't build until an explicit go.
`/feature-request` captures Howard's requests there.
---
## Work Mode
Auto-detect on every user message (first match wins):
| Mode | Triggers | Posture |
|------|----------|---------|
| **remediation** | "remediation tool", "365", "breach", "tenant sweep", M365 keywords | Graph API focus, compliance language, full audit trail |
| **client** | client name, `clients/` work, "for \<client\>" | Careful with data, session logs in `clients/`, name the client |
| **infra** | server names/IPs, SSH, firewall, DNS, deploy, service restart | Confirm before destructive ops, backup-first |
| **dev** | code, build, Rust/cargo, npm, GuruRMM dev, `projects/` work | Delegate freely, less confirmation friction |
| **general** | default | Lightweight |
On mode change: announce `[MODE -> infra]`, tell user to run `/color <color>`. Full details: `.claude/commands/mode.md`
**MANDATORY on every mode change:** write the new mode to `.claude/current-mode` so hooks can read it:
```bash
echo dev > .claude/current-mode # substitute the actual mode name
```
This file is gitignored (machine-local). The `UserPromptSubmit` hook reads it to gate the lock check on dev mode.
**Windows/Git Bash:** always use the relative path above (or forward slashes — `/d/claudetools/.claude/current-mode`). NEVER a backslashed Windows path like `D:\claudetools\.claude\current-mode`: Git Bash strips the backslashes and substitutes the illegal `:` with a Unicode PUA char, creating a garbled junk file instead of writing the path. A `PreToolUse(Bash)` hook (`.claude/hooks/block-backslash-winpath.sh`) blocks such redirects; `sync.sh` also strips any that slip through before staging.
**Windows bash command (the `bash` executable):** In PowerShell contexts (including the Grok/Claude tool run_terminal_command), `bash` often resolves to the WSL stub (`WindowsApps\bash.exe`) instead of the required Git for Windows/MSYS bash. This breaks vault.sh, sync.sh, hooks, etc.
Fix (idempotent):
```powershell
$gitBin = "C:\Program Files\Git\bin"
$gitUsrBin = "C:\Program Files\Git\usr\bin"
if ((Test-Path $gitBin) -and ((Get-Command bash -ErrorAction SilentlyContinue).Source -notlike '*Git*bin*bash.exe')) {
$env:Path = "$gitBin;$gitUsrBin;" + ($env:Path -replace [regex]::Escape("$gitBin;"), '' -replace [regex]::Escape("$gitUsrBin;"), '')
}
```
Then plain `bash .claude/scripts/vault.sh ...` works and shows the MSYS version.
Project helper: `. .claude/scripts/ensure-git-bash.ps1` (see that file + `.claude/memory/feedback_windows_bash_mapping.md`).
The user's PowerShell `$PROFILE` auto-applies the remap on new sessions. For critical calls, prefer the full path `"C:\Program Files\Git\bin\bash.exe" .claude/scripts/...` if env is uncertain. Git Bash terminals (direct launch) are already correct. Related: always use system OpenSSH, not Git's.
**Auto-initialization:** If `.claude/current-mode` is missing (e.g., fresh clone), the UserPromptSubmit hook automatically creates it with "general" as the default mode. No manual setup required.
---
## Identity: You Are a Coordinator
You are NOT an executor. You coordinate specialized agents and preserve your context window.
**Delegate ALL significant work:**
| Operation | Delegate To |
|-----------|------------|
| Database queries/inserts/updates | Database Agent |
| Production code generation | Coding Agent |
| Code review (MANDATORY after changes) | Code Review Agent |
| Test execution | Testing Agent |
| Git commits/push/branch | Gitea Agent |
| Backups/restore | Backup Agent |
| File exploration (broad) | Explore Agent |
| Semantic code search | deep-explore Agent (uses GrepAI) |
| Complex reasoning | General-purpose + Sequential Thinking |
**Do yourself:** Simple responses, reading 1-2 files, presenting results, planning, decisions.
**Rule:** >500 tokens of work = delegate. Code or database = ALWAYS delegate.
**DO NOT** query databases directly. **DO NOT** write production code. **DO NOT** run tests. **DO NOT** commit/push.
**Single-agent for coupled tasks:** For explore → implement or explore → implement → review flows where the context is the same throughout, use one agent across all phases rather than spawning three. Each agent boundary is a cache miss and a context-handoff cost. Spawn separate agents only when tasks are genuinely independent or run in parallel.
### Model Routing (Complexity-Based)
| Tier | Model | When |
|------|-------|------|
| 0 | **Ollama** (local) | Low-stakes: summarize, classify, extract, draft — no code changes, output reviewed before use |
| 1 | `haiku` | Ollama unavailable, or task needs agent tool use / file access |
| 2 | (inherit) | Standard code, DB, tests, git — most work |
| 3 | `opus` | Architecture, security, ambiguous failures, production risk |
**Bump rule:** if the request involves `security`, `auth`, `credential`, `migration`, `production`, or `data loss` — bump one tier up.
Pass `model: "haiku"` or `model: "opus"` explicitly. Omit for Tier 2. Tier 0 is a direct Bash call — see `.claude/OLLAMA.md`.
---
## Automatic Context Loading (CRITICAL)
Load context **before responding** when any trigger fires. Never ask for info that's already in CONTEXT.md.
| Trigger | Action |
|---------|--------|
| Client name mentioned | Read `wiki/clients/<slug>.md` FIRST, then `clients/<name>/session-logs/` for recent detail |
| GuruRMM / Dataforth / project keywords | Read `wiki/projects/<slug>.md` FIRST, then `projects/<project>/CONTEXT.md`, query coord API status + components |
| Server/hostname/IP mentioned | Read `wiki/systems/<slug>.md` FIRST for synthesized knowledge |
| "continue", "resume", "back to", "finish" | Read project wiki article + CONTEXT.md, check coord API for locks + unread messages |
| Servers, IPs, credentials, deploy questions | Check wiki/systems first, then CONTEXT.md — answer from it, never ask |
| Uncertainty >5% about infra or recent work | Check wiki first, then CONTEXT.md before asking the user |
CONTEXT.md locations: `projects/msp-tools/guru-rmm/CONTEXT.md`, `projects/dataforth-dos/CONTEXT.md`, `CONTEXT.md` (root).
Wiki location: `wiki/` (root) — `wiki/clients/`, `wiki/projects/`, `wiki/systems/`, `wiki/patterns/`. Index: `wiki/index.md`.
---
## Projects
**ClaudeTools** — MSP Work Tracking System (Production-Ready)
- Database: MariaDB 10.6.22 @ 172.16.3.30:3306 | API: http://172.16.3.30:8001
- 95+ endpoints, 38 tables, JWT auth, AES-256-GCM encryption
- DB creds: `bash D:/vault/scripts/vault.sh get-field projects/claudetools/database.sops.yaml credentials.password`
**GuruRMM** — Remote Monitoring & Management (Active Development)
- Server: Rust/Axum @ 172.16.3.30:3001 | Dashboard: https://rmm.azcomputerguru.com
- Repo: `azcomputerguru/gururmm` on Gitea (active) — the `projects/msp-tools/guru-rmm/` submodule tracks it. A separate Gitea repo named `guru-rmm` (hyphenated) is an abandoned duplicate; ignore it.
- Roadmap: `projects/msp-tools/guru-rmm/docs/FEATURE_ROADMAP.md` (also `docs/UI_GAPS.md`)
---
## Key Rules
- **Coord messages in system-reminder:** If a `system-reminder` contains "UNREAD COORD MESSAGES", you MUST reproduce the full message block verbatim at the top of your response before addressing anything else. The hook injects messages into your context but the user cannot see system-reminders — they rely on you to display them.
- **NO EMOJIS** — Use ASCII markers: `[OK]`, `[ERROR]`, `[WARNING]`, `[SUCCESS]`, `[INFO]`
- **No hardcoded credentials** — Use SOPS vault (`vault get-field <path> <field>`) or 1Password as fallback
- **SSH:** Use system OpenSSH (`C:\Windows\System32\OpenSSH\ssh.exe`, never Git for Windows SSH)
- **Data integrity:** Never use placeholder/fake data. Check SOPS vault, credentials.md, or ask user.
- **Coding standards:** `.claude/CODING_GUIDELINES.md` (agents read on-demand)
---
## Live State Tracking (ALL Projects)
**Coord API is the live source of truth.** API base: `http://172.16.3.30:8001/api/coord` (no auth).
### Session start
```bash
curl -s "http://172.16.3.30:8001/api/coord/messages?to_session=<SESSION_ID>&unread_only=true"
curl -s "http://172.16.3.30:8001/api/coord/status"
curl -s "http://172.16.3.30:8001/api/coord/locks?project_key=<KEY>"
```
Display unread messages before any work. Mark read: `PUT /api/coord/messages/<id>/read`
### Before significant work — claim a lock
```bash
curl -s -X POST http://172.16.3.30:8001/api/coord/locks \
-H "Content-Type: application/json" \
-d '{"project_key":"gururmm","session_id":"DESKTOP-0O8A1RL/claude-main","resource":"server/src","description":"...","ttl_hours":2}'
```
### After work — release lock + update component
```bash
curl -s -X DELETE "http://172.16.3.30:8001/api/coord/locks/<id>?session_id=<SESSION_ID>"
curl -s -X PUT "http://172.16.3.30:8001/api/coord/components/gururmm/server" \
-H "Content-Type: application/json" \
-d '{"state":"deployed","version":"0.3.0","notes":"...","updated_by":"DESKTOP-0O8A1RL/claude-main"}'
```
**Softfail:** If API unreachable, continue work and log failed calls to `.claude/coord-queue.jsonl`. Drain on next `/sync`.
### Project keys
| project_key | Components | States |
|-------------|------------|--------|
| `gururmm` | `server`, `agents`, `dashboard`, `db_migrations` | `building`, `built`, `deploying`, `deployed`, `degraded` |
| `guruconnect` | `server`, `agent`, `dashboard` | `building`, `built`, `deploying`, `deployed`, `degraded` |
| `claudetools` | `api`, `db_migrations`, `coord_api` | `deploying`, `deployed`, `degraded` |
| `dataforth-dos` | `app`, `db` | `active`, `idle`, `degraded` |
| `clients/<name>` | `(free-form)` | `(free-form)` |
Full protocol + inter-session messaging: `.claude/COORDINATION_PROTOCOL.md`
---
## Automatic Behaviors
- **Frontend Design:** Auto-invoke `/frontend-design` skill after ANY UI change (HTML/CSS/JSX/styling)
- **Sequential Thinking:** Use for genuine complexity — rejection loops, 3+ critical issues, architectural decisions
- **Task Management:** Complex work (>3 steps) → TaskCreate. Persist to `.claude/active-tasks.json`.
- **Auto Todo Creation:** When wrapping up a task that has unresolved follow-up, open items, or deferred work, POST to `POST /api/coord/todos` with `auto_created: true` and `source_context` describing why. Assign `project_key` if project-scoped; assign `assigned_to_user` if only relevant to one tech. Sub-tasks: set `parent_id` to link under a parent todo. Never create a todo for something already being done in the current session.
### Querying Todos
- "What needs to be done with \<project\>?" → `GET /api/coord/todos?project_key=<key>&status_filter=pending`
- "What are my open todos?" → `GET /api/coord/todos?for_user=<user>&status_filter=pending`
- "Show all todos including done" → add `status_filter=all`
- "Mark done" → `PUT /api/coord/todos/<id>` with `{"status": "done", "completed_by": "<user>"}`
### Cross-Session Messages (MANDATORY)
See the **Session Start Protocol** in "Live State Tracking" above. Messages must be displayed and marked read before any other work.
Also scan session logs pulled during `/sync` for legacy `## Note for <user>` sections (transitional — older sessions still use markdown).
---
## Context Recovery
When user references previous work, use `/context` command. Never ask for info in:
- `wiki/` — **Check first.** LLM-compiled synthesized knowledge by client/project/system. Index: `wiki/index.md`
- `credentials.md` — Infrastructure reference (being migrated to SOPS vault)
- `session-logs/` — Daily work logs (also in `projects/*/session-logs/` and `clients/*/session-logs/`)
- **Coordination API** — current locks, component states, workflows, messages: `GET http://172.16.3.30:8001/api/coord/status`
- `projects/*/PROJECT_STATE.md` — ARCHIVED. Read-only historical reference. Do not edit. Use coordination API for live state.
### Credential Access (SOPS Vault)
Use the ClaudeTools vault wrapper — never hardcode the vault path:
```bash
# CLAUDETOOLS_ROOT is the repo root (D:\claudetools on Windows, ~/claudetools on Mac/Linux)
VAULT="$CLAUDETOOLS_ROOT/.claude/scripts/vault.sh"
bash "$VAULT" search "keyword" # Search without decrypting
bash "$VAULT" get-field <path> <field> # Get specific field
bash "$VAULT" get <path> # Decrypt full entry
bash "$VAULT" list # List all entries
```
The wrapper reads `vault_path` from `.claude/identity.json` (per-machine, gitignored).
Each machine sets its own vault path there — no hardcoded paths in any shared file.
Vault structure: `infrastructure/`, `clients/`, `services/`, `projects/`, `msp-tools/`
**1Password fallback:** service account token in `infrastructure/1password-service-account.sops.yaml`
---
## Commands & Skills
| Command | Purpose |
|---------|---------|
| `/checkpoint` | Dual checkpoint: git commit + database context |
| `/save` | Comprehensive session log |
| `/context` | Search wiki first, then session logs, credentials.md, and 1Password |
| `/wiki-compile` | Compile session logs into wiki articles for a client/project/system/all |
| `/wiki-lint` | Health-check wiki for stale IPs, broken backlinks, orphaned articles |
| `/1password` | 1Password secrets management |
| `/sync` | Sync config from Gitea repository |
| `/create-spec` | Create app specification for AutoCoder |
| `/frontend-design` | Modern frontend design (auto-invoke after UI changes) |
| `/rmm` | Remote command execution on GuruRMM agents — list, run, poll, cancel |
| `/remediation-tool` | M365 breach checks, tenant sweeps, gated remediation |
| `/feature-request` | Howard submits a GuruRMM feature request — Claude classifies it and messages Mike |
| `/shape-spec` | Pre-implementation spec for a GuruRMM feature — produces plan.md, shape.md, references.md, standards.md |
| `/rmm-audit` | Full end-to-end audit of GuruRMM: API coverage, UI gaps, Rust/TS quality, security, data integrity. Produces timestamped report + updates UI_GAPS.md |
| `/forum-post` | Post a technical article to community.azcomputerguru.com — drafts from context, shows preview, inserts via paramiko SSH to Flarum DB |
| `/recover` | Reconstruct a session log from a Claude Code transcript after a crash/close-before-save. `/recover <uuid>`, `/recover latest`, or `/recover --list`. See `.claude/RECOVERY.md` |
---
## File Placement
- GuruRMM work → `projects/msp-tools/guru-rmm/` (git submodule tracking the **active** `azcomputerguru/gururmm` repo; the pinned commit normally lags `main` — that's expected, not "stale"). Empty on a fresh clone until `git submodule update --init`; `/sync` now does this automatically.
- GuruRMM session logs → root `session-logs/` (NOT the submodule)
- Client work → `clients/[client-name]/`
- Session logs → project/client `session-logs/` subfolder; general work → root `session-logs/`
- Full guide: `.claude/FILE_PLACEMENT_GUIDE.md`
---
## Local AI (Ollama)
Tier 0 — **Ollama is the documentation and classification engine.** Route prose, summaries, and classification through it; Claude reviews before writing or posting.
**Models:** `qwen3.6:latest` (structured: JSON, classification), `qwen3:8b` / `qwen3:14b` (prose), `codestral:22b` (code suggestions).
**Configuration:** All machine-specific config (endpoint, fallback, prose_model, python command, platform, architecture) lives in `.claude/identity.json`, populated by `.claude/scripts/migrate-identity.sh`. Scripts read `.ollama.endpoint` directly — no curl probing.
**Reference:** `.claude/OLLAMA.md` for full model usage + routing patterns.
### GrepAI (Semantic Code Search)
**Use GrepAI first for any context lookup before reading files directly.** It indexes all session logs, skill files, and project docs with boosted relevance for `.claude/` and `session-logs/`.
- **When to use:** "what did we do with X", "how does Y work", "find where Z is configured", context recovery, exploring unfamiliar code
- **MCP tools:** `grepai_search` (primary), `grepai_trace_callers`, `grepai_trace_callees`
- **Agent:** `deep-explore` (for multi-hop exploration)
- **CLI:** `$CLAUDETOOLS_ROOT/grepai search "query" --json -c -n 5`
- **Watcher:** runs as scheduled task "GrepAI Watcher - claudetools" (auto-starts on login, keeps index current)
---
## Memory (Shared Across Machines)
Stored in-repo at `.claude/memory/` — syncs via Gitea to all workstations.
Index: `.claude/memory/MEMORY.md`
**IMPORTANT:** Always write to `.claude/memory/` (repo-relative), NOT `~/.claude/projects/*/memory/`.
---
## Reference (read on-demand)
- **Fleet machine specs + onboarding checklist:** `.claude/machines/` (per-host `<hostname>.md`, plus `LINUX_PC_ONBOARDING.md`)
- **Project structure, endpoints, workflows:** `.claude/REFERENCE.md`
- **Agent definitions:** `.claude/agents/*.md`
- **MCP servers:** `MCP_SERVERS.md`
- **Coding standards:** `.claude/CODING_GUIDELINES.md`
- **Ollama connection + examples:** `.claude/OLLAMA.md`
- **PROJECT_STATE locking protocol:** `.claude/PROJECT_STATE_PROTOCOL.md`
- **Temp directory graduation workflow:** `.claude/TEMP_GRADUATION.md`
---
**Last Updated:** 2026-05-29
Projects, commands table, file-placement guide, full coord protocol, onboarding, Ollama,
GrepAI, and every detailed workflow: **`.claude/CLAUDE_EXTENDED.md`**.

371
.claude/CLAUDE_EXTENDED.md Normal file
View File

@@ -0,0 +1,371 @@
# ClaudeTools — Extended Operating Manual
> Full reference. The lean always-loaded CORE is `.claude/CLAUDE.md`. Read this when
> onboarding, switching modes, using the coord API, or unsure about a workflow.
---
# ClaudeTools Project Context
## Multi-User Environment (CHECK FIRST)
This repo is shared across multiple team members. **At every session start, BEFORE doing anything else:**
1. **Read `.claude/identity.json`** (local, gitignored). If it exists, greet the user by name and proceed.
2. **If identity.json does NOT exist** (first sync on a new machine):
- Read `.claude/users.json` for the known user list
- Ask: "This looks like a new machine. Are you **Mike Swanson** or **Howard Enos**? (Or someone new?)"
- Based on their answer, create `.claude/identity.json`:
```json
{
"user": "mike",
"full_name": "Mike Swanson",
"email": "mike@azcomputerguru.com",
"role": "admin",
"machine": "<HOSTNAME>",
"vault_path": "<absolute path to vault repo on this machine>",
"claudetools_root": "<absolute path to ClaudeTools repo on this machine>"
}
```
Ask the user where the vault repo is cloned (e.g., `D:/vault`, `~/vault`, `/Users/howard/vault`) and where ClaudeTools is cloned (e.g., `D:/claudetools`, `~/ClaudeTools`, `/Users/mike/ClaudeTools`).
- Set local git config: `git config user.name "<full_name>"` and `git config user.email "<email>"`
- Set git remote (read `gitea_username` from users.json): `git remote set-url origin https://<gitea_username>@git.azcomputerguru.com/azcomputerguru/claudetools.git`
- Add hostname to user's `known_machines` in users.json and commit.
- Run `.claude/scripts/migrate-identity.sh` to populate machine-specific config (ollama, python, platform, architecture).
- **Show the user `.claude/ONBOARDING.md`** — present section by section, explain the WHY, answer questions.
3. **If hostname doesn't match any known machine** for the identified user, update their `known_machines` in users.json.
### Session Log Attribution
Every session log MUST include a `## User` section:
```markdown
## User
- **User:** Mike Swanson (mike)
- **Machine:** DESKTOP-0O8A1RL
- **Role:** admin
```
Commits use local git config (user.name / user.email). Gitea push account is shared (azcomputerguru) but commit authorship tracks the actual person.
### Current Team
| User | Role | Notes |
|---|---|---|
| **Mike Swanson** (mike) | admin | Owner, President of Arizona Computer Guru LLC |
| **Howard Enos** (howard) | tech | Employee, technician. Full trust — same access as admin. |
---
## Work Mode
Auto-detect on every user message (first match wins):
| Mode | Triggers | Posture |
|------|----------|---------|
| **remediation** | "remediation tool", "365", "breach", "tenant sweep", M365 keywords | Graph API focus, compliance language, full audit trail |
| **client** | client name, `clients/` work, "for \<client\>" | Careful with data, session logs in `clients/`, name the client |
| **infra** | server names/IPs, SSH, firewall, DNS, deploy, service restart | Confirm before destructive ops, backup-first |
| **dev** | code, build, Rust/cargo, npm, GuruRMM dev, `projects/` work | Delegate freely, less confirmation friction |
| **general** | default | Lightweight |
On mode change: announce `[MODE -> infra]`, tell user to run `/color <color>`. Full details: `.claude/commands/mode.md`
**MANDATORY on every mode change:** write the new mode to `.claude/current-mode` so hooks can read it:
```bash
echo dev > .claude/current-mode # substitute the actual mode name
```
This file is gitignored (machine-local). The `UserPromptSubmit` hook reads it to gate the lock check on dev mode.
**Windows/Git Bash:** always use the relative path above (or forward slashes — `/d/claudetools/.claude/current-mode`). NEVER a backslashed Windows path like `D:\claudetools\.claude\current-mode`: Git Bash strips the backslashes and substitutes the illegal `:` with a Unicode PUA char, creating a garbled junk file instead of writing the path. A `PreToolUse(Bash)` hook (`.claude/hooks/block-backslash-winpath.sh`) blocks such redirects; `sync.sh` also strips any that slip through before staging.
**Windows bash command (the `bash` executable):** In PowerShell contexts (including the Grok/Claude tool run_terminal_command), `bash` often resolves to the WSL stub (`WindowsApps\bash.exe`) instead of the required Git for Windows/MSYS bash. This breaks vault.sh, sync.sh, hooks, etc.
Fix (idempotent):
```powershell
$gitBin = "C:\Program Files\Git\bin"
$gitUsrBin = "C:\Program Files\Git\usr\bin"
if ((Test-Path $gitBin) -and ((Get-Command bash -ErrorAction SilentlyContinue).Source -notlike '*Git*bin*bash.exe')) {
$env:Path = "$gitBin;$gitUsrBin;" + ($env:Path -replace [regex]::Escape("$gitBin;"), '' -replace [regex]::Escape("$gitUsrBin;"), '')
}
```
Then plain `bash .claude/scripts/vault.sh ...` works and shows the MSYS version.
Project helper: `. .claude/scripts/ensure-git-bash.ps1` (see that file + `.claude/memory/feedback_windows_bash_mapping.md`).
The user's PowerShell `$PROFILE` auto-applies the remap on new sessions. For critical calls, prefer the full path `"C:\Program Files\Git\bin\bash.exe" .claude/scripts/...` if env is uncertain. Git Bash terminals (direct launch) are already correct. Related: always use system OpenSSH, not Git's.
**Auto-initialization:** If `.claude/current-mode` is missing (e.g., fresh clone), the UserPromptSubmit hook automatically creates it with "general" as the default mode. No manual setup required.
---
## Identity: You Are a Coordinator
You are NOT an executor. You coordinate specialized agents and preserve your context window.
**Delegate ALL significant work:**
| Operation | Delegate To |
|-----------|------------|
| Database queries/inserts/updates | Database Agent |
| Production code generation | Coding Agent |
| Code review (MANDATORY after changes) | Code Review Agent |
| Test execution | Testing Agent |
| Git commits/push/branch | Gitea Agent |
| Backups/restore | Backup Agent |
| File exploration (broad) | Explore Agent |
| Semantic code search | deep-explore Agent (uses GrepAI) |
| Complex reasoning | General-purpose + Sequential Thinking |
**Do yourself:** Simple responses, reading 1-2 files, presenting results, planning, decisions.
**Rule:** >500 tokens of work = delegate. Code or database = ALWAYS delegate.
**DO NOT** query databases directly. **DO NOT** write production code. **DO NOT** run tests. **DO NOT** commit/push.
**Single-agent for coupled tasks:** For explore → implement or explore → implement → review flows where the context is the same throughout, use one agent across all phases rather than spawning three. Each agent boundary is a cache miss and a context-handoff cost. Spawn separate agents only when tasks are genuinely independent or run in parallel.
### Model Routing (Complexity-Based)
| Tier | Model | When |
|------|-------|------|
| 0 | **Ollama** (local) | Low-stakes: summarize, classify, extract, draft — no code changes, output reviewed before use |
| 1 | `haiku` | Ollama unavailable, or task needs agent tool use / file access |
| 2 | (inherit) | Standard code, DB, tests, git — most work |
| 3 | `opus` | Architecture, security, ambiguous failures, production risk |
**Bump rule:** if the request involves `security`, `auth`, `credential`, `migration`, `production`, or `data loss` — bump one tier up.
Pass `model: "haiku"` or `model: "opus"` explicitly. Omit for Tier 2. Tier 0 is a direct Bash call — see `.claude/OLLAMA.md`.
---
## Automatic Context Loading (CRITICAL)
Load context **before responding** when any trigger fires. Never ask for info that's already in CONTEXT.md.
| Trigger | Action |
|---------|--------|
| Client name mentioned | Read `wiki/clients/<slug>.md` FIRST, then `clients/<name>/session-logs/` for recent detail |
| GuruRMM / Dataforth / project keywords | Read `wiki/projects/<slug>.md` FIRST, then `projects/<project>/CONTEXT.md`, query coord API status + components |
| Server/hostname/IP mentioned | Read `wiki/systems/<slug>.md` FIRST for synthesized knowledge |
| "continue", "resume", "back to", "finish" | Read project wiki article + CONTEXT.md, check coord API for locks + unread messages |
| Servers, IPs, credentials, deploy questions | Check wiki/systems first, then CONTEXT.md — answer from it, never ask |
| Uncertainty >5% about infra or recent work | Check wiki first, then CONTEXT.md before asking the user |
CONTEXT.md locations: `projects/msp-tools/guru-rmm/CONTEXT.md`, `projects/dataforth-dos/CONTEXT.md`, `CONTEXT.md` (root).
Wiki location: `wiki/` (root) — `wiki/clients/`, `wiki/projects/`, `wiki/systems/`, `wiki/patterns/`. Index: `wiki/index.md`.
---
## Projects
**ClaudeTools** — MSP Work Tracking System (Production-Ready)
- Database: MariaDB 10.6.22 @ 172.16.3.30:3306 | API: http://172.16.3.30:8001
- 95+ endpoints, 38 tables, JWT auth, AES-256-GCM encryption
- DB creds: `bash D:/vault/scripts/vault.sh get-field projects/claudetools/database.sops.yaml credentials.password`
**GuruRMM** — Remote Monitoring & Management (Active Development)
- Server: Rust/Axum @ 172.16.3.30:3001 | Dashboard: https://rmm.azcomputerguru.com
- Repo: `azcomputerguru/gururmm` on Gitea (active) — the `projects/msp-tools/guru-rmm/` submodule tracks it. A separate Gitea repo named `guru-rmm` (hyphenated) is an abandoned duplicate; ignore it.
- Roadmap: `projects/msp-tools/guru-rmm/docs/FEATURE_ROADMAP.md` (also `docs/UI_GAPS.md`)
---
## Key Rules
- **Coord messages in system-reminder:** If a `system-reminder` contains "UNREAD COORD MESSAGES", you MUST reproduce the full message block verbatim at the top of your response before addressing anything else. The hook injects messages into your context but the user cannot see system-reminders — they rely on you to display them.
- **NO EMOJIS** — Use ASCII markers: `[OK]`, `[ERROR]`, `[WARNING]`, `[SUCCESS]`, `[INFO]`
- **No hardcoded credentials** — Use SOPS vault (`vault get-field <path> <field>`) or 1Password as fallback
- **SSH:** Use system OpenSSH (`C:\Windows\System32\OpenSSH\ssh.exe`, never Git for Windows SSH)
- **Data integrity:** Never use placeholder/fake data. Check SOPS vault, credentials.md, or ask user.
- **Coding standards:** `.claude/CODING_GUIDELINES.md` (agents read on-demand)
---
## Live State Tracking (ALL Projects)
**Coord API is the live source of truth.** API base: `http://172.16.3.30:8001/api/coord` (no auth).
### Session start
```bash
curl -s "http://172.16.3.30:8001/api/coord/messages?to_session=<SESSION_ID>&unread_only=true"
curl -s "http://172.16.3.30:8001/api/coord/status"
curl -s "http://172.16.3.30:8001/api/coord/locks?project_key=<KEY>"
```
Display unread messages before any work. Mark read: `PUT /api/coord/messages/<id>/read`
### Before significant work — claim a lock
```bash
curl -s -X POST http://172.16.3.30:8001/api/coord/locks \
-H "Content-Type: application/json" \
-d '{"project_key":"gururmm","session_id":"DESKTOP-0O8A1RL/claude-main","resource":"server/src","description":"...","ttl_hours":2}'
```
### After work — release lock + update component
```bash
curl -s -X DELETE "http://172.16.3.30:8001/api/coord/locks/<id>?session_id=<SESSION_ID>"
curl -s -X PUT "http://172.16.3.30:8001/api/coord/components/gururmm/server" \
-H "Content-Type: application/json" \
-d '{"state":"deployed","version":"0.3.0","notes":"...","updated_by":"DESKTOP-0O8A1RL/claude-main"}'
```
**Softfail:** If API unreachable, continue work and log failed calls to `.claude/coord-queue.jsonl`. Drain on next `/sync`.
### Project keys
| project_key | Components | States |
|-------------|------------|--------|
| `gururmm` | `server`, `agents`, `dashboard`, `db_migrations` | `building`, `built`, `deploying`, `deployed`, `degraded` |
| `guruconnect` | `server`, `agent`, `dashboard` | `building`, `built`, `deploying`, `deployed`, `degraded` |
| `claudetools` | `api`, `db_migrations`, `coord_api` | `deploying`, `deployed`, `degraded` |
| `dataforth-dos` | `app`, `db` | `active`, `idle`, `degraded` |
| `clients/<name>` | `(free-form)` | `(free-form)` |
Full protocol + inter-session messaging: `.claude/COORDINATION_PROTOCOL.md`
---
## Automatic Behaviors
- **Frontend Design:** Auto-invoke `/frontend-design` skill after ANY UI change (HTML/CSS/JSX/styling)
- **Sequential Thinking:** Use for genuine complexity — rejection loops, 3+ critical issues, architectural decisions
- **Task Management:** Complex work (>3 steps) → TaskCreate. Persist to `.claude/active-tasks.json`.
- **Auto Todo Creation:** When wrapping up a task that has unresolved follow-up, open items, or deferred work, POST to `POST /api/coord/todos` with `auto_created: true` and `source_context` describing why. Assign `project_key` if project-scoped; assign `assigned_to_user` if only relevant to one tech. Sub-tasks: set `parent_id` to link under a parent todo. Never create a todo for something already being done in the current session.
### Querying Todos
- "What needs to be done with \<project\>?" → `GET /api/coord/todos?project_key=<key>&status_filter=pending`
- "What are my open todos?" → `GET /api/coord/todos?for_user=<user>&status_filter=pending`
- "Show all todos including done" → add `status_filter=all`
- "Mark done" → `PUT /api/coord/todos/<id>` with `{"status": "done", "completed_by": "<user>"}`
### Cross-Session Messages (MANDATORY)
See the **Session Start Protocol** in "Live State Tracking" above. Messages must be displayed and marked read before any other work.
Also scan session logs pulled during `/sync` for legacy `## Note for <user>` sections (transitional — older sessions still use markdown).
---
## Context Recovery
When user references previous work, use `/context` command. Never ask for info in:
- `wiki/` — **Check first.** LLM-compiled synthesized knowledge by client/project/system. Index: `wiki/index.md`
- `credentials.md` — Infrastructure reference (being migrated to SOPS vault)
- `session-logs/` — Daily work logs (also in `projects/*/session-logs/` and `clients/*/session-logs/`)
- **Coordination API** — current locks, component states, workflows, messages: `GET http://172.16.3.30:8001/api/coord/status`
- `projects/*/PROJECT_STATE.md` — ARCHIVED. Read-only historical reference. Do not edit. Use coordination API for live state.
### Credential Access (SOPS Vault)
Use the ClaudeTools vault wrapper — never hardcode the vault path:
```bash
# CLAUDETOOLS_ROOT is the repo root (D:\claudetools on Windows, ~/claudetools on Mac/Linux)
VAULT="$CLAUDETOOLS_ROOT/.claude/scripts/vault.sh"
bash "$VAULT" search "keyword" # Search without decrypting
bash "$VAULT" get-field <path> <field> # Get specific field
bash "$VAULT" get <path> # Decrypt full entry
bash "$VAULT" list # List all entries
```
The wrapper reads `vault_path` from `.claude/identity.json` (per-machine, gitignored).
Each machine sets its own vault path there — no hardcoded paths in any shared file.
Vault structure: `infrastructure/`, `clients/`, `services/`, `projects/`, `msp-tools/`
**1Password fallback:** service account token in `infrastructure/1password-service-account.sops.yaml`
---
## Commands & Skills
| Command | Purpose |
|---------|---------|
| `/checkpoint` | Dual checkpoint: git commit + database context |
| `/save` | Comprehensive session log |
| `/context` | Search wiki first, then session logs, credentials.md, and 1Password |
| `/wiki-compile` | Compile session logs into wiki articles for a client/project/system/all |
| `/wiki-lint` | Health-check wiki for stale IPs, broken backlinks, orphaned articles |
| `/1password` | 1Password secrets management |
| `/sync` | Sync config from Gitea repository |
| `/create-spec` | Create app specification for AutoCoder |
| `/frontend-design` | Modern frontend design (auto-invoke after UI changes) |
| `/rmm` | Remote command execution on GuruRMM agents — list, run, poll, cancel |
| `/remediation-tool` | M365 breach checks, tenant sweeps, gated remediation |
| `/feature-request` | Howard submits a GuruRMM feature request — Claude classifies it and messages Mike |
| `/shape-spec` | Pre-implementation spec for a GuruRMM feature — produces plan.md, shape.md, references.md, standards.md |
| `/rmm-audit` | Full end-to-end audit of GuruRMM: API coverage, UI gaps, Rust/TS quality, security, data integrity. Produces timestamped report + updates UI_GAPS.md |
| `/forum-post` | Post a technical article to community.azcomputerguru.com — drafts from context, shows preview, inserts via paramiko SSH to Flarum DB |
| `/recover` | Reconstruct a session log from a Claude Code transcript after a crash/close-before-save. `/recover <uuid>`, `/recover latest`, or `/recover --list`. See `.claude/RECOVERY.md` |
---
## File Placement
- GuruRMM work → `projects/msp-tools/guru-rmm/` (git submodule tracking the **active** `azcomputerguru/gururmm` repo; the pinned commit normally lags `main` — that's expected, not "stale"). Empty on a fresh clone until `git submodule update --init`; `/sync` now does this automatically.
- GuruRMM session logs → root `session-logs/` (NOT the submodule)
- Client work → `clients/[client-name]/`
- Session logs → project/client `session-logs/` subfolder; general work → root `session-logs/`
- Full guide: `.claude/FILE_PLACEMENT_GUIDE.md`
---
## Local AI (Ollama)
Tier 0 — **Ollama is the documentation and classification engine.** Route prose, summaries, and classification through it; Claude reviews before writing or posting.
**Models:** `qwen3.6:latest` (structured: JSON, classification), `qwen3:8b` / `qwen3:14b` (prose), `codestral:22b` (code suggestions).
**Configuration:** All machine-specific config (endpoint, fallback, prose_model, python command, platform, architecture) lives in `.claude/identity.json`, populated by `.claude/scripts/migrate-identity.sh`. Scripts read `.ollama.endpoint` directly — no curl probing.
**Reference:** `.claude/OLLAMA.md` for full model usage + routing patterns.
### GrepAI (Semantic Code Search)
**Recall hierarchy — wiki first, GrepAI second.** GrepAI is NOT the first stop for context.
The synthesized **wiki** (`wiki/`, 57 curated client/project/system articles) is the truth layer
for a *known entity* — check it first (it is cheaper and already distilled). Go to GrepAI when the
wiki can't answer:
1. **Code** — `grepai_search` / `grepai_trace_callers` / `grepai_trace_callees` over the Rust+TS
corpus (~8k files). The wiki has zero code awareness; this is GrepAI's irreplaceable value for
GuruRMM/GuruConnect dev (call-graph tracing, "where is Z implemented").
2. **Discovery** — you don't know the entity name, or no wiki article exists yet (a new
client/system not yet compiled).
3. **Sub-synthesis detail** — a fact that was in a raw session log but didn't make the wiki's
summary cut.
Order of recall: **wiki (known entity) -> GrepAI (code / discovery / un-compiled detail) -> raw
file reads.** Do NOT GrepAI something the wiki already answers — that's the redundant overlap.
- **MCP tools:** `grepai_search` (primary), `grepai_trace_callers`, `grepai_trace_callees`
- **Agent:** `deep-explore` (for multi-hop CODE exploration)
- **CLI:** `$CLAUDETOOLS_ROOT/grepai search "query" --json -c -n 5`
- **Watcher:** runs as scheduled task "GrepAI Watcher - claudetools" (auto-starts on login, keeps index current)
---
## Memory (Shared Across Machines)
Stored in-repo at `.claude/memory/` — syncs via Gitea to all workstations.
Index: `.claude/memory/MEMORY.md`
**IMPORTANT:** Always write to `.claude/memory/` (repo-relative), NOT `~/.claude/projects/*/memory/`.
---
## Reference (read on-demand)
- **Fleet machine specs + onboarding checklist:** `.claude/machines/` (per-host `<hostname>.md`, plus `LINUX_PC_ONBOARDING.md`)
- **Project structure, endpoints, workflows:** `.claude/REFERENCE.md`
- **Agent definitions:** `.claude/agents/*.md`
- **MCP servers:** `MCP_SERVERS.md`
- **Coding standards:** `.claude/CODING_GUIDELINES.md`
- **Ollama connection + examples:** `.claude/OLLAMA.md`
- **PROJECT_STATE locking protocol:** `.claude/PROJECT_STATE_PROTOCOL.md`
- **Temp directory graduation workflow:** `.claude/TEMP_GRADUATION.md`
---
**Last Updated:** 2026-05-29

View File

@@ -65,9 +65,12 @@ powershell.exe -Command '$x = 5; Write-Host $x'
---
## Context Lookup — GrepAI First
## Context Lookup — search before reading (wiki first for known entities)
Before reading any file for context, search with GrepAI or Grep. Only open a file when you need its full content for editing or line-by-line review.
For a **known entity's facts** (a specific client/project/system), check the **wiki** first — it is
the synthesized truth layer. For **code and discovery**, search with GrepAI or Grep before reading
any file; only open a file when you need its full content for editing or line-by-line review. Full
rule: `.claude/standards/context-lookup/grepai-first.md`.
| Goal | Tool |
|------|------|

View File

@@ -83,18 +83,23 @@ If neither endpoint responds: verify Tailscale (`tailscale status`) and whether
Use the `/api/chat` endpoint with `think:false` for qwen3 models. The older `/api/generate` endpoint on qwen3 puts output into thinking tokens that don't appear in the `response` field — you'll get an empty response if you use `/api/generate`.
Preferred one-liner:
Preferred one-liner — endpoint **and** model come from `identity.json` (consistent with
**Endpoints** above; no per-call probe). The old inline auto-detect was REMOVED: it called
`urlopen()` as a truthiness test, which *raises* `URLError` on a down host instead of
yielding the fallback — so it crashed on a down localhost rather than failing over to Beast,
and it violated the "no per-call probe" rule.
```bash
python -c "
OLLAMA="${OLLAMA:-$(jq -r '.ollama.endpoint // .ollama.fallback // "http://localhost:11434"' .claude/identity.json)}"
MODEL="${MODEL:-$(jq -r '.ollama.prose_model // "qwen3:14b"' .claude/identity.json)}"
OLLAMA="$OLLAMA" MODEL="$MODEL" python -c "
import urllib.request, json, sys, os
OLLAMA = os.environ.get('OLLAMA') or ('http://localhost:11434' if __import__('urllib.request').request.urlopen(urllib.request.Request('http://localhost:11434/api/tags'),timeout=2) else 'http://100.101.122.4:11434')
body = json.dumps({
'model':'qwen3:14b',
'model': os.environ['MODEL'],
'messages':[{'role':'user','content': sys.argv[1]}],
'stream':False,
'think':False
}).encode()
res = json.loads(urllib.request.urlopen(urllib.request.Request(OLLAMA+'/api/chat', body), timeout=120).read())
res = json.loads(urllib.request.urlopen(urllib.request.Request(os.environ['OLLAMA']+'/api/chat', body), timeout=120).read())
print(res['message']['content'])
" "Your prompt here"
```

View File

@@ -35,6 +35,20 @@ Some configuration files are **machine-local** (gitignored, not synced) because
|------|---------|---------------|
| `.claude/identity.json` | Your name, email, vault path | YES — during onboarding |
| `.claude/current-mode` | Work mode (dev, infra, client, etc.) | YES — defaults to "general" |
| `.claude/settings.local.json` | Per-machine env (`CLAUDETOOLS_ROOT`) + local permissions | env via provisioner (below) |
**`CLAUDETOOLS_ROOT` env — run once per machine after `identity.json` exists:**
```bash
py .claude/scripts/ensure-settings-env.py
```
This seeds `env.CLAUDETOOLS_ROOT` in `.claude/settings.local.json` from
`identity.json.claudetools_root`. Many skill docs invoke
`py "$CLAUDETOOLS_ROOT/.claude/skills/.../x.py"`; Claude Code injects the settings `env`
block into every Bash call, so without this the var is empty and those commands resolve to
the wrong path (the failure logged in `errorlog.md` on 2026-06-14). Idempotent; takes effect
at the next session start. The script is also safe to re-run any time the clone moves.
**`.claude/current-mode`** is used by coordination hooks to determine behavior:
- In `dev` mode: Hooks show active locks as warnings but don't block

View File

@@ -164,7 +164,7 @@ Run all of these. Any False or non-2xx is a problem.
$checks = @(
@{host="172.16.3.20"; port=22; label="Jupiter SSH"},
@{host="172.16.3.20"; port=3000; label="Gitea"},
@{host="172.16.3.30"; port=22; label="GuruRMM VM SSH"},
@{host="172.16.3.30"; port=22; label="GuruRMM SSH (physical box)"},
@{host="172.16.3.30"; port=3001; label="GuruRMM server"},
@{host="172.16.3.30"; port=8001; label="Coord API"},
@{host="172.16.3.20"; port=443; label="NPM HTTPS (via iptables)"},
@@ -196,7 +196,7 @@ Write-Host "$(if ($resp.StatusCode -eq 200) {'[OK]'} else {'[FAIL]'}) sync.azcom
| pfSense | 172.16.0.1 (SSH port 2248) | Router, DNS, Tailscale subnet router |
| Jupiter | 172.16.3.20 | Unraid NAS — hosts all VMs + Docker |
| Uranus | 172.16.3.21 | OwnCloud additional storage (not a proxy) |
| GuruRMM VM | 172.16.3.30 | Linux VM on Jupiter — GuruRMM server, Coord API, MariaDB |
| GuruRMM | 172.16.3.30 | **Physical box** (Lenovo ThinkCentre M83, Ubuntu 26.04), NOT a Jupiter VM — GuruRMM server, Coord API, MariaDB/PostgreSQL. Boots independently of Jupiter. |
| Pluto | 172.16.3.36 | Windows Server 2019 VM on Jupiter — build server |
| Tailscale range | 172.16.0.0/22 | Advertised via pfSense pfsense-2 node |

View File

@@ -56,6 +56,17 @@ You are the Gitea Agent - the sole custodian of version control for all ClaudeTo
**Authentication:** SSH key (C:\Users\MikeSwanson\.ssh\id_ed25519)
**Local Git:** git.exe (Windows Git)
### Non-interactive auth (IMPORTANT)
Mike's hard requirement: git must NEVER sit at an interactive credential/password prompt. That is his actual objection to Git for Windows — its Git Credential Manager (`credential.helper = manager`) pops a prompt and silently hangs any automation/background push. This repo (`D:\ClaudeTools`) is configured to authenticate silently instead: repo-local `credential.helper = store`, primed with the `azcomputerguru` Gitea API token in `~/.git-credentials`, scoped to the internal host `172.16.3.20:3000`. So a plain `git push origin main` / `git fetch` just works with no prompt. The global GCM default is left untouched for other repos.
Rules when running git here:
- Run git from the **PowerShell tool** using native `git.exe`; quote Windows paths as-is.
- ALWAYS set `GIT_TERMINAL_PROMPT=0` (PowerShell: `$env:GIT_TERMINAL_PROMPT='0'`) so a credential failure errors immediately instead of hanging on a hidden prompt — a hang is fatal for background agents.
- If the stored credential is ever missing, get the token from vault `services/gitea.sops.yaml` field `api-token` (username `azcomputerguru`) and either re-append the `store` line to `~/.git-credentials` or push once to `http://azcomputerguru:<token>@172.16.3.20:3000/azcomputerguru/claudetools.git`.
- Note: git writes progress (including "Everything up-to-date") to stderr; under PowerShell 5.1 that surfaces as a `NativeCommandError` even on success — trust `$LASTEXITCODE`/`EXIT=0`, not the red text.
- System OpenSSH (not Git's bundled SSH) remains the rule for any SSH-based remote.
See memory: `feedback_git_noninteractive_auth`.
## Repository Structure
### System Repository

View File

@@ -0,0 +1,136 @@
# ClaudeTools Windows Bootstrap & Recovery Runbook
Rebuild this workstation (GURU-5070, Lenovo Legion Pro 7 16IAX10H) after a clean
Windows reset. Everything here is driven by two scripts in this folder:
- `windows-bootstrap.ps1` — installs tools, restores secrets, clones repos, wires tasks
- `restore-secrets.ps1` — copies secrets/identity from the recovery bundle back into place
The recovery bundle lives on the removable drives:
| Drive | Label | Holds |
|-------|---------|-------|
| **E:** | (FAT32) | `claudetools-recovery\` — secrets + identity + manifests (redundant copy) |
| **F:** | Ventoy | `claudetools-recovery\` — same bundle **plus** `data\` (large client data) |
> F: is also a bootable rescue stick (SystemRescue, Boot Repair) — keep it; it can
> help fix the machine. The bundle lives in `F:\claudetools-recovery\`, Ventoy is untouched.
---
## What's in the bundle (and why it can't just be re-cloned)
`claudetools-recovery\`
- `secrets\`
- `sops-age\keys.txt`**THE most critical file.** The SOPS age private key. Without
it the entire vault (`D:\vault`) is permanently undecryptable. Not stored in any repo.
- `ssh\``id_ed25519` (+pub), `pst-cc-ucg` (+pub), `config`, `known_hosts`
- `claude\``.claude.json`, `.credentials.json` (Claude Code login), settings, keybindings, statusline
- `grok\``auth.json`, `config.toml`, `agent_id`
- `gemini\``oauth_creds.json`, `google_accounts.json`, settings, installation_id
- `git\.gitconfig`, `powershell\Microsoft.PowerShell_profile.ps1`
- `identity\` — repo-local gitignored files: `identity.json`, `settings.local.json`,
`current-mode`, `coord-broadcasts-seen`, `mcp.json`, `.claude/state\`, ticktick tokens, dataforth oauth
- `config\` — Windows Terminal settings, fleet `hosts` file, quote-wizard `.env.production`
- `manifests\``installed-tools.txt`, `ollama-models.txt`, `git-global-config.txt`,
`repos.txt`, `user-environment.reg` / `.txt` (incl. `OLLAMA_MODELS`/`OLLAMA_HOST`/`PROTOC`), `scheduled-tasks\*.xml`
- `at-risk-work\` — local-only WIP rescued from the submodules (not on any remote):
guru-rmm stashes as `.patch` files + guru-connect `tmp-spec018.diff`. The bootstrap
re-applies these automatically in Phase 6 (`restore-at-risk-work.ps1`) — the guru-rmm
ones are put back **as stashes** (`git stash list`), the guru-connect diff is dropped
back as its untracked working file. See `RESTORE-at-risk-work.txt` for manual steps.
- `data\` (F: only) — large non-Gitea client/project data, repo-relative paths
Everything else (all tracked code, skills, commands, docs, session logs, wiki) comes
back from Gitea on clone — no need to back it up.
---
## Fast path (one shot)
From an **elevated PowerShell**, with E: or F: plugged in:
```powershell
# copy the script off the drive first (so it survives a re-clone)
Copy-Item F:\claudetools-recovery\bootstrap\windows-bootstrap.ps1 $env:TEMP\boot.ps1
& $env:TEMP\.. # or just run directly:
F:\claudetools-recovery\bootstrap\windows-bootstrap.ps1 -SkipModels
```
Run it from an **elevated** shell so Phase 0 can rename the machine to `GURU-5070`
(read from the bundle's identity.json; override with `-Hostname <name>`). The rename
needs a **reboot** to take effect — the script reminds you at the end. Re-run after the
reboot to finish any phases that depend on the hostname.
`-SkipModels` defers the ~50 GB Ollama downloads. Drop it (or run Phase 8 later) when
you want them. Add `-RestoreData` to also pull back the large client data from `F:\...\data`.
The script is **idempotent** — safe to re-run; it skips anything already done. To run
just part of it: `-OnlyPhases "1,2,3"`.
---
## Manual path (if you'd rather do it by hand)
0. **Set the hostname** (elevated): `Rename-Computer -NewName GURU-5070 -Restart`. Do this
first so scheduled tasks / coord session IDs line up after the reboot.
1. **Install App Installer** (winget) from the Microsoft Store if missing.
2. **Core tools** (winget ids):
`Git.Git`, `OpenJS.NodeJS.LTS`, `Python.Python.3.14`, `Rustlang.Rustup`,
`Microsoft.VisualStudioCode`, `Ollama.Ollama`, `jqlang.jq`,
`SecretsOPerationS.SOPS`, `FiloSottile.age`, `GitHub.cli`, `AgileBits.1Password.CLI`,
`Microsoft.DotNet.SDK.8`, `Google.Protobuf`, `oschwartz10612.Poppler`, `Tailscale.Tailscale`
Then `dotnet tool install --global wix` (MSI builds).
Set env: `OLLAMA_MODELS=D:\OllamaModels`, `OLLAMA_HOST=0.0.0.0:11434`, `PROTOC=<protoc.exe>`.
3. **AI CLIs:**
- Claude: `irm https://claude.ai/install.ps1 | iex``~/.local/bin/claude.exe`
- Gemini: `npm install -g @google/gemini-cli`
- Grok: `bash -c "curl -fsSL https://x.ai/cli/install.sh | bash"` (Git Bash)
4. **Restore home secrets:** `F:\claudetools-recovery\bootstrap\restore-secrets.ps1 -Group home`
5. **Clone repos:**
```
git clone https://git.azcomputerguru.com/azcomputerguru/claudetools.git D:\claudetools
cd D:\claudetools; git submodule update --init --recursive
git clone https://git.azcomputerguru.com/azcomputerguru/vault.git D:\vault
```
(On-network you can use `http://172.16.3.20:3000/...` to bypass the SSL-renewal blips.)
6. **Restore identity:** `restore-secrets.ps1 -Group repo`
7. **Ollama models (proper set for this 12 GB-VRAM laptop):**
`ollama pull nomic-embed-text:latest` (GrepAI embeddings) and `ollama pull qwen3:8b` (prose_model).
Models live on `D:\OllamaModels` (47.8 GB) — **if D: survived the reset they're already there, skip this.**
Heavy extras (`qwen3:14b`, `codestral:22b`, `qwen3.6:latest`) are opt-in only; they over-saturate 12 GB VRAM.
8. **Scheduled tasks:** import each XML in `manifests\scheduled-tasks\` via
`Register-ScheduledTask -Xml (Get-Content x.xml -Raw) -TaskName "..."`.
9. **Verify:** `D:\claudetools\.claude\scripts\onboarding-diagnostic.ps1`, then `/self-check` in Claude Code.
---
## Post-install: things that need an interactive login
Auth tokens are backed up, but some expire. If a tool says it's unauthenticated:
- **Claude Code:** run `claude`, then `/login` (browser).
- **GitHub CLI:** `gh auth login`
- **1Password:** `op signin`
- **Gemini:** launch `gemini`, complete the Google OAuth browser flow.
- **Grok:** `grok login` (tokens expire after 7 days).
- **Gitea git push:** uses the Windows Credential Manager (`credential.helper=manager`).
First push prompts for the shared `azcomputerguru` account. **Do NOT** bake the password
into the remote URL (the old `D:\work\gururmm` clone did — reset it to a clean URL).
## Verify the vault decrypts (proves the age key restored correctly)
```
bash D:/claudetools/.claude/scripts/vault.sh list
bash D:/claudetools/.claude/scripts/vault.sh get-field projects/claudetools/database.sops.yaml credentials.password
```
If that returns the password, recovery succeeded. If it errors about decryption, the
age key at `%APPDATA%\sops\age\keys.txt` and `~/.config/sops/age/keys.txt` is missing/wrong.
---
## Refreshing this bundle later
Re-run the backup any time (it's just file copies):
`D:\claudetools\.claude\bootstrap\backup-to-bundle.ps1` (writes to E: and F:).

View File

@@ -0,0 +1,169 @@
<#
.SYNOPSIS
Back up ClaudeTools secrets + identity (and optionally large client data) to a
recovery bundle on a removable drive. The inverse of restore-secrets.ps1.
.DESCRIPTION
Captures everything that will NOT come back from a `git clone`:
- out-of-repo secrets under the user profile (age key, ssh, tool auth, git, PS profile)
- repo-local gitignored identity files
- environment manifests (installed tools, ollama models, scheduled-task XML, vscode ext)
- (optional) large gitignored client/project data clusters
Safe to re-run; it refreshes the bundle in place.
.PARAMETER Drives Target drive roots. Default 'E:','F:' (writes the small bundle to both).
.PARAMETER IncludeData Also copy the large client-data clusters (only to the FIRST drive with room; exFAT recommended).
.PARAMETER ClaudeToolsRoot Default D:\claudetools.
.EXAMPLE
.\backup-to-bundle.ps1 # secrets+identity+manifests to E: and F:
.\backup-to-bundle.ps1 -IncludeData # also large data (to F:)
#>
[CmdletBinding()]
param(
[string[]]$Drives = @('E:','F:'),
[switch]$IncludeData,
[string]$ClaudeToolsRoot = 'D:\claudetools',
[string]$DataDrive = 'F:'
)
$ErrorActionPreference = 'Stop'
$u = $env:USERPROFILE
# Decode native (git) stdout as UTF-8 so captured patch text is not mangled, and give
# us a UTF-8 (no BOM) encoding for writing patches `git apply` can actually parse.
try { [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false) } catch {}
$Utf8NoBom = New-Object System.Text.UTF8Encoding($false)
function Save($src,$dst){
if (Test-Path -LiteralPath $src) {
$p = Split-Path $dst -Parent; if (-not (Test-Path $p)) { New-Item -ItemType Directory -Force -Path $p | Out-Null }
Copy-Item -LiteralPath $src -Destination $dst -Force; Write-Host "[OK] $src"
} else { Write-Host "[MISS] $src" }
}
# Build the bundle once under the first available target, then mirror to the rest.
$primary = $Drives | Where-Object { Test-Path "$_\" } | Select-Object -First 1
if (-not $primary) { throw "None of the target drives are accessible: $($Drives -join ', ')" }
$root = "$primary\claudetools-recovery"
Write-Host "=== building bundle at $root ===" -ForegroundColor Cyan
foreach ($d in 'secrets\sops-age','secrets\ssh','secrets\claude','secrets\grok','secrets\gemini','secrets\git','secrets\powershell','identity\state','manifests\scheduled-tasks','bootstrap') {
New-Item -ItemType Directory -Force -Path "$root\$d" | Out-Null
}
# --- secrets ---
Save "$u\.config\sops\age\keys.txt" "$root\secrets\sops-age\keys.txt"
if (Test-Path "$u\.ssh") { Copy-Item "$u\.ssh\*" "$root\secrets\ssh\" -Force; Write-Host "[OK] ~/.ssh/*" }
Save "$u\.claude.json" "$root\secrets\claude\.claude.json"
Save "$u\.claude\.credentials.json" "$root\secrets\claude\.credentials.json"
Save "$u\.claude\settings.json" "$root\secrets\claude\settings.json"
Save "$u\.claude\keybindings.json" "$root\secrets\claude\keybindings.json"
Save "$u\.claude\statusline-command.sh" "$root\secrets\claude\statusline-command.sh"
Save "$u\.grok\auth.json" "$root\secrets\grok\auth.json"
Save "$u\.grok\config.toml" "$root\secrets\grok\config.toml"
Save "$u\.grok\agent_id" "$root\secrets\grok\agent_id"
Save "$u\.gemini\oauth_creds.json" "$root\secrets\gemini\oauth_creds.json"
Save "$u\.gemini\google_accounts.json" "$root\secrets\gemini\google_accounts.json"
Save "$u\.gemini\settings.json" "$root\secrets\gemini\settings.json"
Save "$u\.gemini\installation_id" "$root\secrets\gemini\installation_id"
Save "$u\.gitconfig" "$root\secrets\git\.gitconfig"
# user-global Claude commands + plugins (not in repo)
if (Test-Path "$u\.claude\commands") { New-Item -ItemType Directory -Force -Path "$root\secrets\claude-global\commands" | Out-Null; robocopy "$u\.claude\commands" "$root\secrets\claude-global\commands" /E /R:1 /W:1 /NFL /NDL /NJH /NJS /NP | Out-Null; Write-Host "[OK] ~/.claude/commands" }
if (Test-Path "$u\.claude\plugins") { New-Item -ItemType Directory -Force -Path "$root\secrets\claude-global\plugins" | Out-Null; robocopy "$u\.claude\plugins" "$root\secrets\claude-global\plugins" /E /R:1 /W:1 /NFL /NDL /NJH /NJS /NP | Out-Null; Write-Host "[OK] ~/.claude/plugins" }
Save $PROFILE "$root\secrets\powershell\Microsoft.PowerShell_profile.ps1"
# --- repo-local identity ---
Save "$ClaudeToolsRoot\.claude\identity.json" "$root\identity\identity.json"
Save "$ClaudeToolsRoot\.claude\settings.local.json" "$root\identity\settings.local.json"
Save "$ClaudeToolsRoot\.claude\current-mode" "$root\identity\current-mode"
Save "$ClaudeToolsRoot\.claude\coord-broadcasts-seen" "$root\identity\coord-broadcasts-seen"
Save "$ClaudeToolsRoot\.mcp.json" "$root\identity\mcp.json"
Save "$ClaudeToolsRoot\mcp-servers\ticktick\.tokens.json" "$root\identity\ticktick-tokens.json"
Save "$ClaudeToolsRoot\clients\dataforth\Oauth.txt" "$root\identity\dataforth-oauth.txt"
if (Test-Path "$ClaudeToolsRoot\.claude\state") { Copy-Item "$ClaudeToolsRoot\.claude\state\*" "$root\identity\state\" -Recurse -Force -ErrorAction SilentlyContinue }
# --- bootstrap scripts (so the drive is self-contained) ---
Copy-Item "$ClaudeToolsRoot\.claude\bootstrap\*.ps1" "$root\bootstrap\" -Force -ErrorAction SilentlyContinue
Copy-Item "$ClaudeToolsRoot\.claude\bootstrap\RESTORE.md" "$root\bootstrap\" -Force -ErrorAction SilentlyContinue
# --- at-risk local WIP: stashes + untracked diffs that are on NO remote ---
# Written as UTF-8 (no BOM, LF) so restore-at-risk-work.ps1 / `git apply` can parse them.
# (Earlier ad-hoc captures used PowerShell `>` redirection = UTF-16, which git apply
# rejects with "No valid patches in input" - hence the explicit byte-level write here.)
$awRoot = "$root\at-risk-work"
function Save-RepoStashes($repo,$label){
if (-not (Test-Path "$repo\.git")) { return }
$marks = @(& git -C $repo stash list --format='%gd' 2>$null)
if (-not $marks) { return }
$dir = "$awRoot\$label"; New-Item -ItemType Directory -Force -Path $dir | Out-Null
$base = (& git -C $repo rev-parse HEAD 2>$null)
[System.IO.File]::WriteAllText("$dir\BASE-COMMIT.txt", "$base`n", $Utf8NoBom)
for ($i=0; $i -lt $marks.Count; $i++) {
$files = @(& git -C $repo stash show --name-only "stash@{$i}" 2>$null)
$slug = if ($files.Count) { ([IO.Path]::GetFileNameWithoutExtension($files[0])) -replace '[^\w\-]','_' } else { "stash$i" }
$lines = @(& git -C $repo --no-pager stash show -p "stash@{$i}" 2>$null)
[System.IO.File]::WriteAllText("$dir\stash$i-$slug.patch", (($lines -join "`n") + "`n"), $Utf8NoBom)
Write-Host "[OK] at-risk stash: $label stash@{$i} -> stash$i-$slug.patch"
}
}
Save-RepoStashes "$ClaudeToolsRoot\projects\msp-tools\guru-rmm" 'guru-rmm'
Save-RepoStashes "$ClaudeToolsRoot\projects\msp-tools\guru-connect" 'guru-connect'
# untracked working diffs (e.g. tmp-*.diff) that aren't committed anywhere
$gcRepo = "$ClaudeToolsRoot\projects\msp-tools\guru-connect"
if (Test-Path $gcRepo) {
Get-ChildItem $gcRepo -Filter 'tmp-*.diff' -File -ErrorAction SilentlyContinue | ForEach-Object {
$dir = "$awRoot\guru-connect"; New-Item -ItemType Directory -Force -Path $dir | Out-Null
Copy-Item $_.FullName "$dir\$($_.Name)" -Force; Write-Host "[OK] at-risk untracked diff: guru-connect\$($_.Name)"
}
}
# --- manifests ---
$m = "$root\manifests"
$tools = 'node','npm','claude','gemini','grok','ollama','py','git','gh','jq','sops','age','cargo','rustc','code','op'
($tools | ForEach-Object { $c = Get-Command $_ -ErrorAction SilentlyContinue; if ($c) { $v = try { (& $_ --version 2>$null | Select-Object -First 1) } catch {''}; "{0,-10} {1,-55} {2}" -f $_,$c.Source,$v } else { "{0,-10} NOT INSTALLED" -f $_ } }) | Out-File "$m\installed-tools.txt" -Encoding utf8
ollama list 2>$null | Out-File "$m\ollama-models.txt" -Encoding utf8
git config --global --list | Out-File "$m\git-global-config.txt" -Encoding utf8
$ext = & code --list-extensions 2>$null; if ($ext) { $ext | Out-File "$m\vscode-extensions.txt" -Encoding utf8 }
foreach ($tn in "GrepAI Watcher - claudetools","ClaudeTools - Orphaned Session Detector","ClaudeTools - KSTEEN SmartBadge Daily") {
$safe = ($tn -replace '[^\w\-]','_')
try { Export-ScheduledTask -TaskName $tn 2>$null | Out-File "$m\scheduled-tasks\$safe.xml" -Encoding utf8 } catch {}
}
# user environment vars (.reg restorable + readable)
reg export "HKCU\Environment" "$m\user-environment.reg" /y 2>$null | Out-Null
(Get-Item 'HKCU:\Environment' | Select-Object -ExpandProperty Property | ForEach-Object { "{0}={1}" -f $_, (Get-ItemProperty 'HKCU:\Environment' -Name $_).$_ }) | Out-File "$m\user-environment.txt" -Encoding utf8
# --- machine config (Windows Terminal, hosts, repo-local real .env files) ---
New-Item -ItemType Directory -Force -Path "$root\config" | Out-Null
$wt = "$env:LOCALAPPDATA\Packages\Microsoft.WindowsTerminal_8wekyb3d8bbwe\LocalState\settings.json"
if (Test-Path $wt) { Save $wt "$root\config\windows-terminal-settings.json" }
Save "$env:WINDIR\System32\drivers\etc\hosts" "$root\config\hosts"
Save "$ClaudeToolsRoot\projects\msp-tools\quote-wizard\frontend\.env.production" "$root\config\quote-wizard.frontend.env.production"
# --- large data (optional) ---
if ($IncludeData) {
$base = "$DataDrive\claudetools-recovery\data"
$xd = @('node_modules','.venv','venv','__pycache__','target','.grepai','.pytest_cache','dist','build')
$xf = @('Thumbs.db','desktop.ini','*.pyc','*.mp3') # radio-show MP3s live on IX Web Hosting - not backed up here
$clusters = @(
'clients\valleywide\app-modernization\source-analysis',
'clients\grabb-durando\ai-demand-review',
'projects\dataforth-dos\datasheet-pipeline',
'projects\dataforth-dos\dfwds-research',
'projects\radio-show\audio-processor'
)
Write-Host "=== copying large data to $base ===" -ForegroundColor Cyan
foreach ($c in $clusters) {
if (Test-Path "$ClaudeToolsRoot\$c") { robocopy "$ClaudeToolsRoot\$c" "$base\$c" /E /R:1 /W:1 /XD $xd /XF $xf /NFL /NDL /NP | Out-Null; Write-Host "[OK] $c" }
}
}
# --- mirror small bundle to the other drives ---
foreach ($d in $Drives) {
if ($d -eq $primary) { continue }
if (Test-Path "$d\") {
Write-Host "=== mirroring bundle -> $d\claudetools-recovery ===" -ForegroundColor Cyan
robocopy $root "$d\claudetools-recovery" /E /R:1 /W:1 /XD data /NFL /NDL /NP | Out-Null
Write-Host "[OK] mirrored to $d"
}
}
Write-Host "`n[DONE] backup-to-bundle.ps1" -ForegroundColor Green

View File

@@ -0,0 +1,113 @@
<#
.SYNOPSIS
Restore local-only WIP (stashes + untracked diffs) that was rescued into the
recovery bundle's at-risk-work\ folder. Run AFTER the repos + submodules are cloned.
.DESCRIPTION
guru-rmm : each stashN-*.patch is applied to the working tree and then re-stashed,
faithfully recreating the original `git stash` entries. Patches are
processed highest-N-first so stash0 ends up on top (stash@{0}), matching
the original LIFO order. The working tree is left CLEAN (changes live in
the stash, exactly as before).
guru-connect : tmp-spec018.diff was an UNTRACKED working file, so it is copied back
into the repo as-is (not applied). Apply it yourself if/when you want it.
Non-destructive and re-runnable. If a patch won't apply cleanly (submodule moved on),
it is reported and the .patch file is left in place for manual `git apply --3way`.
ROBUSTNESS NOTES (why this is not just `git apply <file>`):
* Patch files may have been written by PowerShell redirection (UTF-16 LE/BE w/ BOM).
`git apply` only understands UTF-8/ASCII and otherwise reports
"No valid patches in input". Get-Utf8PatchPath normalizes any encoding to a
UTF-8 (no BOM) temp copy before applying.
* git writes progress/errors to stderr; capturing that with `2>&1` while
$ErrorActionPreference='Stop' turns it into a *terminating* error (PS 5.1
NativeCommandError) that aborts the whole bootstrap. Invoke-Git captures
output without that trap and returns the real exit code.
* If the submodule still has stashes, the WIP almost certainly survived the reset.
Re-applying would create DUPLICATE stashes, so we skip and report instead.
.PARAMETER BundlePath Recovery bundle root (auto-detect F:\ then E:\).
.PARAMETER ClaudeToolsRoot Default D:\claudetools.
#>
[CmdletBinding()]
param([string]$BundlePath,[string]$ClaudeToolsRoot='D:\claudetools')
$ErrorActionPreference='Stop'
# Read a patch regardless of encoding (UTF-16 LE/BE +/- BOM, UTF-8 +/- BOM) and return
# the path to a normalized UTF-8 (no BOM) temp copy that `git apply` can parse.
function Get-Utf8PatchPath($path){
$bytes = [System.IO.File]::ReadAllBytes($path)
if ($bytes.Length -ge 2 -and $bytes[0] -eq 0xFF -and $bytes[1] -eq 0xFE) { $text = [System.Text.Encoding]::Unicode.GetString($bytes,2,$bytes.Length-2) }
elseif ($bytes.Length -ge 2 -and $bytes[0] -eq 0xFE -and $bytes[1] -eq 0xFF) { $text = [System.Text.Encoding]::BigEndianUnicode.GetString($bytes,2,$bytes.Length-2) }
elseif ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) { $text = [System.Text.Encoding]::UTF8.GetString($bytes,3,$bytes.Length-3) }
else {
# No BOM: detect UTF-16 LE without BOM by counting interleaved NUL bytes in the head.
$nul = 0; $n = [Math]::Min(64,$bytes.Length)
for ($i=0; $i -lt $n; $i++) { if ($bytes[$i] -eq 0) { $nul++ } }
if ($nul -gt 8) { $text = [System.Text.Encoding]::Unicode.GetString($bytes) }
else { $text = [System.Text.Encoding]::UTF8.GetString($bytes) }
}
$text = $text -replace "`r`n","`n" # normalize to LF so git apply is happy
$tmp = [System.IO.Path]::GetTempFileName()
[System.IO.File]::WriteAllText($tmp, $text, (New-Object System.Text.UTF8Encoding($false)))
return $tmp
}
# Run git without letting native stderr (under $ErrorActionPreference='Stop') become a
# terminating error. Returns [pscustomobject]@{ Code; Output }.
function Invoke-Git([string[]]$GitArgs){
$old = $ErrorActionPreference; $ErrorActionPreference = 'Continue'
try { $out = (& git @GitArgs 2>&1 | Out-String); $code = $LASTEXITCODE }
finally { $ErrorActionPreference = $old }
[pscustomobject]@{ Code = $code; Output = ($out).Trim() }
}
if (-not $BundlePath) { foreach ($d in 'F:','E:','D:') { if (Test-Path "$d\claudetools-recovery\at-risk-work") { $BundlePath="$d\claudetools-recovery"; break } } }
$aw = "$BundlePath\at-risk-work"
if (-not $BundlePath -or -not (Test-Path $aw)) { Write-Host "[INFO] no at-risk-work folder found in bundle - nothing to restore"; return }
Write-Host "[INFO] restoring at-risk WIP from $aw" -ForegroundColor Cyan
function Have-Git($repo){ Test-Path "$repo\.git" }
# ---- guru-rmm stashes ----
$rmm = "$ClaudeToolsRoot\projects\msp-tools\guru-rmm"
if ((Test-Path "$aw\guru-rmm") -and (Have-Git $rmm)) {
$existing = (Invoke-Git @('-C',$rmm,'stash','list')).Output
if ($existing) {
Write-Host "[SKIP] guru-rmm already has stashes (local WIP survived the reset) - not re-applying to avoid duplicates:" -ForegroundColor Yellow
Write-Host $existing
Write-Host " Bundle patches remain in $aw\guru-rmm; apply by hand if you really need them." -ForegroundColor Yellow
}
elseif ((Invoke-Git @('-C',$rmm,'status','--porcelain')).Output) {
Write-Host "[WARN] guru-rmm working tree is dirty; skipping auto-restore to avoid mixing changes. Apply patches in $aw\guru-rmm manually." -ForegroundColor Yellow
} else {
# highest N first so stash0 lands at stash@{0}
$patches = Get-ChildItem "$aw\guru-rmm" -Filter '*.patch' | Sort-Object Name -Descending
foreach ($p in $patches) {
$u8 = Get-Utf8PatchPath $p.FullName
try {
$chk = Invoke-Git @('-C',$rmm,'apply','--check','--3way',$u8)
if ($chk.Code -ne 0) { Write-Host "[WARN] won't apply cleanly, left for manual restore: $($p.Name) ($($chk.Output))" -ForegroundColor Yellow; continue }
Invoke-Git @('-C',$rmm,'apply','--3way',$u8) | Out-Null
Invoke-Git @('-C',$rmm,'stash','push','-u','-m',"restored WIP: $($p.BaseName)") | Out-Null
Write-Host "[OK] re-stashed guru-rmm: $($p.BaseName)" -ForegroundColor Green
} finally { Remove-Item $u8 -Force -ErrorAction SilentlyContinue }
}
Write-Host "[INFO] guru-rmm stashes now:" -ForegroundColor Cyan
Write-Host (Invoke-Git @('-C',$rmm,'stash','list')).Output
}
}
# ---- guru-connect untracked diff ----
$gc = "$ClaudeToolsRoot\projects\msp-tools\guru-connect"
$diff = "$aw\guru-connect\tmp-spec018.diff"
if ((Test-Path $diff) -and (Test-Path $gc)) {
if (Test-Path "$gc\tmp-spec018.diff") {
Write-Host "[SKIP] guru-connect\tmp-spec018.diff already present in repo (survived the reset) - not overwriting." -ForegroundColor Yellow
} else {
Copy-Item $diff "$gc\tmp-spec018.diff" -Force
Write-Host "[OK] guru-connect\tmp-spec018.diff restored (untracked working file - 'git apply --3way tmp-spec018.diff' to apply it)" -ForegroundColor Green
}
}
Write-Host "[DONE] at-risk WIP restore" -ForegroundColor Cyan

View File

@@ -0,0 +1,147 @@
<#
.SYNOPSIS
Restore ClaudeTools secrets + machine identity from a recovery bundle
(produced by the Windows bootstrap backup) back to their real locations.
.DESCRIPTION
Two restore groups:
[home] -> out-of-repo secrets that live under the user profile
(SOPS age key, SSH keys, Claude/grok/gemini auth, git config,
PowerShell profile). These are needed BEFORE cloning repos.
[repo] -> repo-local, gitignored files that go back into D:\claudetools
(identity.json, settings.local.json, current-mode, .mcp.json,
.claude/state, ticktick tokens, dataforth oauth). These require
the claudetools repo to already be cloned.
Idempotent. Only restores files that exist in the bundle. Never overwrites a
newer file unless -Force is given.
.PARAMETER BundlePath
Path to the recovery bundle root (the folder containing 'secrets' and
'identity'). Auto-detected from F:\ then E:\ if not supplied.
.PARAMETER ClaudeToolsRoot
Where claudetools is / will be cloned. Default D:\claudetools.
.PARAMETER Group
home | repo | all (default all).
.EXAMPLE
.\restore-secrets.ps1 -Group home # before cloning repos
.\restore-secrets.ps1 -Group repo # after cloning claudetools
#>
[CmdletBinding()]
param(
[string]$BundlePath,
[string]$ClaudeToolsRoot = 'D:\claudetools',
[ValidateSet('home','repo','all')][string]$Group = 'all',
[switch]$Force
)
$ErrorActionPreference = 'Stop'
function Find-Bundle {
foreach ($d in 'F:','E:','D:') {
$p = "$d\claudetools-recovery"
if (Test-Path "$p\secrets") { return $p }
}
return $null
}
if (-not $BundlePath) { $BundlePath = Find-Bundle }
if (-not $BundlePath -or -not (Test-Path "$BundlePath\secrets")) {
throw "Recovery bundle not found. Plug in the drive or pass -BundlePath. Looked for <drive>:\claudetools-recovery\secrets"
}
Write-Host "[INFO] Using recovery bundle: $BundlePath" -ForegroundColor Cyan
function Restore-One($src, $dst) {
if (-not (Test-Path -LiteralPath $src)) { Write-Host "[SKIP] not in bundle: $src"; return }
$parent = Split-Path $dst -Parent
if ($parent -and -not (Test-Path $parent)) { New-Item -ItemType Directory -Force -Path $parent | Out-Null }
if ((Test-Path -LiteralPath $dst) -and -not $Force) {
Write-Host "[KEEP] exists (use -Force to overwrite): $dst" -ForegroundColor Yellow
return
}
Copy-Item -LiteralPath $src -Destination $dst -Force
Write-Host "[OK] $dst" -ForegroundColor Green
}
# ---------------------------------------------------------------- HOME secrets
if ($Group -in 'home','all') {
Write-Host "`n=== Restoring home-profile secrets ===" -ForegroundColor Cyan
$u = $env:USERPROFILE
$s = "$BundlePath\secrets"
# SOPS age key (CRITICAL - vault is undecryptable without it)
New-Item -ItemType Directory -Force -Path "$u\.config\sops\age" | Out-Null
New-Item -ItemType Directory -Force -Path "$env:APPDATA\sops\age" | Out-Null
Restore-One "$s\sops-age\keys.txt" "$u\.config\sops\age\keys.txt"
Restore-One "$s\sops-age\keys.txt" "$env:APPDATA\sops\age\keys.txt"
# SSH
New-Item -ItemType Directory -Force -Path "$u\.ssh" | Out-Null
if (Test-Path "$s\ssh") {
Get-ChildItem "$s\ssh" -File | ForEach-Object { Restore-One $_.FullName "$u\.ssh\$($_.Name)" }
# lock down private key perms (remove inheritance, owner-only)
Get-ChildItem "$u\.ssh" -File | Where-Object { $_.Name -notmatch '\.pub$' -and $_.Name -ne 'known_hosts' -and $_.Name -ne 'config' } | ForEach-Object {
icacls $_.FullName /inheritance:r /grant:r "$($env:USERNAME):(F)" 2>$null | Out-Null
}
}
# Claude Code auth/config
Restore-One "$s\claude\.claude.json" "$u\.claude.json"
Restore-One "$s\claude\.credentials.json" "$u\.claude\.credentials.json"
Restore-One "$s\claude\settings.json" "$u\.claude\settings.json"
Restore-One "$s\claude\keybindings.json" "$u\.claude\keybindings.json"
Restore-One "$s\claude\statusline-command.sh" "$u\.claude\statusline-command.sh"
# grok
Restore-One "$s\grok\auth.json" "$u\.grok\auth.json"
Restore-One "$s\grok\config.toml" "$u\.grok\config.toml"
Restore-One "$s\grok\agent_id" "$u\.grok\agent_id"
# gemini
Restore-One "$s\gemini\oauth_creds.json" "$u\.gemini\oauth_creds.json"
Restore-One "$s\gemini\google_accounts.json" "$u\.gemini\google_accounts.json"
Restore-One "$s\gemini\settings.json" "$u\.gemini\settings.json"
Restore-One "$s\gemini\installation_id" "$u\.gemini\installation_id"
# user-global Claude commands + plugins (not in the repo)
if (Test-Path "$s\claude-global\commands") {
New-Item -ItemType Directory -Force -Path "$u\.claude\commands" | Out-Null
Copy-Item "$s\claude-global\commands\*" "$u\.claude\commands\" -Recurse -Force
Write-Host "[OK] $u\.claude\commands\*" -ForegroundColor Green
}
if (Test-Path "$s\claude-global\plugins") {
New-Item -ItemType Directory -Force -Path "$u\.claude\plugins" | Out-Null
Copy-Item "$s\claude-global\plugins\*" "$u\.claude\plugins\" -Recurse -Force
Write-Host "[OK] $u\.claude\plugins\*" -ForegroundColor Green
}
# git global config
Restore-One "$s\git\.gitconfig" "$u\.gitconfig"
# PowerShell profile
Restore-One "$s\powershell\Microsoft.PowerShell_profile.ps1" $PROFILE
}
# ---------------------------------------------------------------- REPO-local
if ($Group -in 'repo','all') {
Write-Host "`n=== Restoring repo-local identity files ===" -ForegroundColor Cyan
if (-not (Test-Path $ClaudeToolsRoot)) {
Write-Host "[WARN] $ClaudeToolsRoot does not exist yet. Clone the repo first, then re-run with -Group repo." -ForegroundColor Yellow
} else {
$i = "$BundlePath\identity"
Restore-One "$i\identity.json" "$ClaudeToolsRoot\.claude\identity.json"
Restore-One "$i\settings.local.json" "$ClaudeToolsRoot\.claude\settings.local.json"
Restore-One "$i\current-mode" "$ClaudeToolsRoot\.claude\current-mode"
Restore-One "$i\coord-broadcasts-seen" "$ClaudeToolsRoot\.claude\coord-broadcasts-seen"
Restore-One "$i\mcp.json" "$ClaudeToolsRoot\.mcp.json"
Restore-One "$i\ticktick-tokens.json" "$ClaudeToolsRoot\mcp-servers\ticktick\.tokens.json"
Restore-One "$i\dataforth-oauth.txt" "$ClaudeToolsRoot\clients\dataforth\Oauth.txt"
if (Test-Path "$i\state") {
New-Item -ItemType Directory -Force -Path "$ClaudeToolsRoot\.claude\state" | Out-Null
Copy-Item "$i\state\*" "$ClaudeToolsRoot\.claude\state\" -Recurse -Force
Write-Host "[OK] $ClaudeToolsRoot\.claude\state\*" -ForegroundColor Green
}
}
}
Write-Host "`n[DONE] restore-secrets.ps1 ($Group)" -ForegroundColor Cyan

View File

@@ -0,0 +1,346 @@
<#
.SYNOPSIS
ClaudeTools Windows bootstrap - rebuild a workstation after a clean OS reset.
.DESCRIPTION
Installs every tool ClaudeTools needs, restores secrets + identity from the
recovery bundle, clones the repos, wires up scheduled tasks, and verifies.
Designed to be run top-to-bottom on a fresh Windows 11 install. Idempotent:
re-running skips anything already present.
ORDER OF OPERATIONS (each phase depends on the previous):
0. Preflight - winget, execution policy, UTF-8
1. Core tooling - git, node, python, rust, vscode, ollama, jq, sops, age, gh, op
2. PATH refresh - make freshly-installed tools callable this session
3. AI CLIs - claude (native), gemini (npm), grok (git-bash installer)
4. Restore secrets - age key, ssh, tool auth, git config, PS profile [home group]
5. Clone repos - claudetools + vault + submodules
6. Restore identity - identity.json, settings.local, .mcp.json, state [repo group]
7. Python deps - pip installs for MCP servers / scripts
8. Ollama models - pull qwen/codestral/nomic (optional, large)
9. Scheduled tasks - GrepAI watcher, orphan detector, smartbadge
10. Large data - restore client data from bundle (optional)
11. Verify - onboarding diagnostic
.PARAMETER BundlePath
Recovery bundle root (folder containing 'secrets'/'identity'). Auto-detect F:\ then E:\.
.PARAMETER SkipModels Skip the multi-GB ollama model pulls.
.PARAMETER RestoreData Also restore the large client data from <bundle>\data.
.PARAMETER GiteaHost Gitea base URL. Default git.azcomputerguru.com (use 172.16.3.20:3000 on-network).
.PARAMETER OnlyPhases Comma list of phase numbers to run (e.g. "1,2,3"). Default: all.
.EXAMPLE
# full rebuild, skip giant model downloads for now
.\windows-bootstrap.ps1 -SkipModels
.NOTES
Run from an elevated PowerShell for cleanest winget machine-scope installs,
though most packages also install at user scope without admin.
#>
[CmdletBinding()]
param(
[string]$BundlePath,
[switch]$SkipModels,
[switch]$RestoreData,
[string]$GiteaHost = 'https://git.azcomputerguru.com',
[string]$ClaudeToolsRoot = 'D:\claudetools',
[string]$VaultRoot = 'D:\vault',
[string]$Hostname, # target computer name; default = identity.json .machine, else GURU-5070
[string]$OnlyPhases
)
$ErrorActionPreference = 'Stop'
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
function Phase($n,$title){ if ($OnlyPhases -and ($OnlyPhases -split ',').Trim() -notcontains "$n") { return $false }; Write-Host "`n========== PHASE $n : $title ==========" -ForegroundColor Cyan; return $true }
function Info($m){ Write-Host "[INFO] $m" }
function Ok($m){ Write-Host "[OK] $m" -ForegroundColor Green }
function Warn($m){ Write-Host "[WARN] $m" -ForegroundColor Yellow }
function Have($cmd){ [bool](Get-Command $cmd -ErrorAction SilentlyContinue) }
function Refresh-Path { $env:Path = [Environment]::GetEnvironmentVariable('Path','Machine') + ';' + [Environment]::GetEnvironmentVariable('Path','User') }
function Find-Bundle {
if ($BundlePath -and (Test-Path "$BundlePath\secrets")) { return $BundlePath }
foreach ($d in 'F:','E:','D:') { if (Test-Path "$d\claudetools-recovery\secrets") { return "$d\claudetools-recovery" } }
return $null
}
# ============================================================ PHASE 0
if (Phase 0 'Preflight') {
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
try { Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass -Force } catch {}
if (-not (Have winget)) { throw "winget not found. Install 'App Installer' from the Microsoft Store, then re-run." }
Ok "winget present: $((Get-Command winget).Source)"
$script:Bundle = Find-Bundle
if ($script:Bundle) { Ok "recovery bundle: $script:Bundle" } else { Warn "no recovery bundle found - secret/identity restore phases will be skipped" }
# Hostname - a fresh Windows install is DESKTOP-xxxxx; identity.json + scheduled tasks
# + coord session IDs all expect the real name. Rename needs admin and a reboot to apply.
$target = $Hostname
if (-not $target -and $script:Bundle -and (Test-Path "$script:Bundle\identity\identity.json")) {
try { $target = (Get-Content "$script:Bundle\identity\identity.json" -Raw | ConvertFrom-Json).machine } catch {}
}
if (-not $target) { $target = 'GURU-5070' }
if ($env:COMPUTERNAME -ne $target) {
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
if ($isAdmin) {
try { Rename-Computer -NewName $target -Force -ErrorAction Stop; $script:RebootNeeded = $true; Ok "hostname: $env:COMPUTERNAME -> $target (takes effect after reboot)" }
catch { Warn "rename to '$target' failed: $($_.Exception.Message)" }
} else { Warn "hostname is '$env:COMPUTERNAME', target '$target' - run this script as Administrator to rename (or manually: Rename-Computer -NewName $target -Restart)" }
} else { Ok "hostname already '$target'" }
}
# ============================================================ PHASE 1
if (Phase 1 'Core tooling (winget)') {
$pkgs = @(
@{id='Git.Git'; cmd='git'},
@{id='OpenJS.NodeJS.LTS'; cmd='node'},
@{id='Python.Python.3.14'; cmd='py'},
@{id='Rustlang.Rustup'; cmd='cargo'},
@{id='Microsoft.VisualStudioCode'; cmd='code'},
@{id='Ollama.Ollama'; cmd='ollama'},
@{id='jqlang.jq'; cmd='jq'},
@{id='SecretsOPerationS.SOPS'; cmd='sops'},
@{id='FiloSottile.age'; cmd='age'},
@{id='GitHub.cli'; cmd='gh'},
@{id='AgileBits.1Password.CLI'; cmd='op'},
@{id='Microsoft.DotNet.SDK.8'; cmd='dotnet'}, # MSI builds / wix
@{id='Google.Protobuf'; cmd='protoc'}, # gururmm prost builds (PROTOC env)
@{id='oschwartz10612.Poppler'; cmd='pdftoppm'}, # dataforth datasheet PDF pipeline
@{id='Tailscale.Tailscale'; cmd='tailscale'} # fleet connectivity (100.x mesh)
)
foreach ($p in $pkgs) {
if (Have $p.cmd) { Ok "$($p.cmd) already installed"; continue }
Info "installing $($p.id) ..."
winget install --id $p.id --exact --silent --accept-package-agreements --accept-source-agreements --disable-interactivity
if ($LASTEXITCODE -ne 0) { Warn "winget returned $LASTEXITCODE for $($p.id) (may already be installed or need elevation)" }
}
Refresh-Path
}
# ============================================================ PHASE 2
if (Phase 2 'PATH refresh') {
Refresh-Path
foreach ($c in 'git','node','npm','py','cargo','jq','sops','age','gh','op','ollama','code','dotnet','protoc','tailscale') {
if (Have $c) { Ok "$c -> $((Get-Command $c).Source)" } else { Warn "$c still not on PATH (open a new shell after install)" }
}
# PROTOC env var for Rust prost builds (path is version-specific, so resolve it live)
$protoc = (Get-Command protoc -ErrorAction SilentlyContinue).Source
if ($protoc) { [Environment]::SetEnvironmentVariable('PROTOC',$protoc,'User'); $env:PROTOC=$protoc; Ok "PROTOC=$protoc" }
}
# ============================================================ PHASE 3
if (Phase 3 'AI CLIs') {
# Claude Code - official native installer -> %USERPROFILE%\.local\bin\claude.exe
if (Have claude) { Ok "claude already installed" } else {
Info "installing Claude Code (native installer)"
try { irm https://claude.ai/install.ps1 | iex } catch { Warn "claude install failed: $_ (manual: irm https://claude.ai/install.ps1 | iex)" }
}
# Gemini CLI - npm global
if (Have gemini) { Ok "gemini already installed" } else {
Info "installing @google/gemini-cli"
npm install -g @google/gemini-cli
}
# Grok CLI - xAI installer (bash; needs Git Bash from Phase 1)
if (Have grok) { Ok "grok already installed" } else {
$bash = 'C:\Program Files\Git\bin\bash.exe'
if (Test-Path $bash) { Info "installing grok via $bash"; & $bash -lc "curl -fsSL https://x.ai/cli/install.sh | bash" }
else { Warn "Git Bash not found; install Git first, then: bash -c 'curl -fsSL https://x.ai/cli/install.sh | bash'" }
}
Refresh-Path
$env:Path += ";$env:USERPROFILE\.local\bin;$env:USERPROFILE\.grok\bin;$env:APPDATA\npm"
# Persist the AI-CLI dirs to the User PATH so claude/grok/gemini stay callable in
# every new shell (their installers don't always add these; grok especially is a
# bare ~\.grok\bin drop that was session-only after the 2026-06-06 rebuild).
$userPath = [Environment]::GetEnvironmentVariable('Path','User')
foreach ($d in "$env:USERPROFILE\.local\bin", "$env:USERPROFILE\.grok\bin", "$env:APPDATA\npm") {
if ((Test-Path $d) -and ($userPath -notmatch [regex]::Escape($d))) { $userPath = $userPath.TrimEnd(';') + ";$d" }
}
[Environment]::SetEnvironmentVariable('Path', $userPath, 'User')
Ok "AI-CLI dirs persisted to User PATH"
}
# ============================================================ PHASE 4
if (Phase 4 'Restore home secrets + machine config') {
if ($script:Bundle) {
& "$here\restore-secrets.ps1" -BundlePath $script:Bundle -Group home
# Stable machine env vars (NOT a blanket reg import - the saved PATH has stale
# version-pinned winget paths. user-environment.reg is kept as reference only.)
[Environment]::SetEnvironmentVariable('OLLAMA_MODELS','D:\OllamaModels','User'); $env:OLLAMA_MODELS='D:\OllamaModels'
[Environment]::SetEnvironmentVariable('OLLAMA_HOST','0.0.0.0:11434','User'); $env:OLLAMA_HOST='0.0.0.0:11434'
Ok "set OLLAMA_MODELS=D:\OllamaModels, OLLAMA_HOST=0.0.0.0:11434"
# Windows Terminal settings
$wtDst = "$env:LOCALAPPDATA\Packages\Microsoft.WindowsTerminal_8wekyb3d8bbwe\LocalState\settings.json"
if (Test-Path "$script:Bundle\config\windows-terminal-settings.json") {
$p = Split-Path $wtDst -Parent
if (Test-Path $p) { Copy-Item "$script:Bundle\config\windows-terminal-settings.json" $wtDst -Force; Ok "Windows Terminal settings restored" }
else { Warn "Windows Terminal not installed yet - restore its settings.json later from config\" }
}
# hosts file (fleet Tailscale MagicDNS entries) - needs admin; merge note only
if (Test-Path "$script:Bundle\config\hosts") {
Warn "fleet hosts entries are in config\hosts - merge into $env:WINDIR\System32\drivers\etc\hosts as admin if Tailscale MagicDNS isn't resolving"
}
}
else { Warn "no bundle - skipping. Restore the SOPS age key + SSH keys manually or the vault will not decrypt." }
}
# ============================================================ PHASE 5
if (Phase 5 'Clone repos') {
if (-not (Test-Path "$ClaudeToolsRoot\.git")) {
Info "cloning claudetools -> $ClaudeToolsRoot"
git clone "$GiteaHost/azcomputerguru/claudetools.git" $ClaudeToolsRoot
Push-Location $ClaudeToolsRoot
Info "initializing submodules (gururmm / guruconnect)"
git submodule update --init --recursive
Pop-Location
} else { Ok "claudetools repo already present" }
if (-not (Test-Path "$VaultRoot\.git")) {
Info "cloning vault -> $VaultRoot"
git clone "$GiteaHost/azcomputerguru/vault.git" $VaultRoot
} else { Ok "vault repo already present" }
# safe.directory entries (mirror the prior machine)
foreach ($d in $ClaudeToolsRoot,$VaultRoot,"$ClaudeToolsRoot/projects/msp-tools/guru-rmm") {
git config --global --add safe.directory ($d -replace '\\','/') 2>$null
}
}
# ============================================================ PHASE 6
if (Phase 6 'Restore repo-local identity + at-risk WIP') {
if ($script:Bundle) {
& "$here\restore-secrets.ps1" -BundlePath $script:Bundle -Group repo -ClaudeToolsRoot $ClaudeToolsRoot
# Recreate local-only WIP (guru-rmm stashes, guru-connect untracked diff) that
# would otherwise have been lost - faithfully puts the stashes back as stashes.
& "$here\restore-at-risk-work.ps1" -BundlePath $script:Bundle -ClaudeToolsRoot $ClaudeToolsRoot
}
else { Warn "no bundle - you must hand-create .claude/identity.json (see CLAUDE.md multi-user section)" }
# Non-interactive git auth (Mike's hard requirement: git must NEVER hang on a
# Git Credential Manager password prompt). setup-git-auth.sh primes the `store`
# credential helper from the vault Gitea token, scoped to each repo's actual remote
# host. Needs the age key (Phase 4) + identity.json (above) + vault repo (Phase 5).
# Idempotent + fail-silent; also runs from the SessionStart hook in settings.json.
$ghauth = "$ClaudeToolsRoot\.claude\scripts\setup-git-auth.sh"
$gbash = 'C:\Program Files\Git\bin\bash.exe'
if ((Test-Path $ghauth) -and (Test-Path $gbash)) {
Info "priming non-interactive git auth (vault token -> credential store)"
& $gbash "$ghauth"
Ok "git credential store primed; GIT_TERMINAL_PROMPT=0 enforced via .claude/settings.json env"
} else { Warn "setup-git-auth.sh or Git Bash missing - prime git creds manually so pushes don't prompt" }
}
# ============================================================ PHASE 7
if (Phase 7 'Python deps + .NET tools') {
# WiX toolset (MSI builds, e.g. gururmm agent) - dotnet global tool
if (Have dotnet) {
if (dotnet tool list --global 2>$null | Select-String '\bwix\b') { Ok "wix tool already installed" }
else { Info "installing wix dotnet tool"; dotnet tool install --global wix 2>$null }
}
# IMPORTANT: ClaudeTools uses TWO python interpreters on Windows and they must
# BOTH have the deps, or pieces silently break:
# - `py` -> Python 3.14 : vault yaml-query.py (get-field), helper/skill
# scripts, scheduled tasks (detect_orphaned_sessions)
# - `python` -> Python 3.12 : the interpreter `.mcp.json` launches the MCP
# servers with (ticktick needs httpx + mcp)
# Installing into only one leaves the other broken (the 2026-06-06 rebuild shipped
# with ticktick MCP dead = no httpx/mcp in 3.12, and vault get-field dead = no
# PyYAML in 3.14). De-dupe by real sys.executable so a single install isn't run twice.
$interps = @(); $seen = @{}
foreach ($cand in 'py','python','python3') {
if (Have $cand) {
$real = (& $cand -c "import sys;print(sys.executable)" 2>$null)
if ($real -and -not $seen[$real]) { $seen[$real] = $true; $interps += $cand }
}
}
if (-not $interps) { Warn "no python interpreter found - skip python deps" }
else {
$reqs = Get-ChildItem $ClaudeToolsRoot -Recurse -Filter 'requirements*.txt' -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -notmatch '\\(node_modules|\.venv|venv|target)\\' }
# baseline libs used by helper scripts / MCP / vault across the harness
$baseline = @('requests','paramiko','mcp','httpx','pyyaml','websocket-client')
foreach ($ic in $interps) {
Info "[$ic] upgrading pip"; & $ic -m pip install --upgrade pip 2>$null
foreach ($r in $reqs) { Info "[$ic] pip install -r $($r.Name)"; & $ic -m pip install -r $r.FullName 2>$null }
Info "[$ic] baseline libs"; & $ic -m pip install @baseline 2>$null
}
Ok "python deps installed into: $($interps -join ', ') (best-effort)"
}
}
# ============================================================ PHASE 8
if (Phase 8 'Ollama models') {
# Expected model set for THIS machine (identity.json prose_model + OLLAMA.md routing):
# nomic-embed-text - REQUIRED for GrepAI semantic search (embeddings)
# qwen3:8b - prose_model qwen3:14b - heavier prose
# codestral:22b - code suggestions qwen3.6:latest - structured/JSON + classify
# All five live on D:\OllamaModels (~48 GB) and SURVIVE an OS reset when D: is intact,
# so a normal rebuild pulls NOTHING. Only a wiped D: triggers the full re-download.
$models = @('nomic-embed-text:latest','qwen3:8b','qwen3:14b','codestral:22b','qwen3.6:latest')
if ($SkipModels) { Warn "-SkipModels set, skipping model pulls" }
elseif (Have ollama) {
if (-not $env:OLLAMA_MODELS) { [Environment]::SetEnvironmentVariable('OLLAMA_MODELS','D:\OllamaModels','User'); $env:OLLAMA_MODELS='D:\OllamaModels' }
# GOTCHA (2026-06-06): right after login `ollama list` can return EMPTY even though
# D:\OllamaModels is fully populated - the tray app's server needs a few seconds to
# hydrate its model-list cache. Do NOT treat an empty list as "models gone" or you
# re-download 48 GB for nothing. If manifests are on disk, restart + wait first.
$listed = (ollama list 2>$null | Out-String).Trim() -split "`n" | Select-Object -Skip 1
if ((Test-Path 'D:\OllamaModels\manifests') -and -not $listed) {
Warn "ollama list empty but D:\OllamaModels populated - restarting ollama, waiting for hydration"
Get-Process 'ollama','ollama app' -ErrorAction SilentlyContinue | Stop-Process -Force; Start-Sleep 2
$oapp = "$env:LOCALAPPDATA\Programs\Ollama\ollama app.exe"
if (Test-Path $oapp) { Start-Process $oapp } else { Start-Process ollama -ArgumentList 'serve' -WindowStyle Hidden }
Start-Sleep 10
}
$have = (ollama list 2>$null | Out-String)
foreach ($m in $models) {
$short = $m -replace ':latest$',''
if ($have -match [regex]::Escape($short)) { Ok "$m already present on D:\OllamaModels (no download)" }
else { Info "ollama pull $m"; ollama pull $m }
}
} else { Warn "ollama missing - skip" }
}
# ============================================================ PHASE 9
if (Phase 9 'Scheduled tasks') {
$tdir = "$script:Bundle\manifests\scheduled-tasks"
if ($script:Bundle -and (Test-Path $tdir)) {
Get-ChildItem $tdir -Filter *.xml | ForEach-Object {
$name = ($_.BaseName -replace '_',' ')
try {
$xml = Get-Content $_.FullName -Raw
Register-ScheduledTask -TaskName $name -Xml $xml -Force -ErrorAction Stop | Out-Null
Ok "registered task: $name"
} catch { Warn "task '$name' import failed: $($_.Exception.Message) (paths/user may differ - re-create manually)" }
}
} else { Warn "no exported tasks in bundle - skip (see manifests\scheduled-tasks)" }
}
# ============================================================ PHASE 10
if (Phase 10 'Large client data (optional)') {
if ($RestoreData -and $script:Bundle -and (Test-Path "$script:Bundle\data")) {
Info "restoring large data $script:Bundle\data -> $ClaudeToolsRoot"
robocopy "$script:Bundle\data" $ClaudeToolsRoot /E /R:1 /W:1 /NFL /NDL /NP | Out-Null
Ok "large data restored"
} else { Warn "skipped (pass -RestoreData to restore client data clusters)" }
}
# ============================================================ PHASE 11
if (Phase 11 'Verify') {
$diag = "$ClaudeToolsRoot\.claude\scripts\onboarding-diagnostic.ps1"
if (Test-Path $diag) { Info "running onboarding diagnostic"; & $diag }
else { Warn "diagnostic not found - run '/self-check' inside Claude Code to verify wiring" }
Write-Host "`n[NEXT] Interactive logins that may need a refresh (tokens expire):" -ForegroundColor Cyan
Write-Host " claude (if .credentials.json expired: run 'claude' and /login)"
Write-Host " gh auth login op signin gemini (browser) grok login"
Write-Host " Verify vault: bash $ClaudeToolsRoot/.claude/scripts/vault.sh list"
}
if ($script:RebootNeeded) {
Write-Host "`n[REBOOT] Hostname was changed to '$target' - REBOOT for it to take effect." -ForegroundColor Yellow
Write-Host " (scheduled tasks + coord session IDs read the hostname, so reboot before relying on them)"
}
Write-Host "`n[DONE] windows-bootstrap.ps1 complete." -ForegroundColor Green

View File

@@ -14,11 +14,10 @@ Please create a comprehensive git checkpoint with the following steps:
- Run `git diff` to see detailed changes in tracked files
- Run `git log -5 --oneline` to understand the commit message style of this repository
3. **Stage everything**:
3. **Decide what will be staged** (do NOT stage yet):
- Add ALL tracked changes (modified and deleted files)
- Add ALL untracked files (new files)
- Use `git add -A` or `git add .` to stage everything
- Identify all tracked changes (modified/deleted) and untracked (new) files via `git status`.
- Staging is done **atomically with the commit, under the repo lock, in step 5** — do not run a separate `git add` here. This prevents a concurrent session in a shared worktree (e.g. ClaudeTools) from having its dirty files swept into this checkpoint.
4. **Draft commit message body via Ollama** (documentation engine):
@@ -34,7 +33,7 @@ Please create a comprehensive git checkpoint with the following steps:
# Ollama drafts the body; fallback to Claude if unavailable
if [ -n "$OLLAMA" ]; then
BODY=$(py -c "
BODY=$(bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" -c "
import urllib.request, json
diff = open('C:/Users/guru/AppData/Local/Temp/checkpoint_diff.txt', encoding='utf-8').read()
prompt = 'Write a git commit message BODY only (not the summary line). Imperative mood. What changed and why. No filler. Under 150 words.\n\nDIFF:\n' + diff
@@ -49,7 +48,17 @@ print(res['message']['content'])
- **Body**: Ollama draft (Claude reviews); Claude writes directly if Ollama unavailable
- **Footer**: `Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>`
5. **Execute the commit**: Create the commit with the properly formatted message following this repository's conventions.
5. **Execute the commit (locked)**: Write the final message (summary line + body + footer) to a temp file, then stage + commit **atomically under the repo's commit lock** so concurrent sessions can't interleave or get swept in:
```bash
# MSG = path to the composed commit-message file; LOCK = the shared lock wrapper
LOCK="${CLAUDETOOLS_ROOT:-/d/claudetools}/.claude/scripts/sync-lock.sh"
bash "$LOCK" run bash -c 'git add -A && git commit -F "$1"' _ "$MSG"
```
- The lock is scoped to the **current repo** (`git rev-parse --show-toplevel`/.git), so this serializes correctly whether the checkpoint is in ClaudeTools (shares the same lock as `/sync` and `/scc`) or in a project repo (its own lock). The wrapper errors out (exit 2) if you're not in a git repo.
- If it **exits 75**, another commit/sync holds the lock — wait briefly and retry, or report "checkpoint deferred".
- This is a **local commit only** (no push), matching checkpoint's purpose.
- `$CLAUDETOOLS_ROOT` should be set per-machine; the `/d/claudetools` fallback is for this box only — on Mac/Linux it resolves from the env var.
## Part 2: Verify Git Checkpoint

View File

@@ -0,0 +1,30 @@
---
name: discord-dm
description: Send a Discord message to an org member's DMs or a team channel via the ClaudeTools bot. Prepopulated with user + channel IDs. Use for copy-paste-friendly delivery of wrapped command lines (consent links, long one-liners) or to ping someone directly.
---
# /discord-dm — direct Discord messaging
Thin entry point to the `discord-dm` skill. Engine: `.claude/scripts/discord-dm.sh`.
## Usage
```
/discord-dm <recipient> <message> Send a DM (mike|howard|rob|winter) or post to a channel (#bot-alerts|#dev-alerts)
/discord-dm list Show known users + channel IDs
```
Examples:
```bash
bash .claude/scripts/discord-dm.sh mike "https://login.microsoftonline.com/.../adminconsent?client_id=..."
bash .claude/scripts/discord-dm.sh dev "build promoted to stable"
echo "$LONG_LINK" | bash .claude/scripts/discord-dm.sh mike
```
## Standing rule
Any **wrapped / long single-line output** (M365 consent links, long CLI one-liners,
URLs with query strings) should be **DM'd to `mike`** so it's cleanly copy-pasteable
rather than mangled by terminal wrapping. See `.claude/skills/discord-dm/SKILL.md`
for the recipient forms, prepopulated directory, and gotchas.

View File

@@ -1,473 +1,101 @@
GuruRMM Feature Request — Comprehensive Analysis & Specification
# GuruRMM Feature Request -> RMM Thoughts
When Howard (or Mike) submits a feature request, conduct full research and produce a detailed specification with implementation recommendations.
When Howard (or Mike) submits a GuruRMM feature request, **capture it as a raw entry in
the RMM Thoughts backlog** — do NOT jump straight to a full spec or the roadmap. Those
are downstream, decision-gated stages.
Pipeline (see `.claude/memory/feedback_rmm_thoughts_backlog.md`):
**THOUGHT (this command, Status: Raw) -> DISCUSS -> SPEC (`/shape-spec` -> `specs/<slug>/`)
-> ROADMAP (`docs/FEATURE_ROADMAP.md`) -> BUILD.**
Backlog doc: `projects/msp-tools/guru-rmm/docs/RMM_THOUGHTS.md`.
---
## Phase 1 — Context Loading
## Phase 1 — Light triage (Ollama, optional)
1. **Read identity and machine info:**
- `.claude/identity.json` — hostname, user, Ollama endpoint
Read `.claude/identity.json` for the user (Howard/Mike) and the Ollama endpoint
(`.ollama.endpoint`). Call Ollama `qwen3.6:latest` (strict JSON) for a LIGHT triage —
NOT deep research, NOT a spec:
2. **Read project documentation:**
- `projects/msp-tools/guru-rmm/docs/FEATURE_ROADMAP.md` — existing features, structure, priorities
- `projects/msp-tools/guru-rmm/docs/UI_GAPS.md` — current UI implementation status
- `.claude/CODING_GUIDELINES.md` — code standards, patterns, architecture rules
- `projects/msp-tools/guru-rmm/CONTEXT.md` — current project state, tech stack, architecture
3. **Determine Ollama endpoint:**
- `DESKTOP-0O8A1RL`: `http://localhost:11434`
- All other machines: `http://100.92.127.64:11434`
---
## Phase 2 — Initial Classification (Ollama)
Call Ollama with model `qwen3.6:latest` (strict JSON) to perform initial classification:
**Prompt:**
```
You are analyzing a feature request for GuruRMM, a Rust/Axum/TypeScript RMM tool for MSPs.
Roadmap sections: Core Agent Features, Server/API Features, Dashboard & UI, Platform & Infrastructure, Integrations, Security Features, Future Considerations.
Feature request: $ARGUMENTS
Respond with JSON only:
{
"section": "...",
"subsection": "...",
"priority": "P1|P2|P3",
"brief_summary": "1-2 sentence plain English summary",
"similar_features": ["list of similar/related features that might already exist"],
"research_needed": ["list of areas requiring investigation before implementation"]
}
You are triaging a GuruRMM feature request into a backlog. Request: $ARGUMENTS
Respond JSON only:
{"title": "short kebab-or-title-case name", "summary": "1-2 sentence plain-English summary",
"section_guess": "Core Agent | Server/API | Dashboard & UI | Platform | Integrations | Security | Alerting | Other",
"priority_guess": "P1|P2|P3"}
```
If Ollama unreachable, perform classification yourself.
If Ollama is unreachable, do this triage yourself. Do NOT search the codebase or write a
spec at this stage.
---
## Phase 3Research & Investigation
## Phase 2Append to RMM Thoughts
Based on the classification and research_needed list:
Append a new entry to the bottom of `projects/msp-tools/guru-rmm/docs/RMM_THOUGHTS.md`:
### 3.1 — Codebase Search
Search for similar/related implementations:
- Use Grep to search for related functionality in `projects/msp-tools/guru-rmm/`
- Check `server/src/` for API patterns
- Check `agent/src/` for agent-side functionality
- Check `dashboard/src/` for UI patterns
- Identify existing code that could be extended vs. new code needed
### 3.2 — External Research (if needed)
If the feature involves:
- Industry standards (e.g., SNMP, Syslog, API protocols): WebSearch for best practices
- Security implications: Research common vulnerabilities and mitigations
- Third-party integrations: Check if APIs/SDKs exist
- Platform-specific behavior: Research OS-level APIs (Windows/Linux/macOS)
### 3.3 — Architecture Analysis
Consider:
- Where does this feature fit in the architecture? (agent, server, dashboard, all three?)
- What database schema changes are needed?
- What API endpoints are needed?
- Are there performance/scalability implications?
- Security considerations?
---
## Phase 4 — Consult Coding Guidelines
Read `.claude/CODING_GUIDELINES.md` and identify relevant patterns:
- Error handling requirements
- API design patterns
- Database conventions
- Frontend patterns
- Security requirements
- Testing requirements
---
## Phase 5 — Specification Generation (Ollama)
Use Ollama with model `qwen3:14b` (prose) to generate comprehensive specification:
**Prompt:**
```
You are writing a detailed implementation specification for a GuruRMM feature.
FEATURE REQUEST: $ARGUMENTS
RESEARCH FINDINGS:
- Classification: <section/subsection/priority>
- Similar existing features: <list>
- Codebase search results: <relevant files/patterns found>
- External research: <standards, best practices, security considerations>
- Architecture fit: <where it belongs in the system>
CODING GUIDELINES REQUIREMENTS:
<relevant excerpts from CODING_GUIDELINES.md>
Write a comprehensive specification with these sections:
1. OVERVIEW
- What the feature does (2-3 sentences)
- User-facing benefit
- Primary use cases
2. SCOPE
- What's included in v1
- What's explicitly out of scope (for future)
- Success criteria
3. ARCHITECTURE
- Components involved (agent/server/dashboard)
- Data flow
- Database schema changes
- API endpoints needed
4. IMPLEMENTATION DETAILS
Agent (if applicable):
- Files to modify/create
- Rust structs/enums needed
- IPC commands (if any)
Server (if applicable):
- API routes
- Database migrations
- Business logic modules
Dashboard (if applicable):
- New pages/components
- State management
- API integration
5. SECURITY CONSIDERATIONS
- Authentication/authorization requirements
- Input validation
- Audit logging
- Potential vulnerabilities and mitigations
6. TESTING STRATEGY
- Unit tests needed
- Integration tests
- Manual test scenarios
7. ROLLOUT PLAN
- Feature flag approach
- Backward compatibility
- Migration path
- Documentation needs
8. EFFORT ESTIMATE
- Small (1-2 days), Medium (3-5 days), Large (1-2 weeks), X-Large (2+ weeks)
- Breakdown by component
Be specific and actionable. Reference actual file paths, struct names, and patterns from the codebase.
```
If Ollama unreachable, write the specification yourself using the research findings.
---
## Phase 6 — Roadmap Placement Analysis
Analyze the FEATURE_ROADMAP.md structure to determine:
1. **Exact placement:** Which existing subsection does this belong in? Or does it need a new subsection?
2. **Build sequencing:** Based on the roadmap structure and existing priorities:
- What features must be built before this one? (dependencies)
- What features does this unblock? (enables)
- Which sprint/milestone does this fit into?
3. **Priority justification:**
- P1: Blocks other critical features, security-critical, or MVP requirement
- P2: Important for competitive parity, customer requests, or usability
- P3: Nice-to-have, future enhancement, or edge case
---
## Phase 7 — Write Specification Document
Create a new file: `projects/msp-tools/guru-rmm/docs/specs/SPEC-XXX-<feature-name>.md`
Where XXX is the next available number (check existing specs directory).
**File format:**
```markdown
# SPEC-XXX: <Feature Name>
**Status:** Proposed
**Priority:** P1/P2/P3
**Requested By:** <Howard|Mike> (<date>)
**Estimated Effort:** <Small|Medium|Large|X-Large>
## <Title>
- Added: <Howard|Mike>, <YYYY-MM-DD> | Status: Raw | section guess: <section> | priority guess: <P?>
---
## Overview
<2-3 sentence summary>
**Use Cases:**
- <primary use case>
- <secondary use case>
**Success Criteria:**
- <measurable criteria>
---
## Scope
### Included in v1
- <feature 1>
- <feature 2>
### Explicitly Out of Scope
- <future enhancement>
---
## Architecture
### Components
- **Agent:** <what agent does>
- **Server:** <what server does>
- **Dashboard:** <what dashboard does>
### Data Flow
<step-by-step description or diagram>
### Database Schema
```sql
-- New tables or columns
<the request, in the submitter's words> <one-line triage summary if it adds clarity>
```
### API Endpoints
- `POST /api/...` — <description>
- `GET /api/...` — <description>
Keep it short — it is a RAW thought, not a spec. Do not embellish or design it.
---
## Implementation Details
## Phase 3 — Notify + track
### Agent (`agent/src/`)
**Files to modify:**
- `agent/src/xyz.rs` — <what changes>
**New structs/enums:**
```rust
// Example code
```
### Server (`server/src/`)
**Files to modify:**
- `server/src/routes/xyz.rs` — <what changes>
**Database migrations:**
- `migrations/YYYYMMDD_feature_name.sql`
### Dashboard (`dashboard/src/`)
**New components:**
- `dashboard/src/components/XyzFeature.tsx` — <description>
**API integration:**
- Use `useQuery` for GET, `useMutation` for POST/PUT
- **Coord todo** (so it is visible fleet-wide), via `coord` skill:
`todo add "RMM THOUGHT (Raw): <title> — <summary>. See docs/RMM_THOUGHTS.md." --project gururmm --auto --source "feature-request by <who> <date>"`
- **If Howard submitted it**, send a coord message so Mike sees it:
`msg send ALL "RMM Thought added: <title>" "<who> added a GuruRMM thought (Status: Raw) to docs/RMM_THOUGHTS.md: <summary>. Ready to discuss when you are — not spec'd or roadmapped yet."`
---
## Security Considerations
- **Authentication:** <requirements>
- **Authorization:** <who can access>
- **Input Validation:** <validation rules>
- **Audit Logging:** <what to log>
- **Threat Model:** <potential attacks and mitigations>
---
## Testing Strategy
### Unit Tests
- `agent/tests/xyz_test.rs` — <test scenarios>
- `server/tests/api/xyz_test.rs` — <test scenarios>
### Integration Tests
- <end-to-end test scenarios>
### Manual Testing
1. <test step 1>
2. <test step 2>
---
## Rollout Plan
1. **Feature flag:** `feature.xyz.enabled` (default: false)
2. **Database migration:** Apply schema changes
3. **Agent update:** Deploy agent with feature flag check
4. **Dashboard deploy:** UI available when feature enabled
5. **Documentation:** Update user guide
### Backward Compatibility
<how older agents/servers handle this>
---
## Dependencies
**Must be completed first:**
- <existing feature or infrastructure>
**Enables future features:**
- <what this unblocks>
---
## Open Questions
- <question 1>
- <question 2>
---
## References
- Related roadmap section: <link>
- Similar implementations: <links to code>
- External documentation: <links>
---
**Next Steps:**
1. Review specification with team
2. Refine based on feedback
3. Move to sprint backlog
4. Assign to developer
```
---
## Phase 8 — Update Roadmap
Add or update the feature in `FEATURE_ROADMAP.md`:
- If it fits an existing subsection, add it there
- If it needs a new subsection, create one
- Link to the spec document: `[Feature Name](docs/specs/SPEC-XXX-feature-name.md) - P2`
- Add checkboxes for sub-tasks if applicable
---
## Phase 9 — Commit Changes
## Phase 4 — Commit (docs-only, gururmm repo)
```bash
cd projects/msp-tools/guru-rmm
git add docs/specs/SPEC-XXX-feature-name.md docs/FEATURE_ROADMAP.md
git commit -m "spec: add SPEC-XXX <feature name>
Comprehensive specification for <brief description>.
Requested by <Howard|Mike>.
- Full architecture analysis
- Implementation details across agent/server/dashboard
- Security considerations
- Effort estimate: <Small|Medium|Large|X-Large>
- Priority: P1/P2/P3
- Added to roadmap under <section>/<subsection>"
git push origin main
git checkout -b docs/rmm-thought-<slug>
git add docs/RMM_THOUGHTS.md
git commit -m "docs(rmm-thoughts): add thought - <title> (requested by <who>)" # + Co-Authored-By trailer
git fetch origin && git rebase origin/main
git push origin docs/rmm-thought-<slug>:main
git checkout main && git merge --ff-only origin/main && git branch -d docs/rmm-thought-<slug>
```
Then update submodule pointer in parent repo:
```bash
cd /Users/azcomputerguru/ClaudeTools
git add projects/msp-tools/guru-rmm
git commit -m "chore: update guru-rmm submodule (SPEC-XXX <feature name>)"
git push origin main
```
Do NOT touch the parent repo submodule pointer.
---
## Phase 10Send Coord Message (if requested by Howard)
## Phase 5Respond
If Howard submitted this (not Mike), send a coord message:
```bash
curl -s -X POST http://172.16.3.30:8001/api/coord/messages \
-H "Content-Type: application/json" \
-d '{
"from_session": "<HOSTNAME>/claude-main",
"to_session": "ALL_SESSIONS",
"project_key": "gururmm",
"subject": "Feature Spec Complete: <feature name>",
"body": "Howard submitted a feature request. Full specification created.\n\nSPEC: docs/specs/SPEC-XXX-<feature-name>.md\n\nPriority: <P1/P2/P3>\nEffort: <Small|Medium|Large|X-Large>\nPlacement: <section>/<subsection>\n\nSummary:\n<2-3 sentence summary>\n\nReady for review and sprint planning."
}'
```
---
## Phase 11 — Response to User
Provide a comprehensive summary:
Tell the user the request was **added to RMM Thoughts at Status: Raw** — summarize it,
and say it will be discussed before any spec or roadmap entry. Do NOT claim a spec was
created or that it is on the roadmap.
```
[SUCCESS] Feature specification created
[OK] Added to RMM Thoughts (Status: Raw)
SPEC-XXX: <Feature Name>
Priority: P1/P2/P3
Effort: <Small|Medium|Large|X-Large>
Placement: <section>/<subsection>
<Title> (section guess: <section> | priority guess: <P?>)
<summary>
OVERVIEW
<2-3 sentence summary>
KEY COMPONENTS
- Agent: <brief>
- Server: <brief>
- Dashboard: <brief>
SECURITY CONSIDERATIONS
- <key security points>
DEPENDENCIES
- Requires: <list>
- Enables: <list>
FILES CREATED
- docs/specs/SPEC-XXX-<feature-name>.md (full specification)
- Updated FEATURE_ROADMAP.md
The specification includes:
✓ Complete architecture analysis
✓ Implementation details for all components
✓ Security threat model and mitigations
✓ Testing strategy
✓ Rollout plan with feature flags
✓ Effort breakdown
<If Howard submitted:>
Coord message sent to Mike for review and sprint planning.
<Next steps based on priority:>
P1: Schedule for immediate sprint
P2: Add to near-term backlog
P3: Track for future consideration
Next: we discuss it -> /shape-spec if approved -> roadmap -> build.
Tracked: coord todo <id>.<if Howard: coord message sent to Mike.>
```
---
## Error Handling
- If Ollama unreachable: Perform all analysis yourself (no degradation)
- If coord API fails: Warn user but continue (they can manually notify Mike)
- If spec number conflicts: Check existing specs and use next available
- If roadmap section unclear: Create new subsection rather than force-fit
---
## Notes
- This command can take 2-5 minutes due to research and specification generation
- The specification is a living document — can be refined during sprint planning
- Feature flags ensure safe rollout even for partially complete features
- Effort estimates are initial and may be revised during implementation
- This command does NOT auto-create a SPEC-XXX doc or a roadmap entry anymore. The old
behaviour (full Ollama spec generation + roadmap edit on every request) jumped past the
discuss stage; spec work now happens via `/shape-spec` once a thought is approved.
- To advance a thought later: discuss it (-> Status: Discussed), `/shape-spec` it
(-> Spec'd, `specs/<slug>/`), then add it to `FEATURE_ROADMAP.md` (-> Roadmapped).
- Ollama unreachable: do the triage yourself, no degradation. Coord API down: warn and
continue (the doc commit is the durable record).

View File

@@ -25,7 +25,7 @@ determine what to post (the most recent technical problem solved, fix documented
| DB host | localhost (on IX) |
| DB name | azcompu_flarum |
| DB user | azcompu_flarum |
| DB pass | `Fl@rum2026!CGS` |
| DB pass | vault: `services/flarum-community.sops.yaml credentials.db_password` |
| IX SSH | root@172.16.3.10 — password from vault: `infrastructure/ix-server.sops.yaml credentials.password` |
| Admin user_id | 1 (MikeSwanson) |
@@ -219,7 +219,7 @@ The closing nowdoc marker `FLARUM_POST_XML_END;` must be at column 0 with no lea
<?php
ini_set('display_errors', 1); error_reporting(E_ALL);
$dsn = 'mysql:host=localhost;dbname=azcompu_flarum;charset=utf8mb4';
$pdo = new PDO($dsn, 'azcompu_flarum', 'Fl@rum2026!CGS', [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$pdo = new PDO($dsn, 'azcompu_flarum', '<DB_PASS from vault services/flarum-community.sops.yaml>', [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$user_id = 1; $tag_id = %%TAG_ID%%;
$title = %%TITLE_PHP%%;

View File

@@ -2,6 +2,8 @@
Read and send mail for an Arizona Computer Guru mailbox via Microsoft Graph, using the shared **Claude-MSP-Access** app. Defaults to the mailbox of the user running it (from `identity.json`).
> **[BLOCKED 2026-06-14]** The `Claude-MSP-Access` app (`fabb3421`) was **DELETED** from the azcomputerguru.com tenant, so every token request returns **AADSTS700016** and this command cannot read or send until a replacement mail-capable app is provisioned. Decision (2026-06-15): the replacement is the **Exchange Operator** suite tier (`exchange-op`, `b43e7342-5b4b-492f-890f-bb5a4f7f40e9`) once `Mail.Send` (+ optionally `Mail.ReadWrite`/`Contacts`) is added to its manifest and consented — Mail.Send's real use is IR victim-notification during mailbox takeovers, so it lives in the suite. NOT yet provisioned. If a token fails with `AADSTS700016`, this is why — do not retry; surface this note. When provisioned, repoint `client_id` (API Configuration + the `py` helper) to `b43e7342...` and the vault path to `computerguru-exchange-operator.sops.yaml`. See `errorlog.md` and `remediation-tool/references/gotchas.md`.
## Usage
```
@@ -64,7 +66,7 @@ VAULT="$REPO_ROOT/.claude/scripts/vault.sh"
All Graph calls go through this `py` helper. It reads the secret from the vault, caches the token, and exposes `get`/`post`. Reuse the pattern per command.
```bash
py - "$MAILBOX" "$1" <<'PY'
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" - "$MAILBOX" "$1" <<'PY'
import os, sys, json, time, subprocess, urllib.request, urllib.parse, re
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
MAILBOX = sys.argv[1]

View File

@@ -0,0 +1,36 @@
# /onboard365 — Single-consent M365 tenant onboarding
Onboard a customer Microsoft 365 tenant to the ComputerGuru remediation app suite with **one**
customer admin-consent click. Thin entry point to the `onboard365` skill.
## Usage
```
/onboard365 <domain|tenant-id> Smart: print the consent link if not yet consented,
or provision the whole suite if it is.
/onboard365 link <domain> Just generate the single Tenant Admin consent URL.
/onboard365 status <domain> Dry-run: show current consent / role state.
/onboard365 provision <domain> After the customer consents: provision all apps + roles.
```
## What it does
The customer Global Admin consents once to **ComputerGuru Tenant Admin**. Using that grant,
`onboard-tenant.sh` (reused from the `remediation-tool` skill) then creates the service
principals for Security Investigator, Exchange Operator, User Manager, and (if MDE-licensed)
Defender Add-on, grants all their Graph/EXO/Defender permissions, and assigns the required
Entra directory roles — no further customer clicks.
## Implementation
1. Read the full playbook in `.claude/skills/onboard365/SKILL.md`.
2. Run `bash .claude/skills/onboard365/scripts/onboard365.sh <subcommand> <domain>`
(the script auto-locates the reused remediation-tool scripts and the vault).
3. Confirm the target tenant with the user before generating a link, and again before
`provision` (high-privilege, customer-facing).
4. After a clean provision, **record it**: set the tenant's `Onboarded` column to `YES` in the
REPO copy of `remediation-tool/references/tenants.md` and note the onboarding in the client
wiki. (See SKILL.md → Recording.)
This is the front door; once a tenant is onboarded, breach checks and remediation are the
`remediation-tool` skill.

View File

@@ -22,7 +22,7 @@ Reconstruct a session log from a Claude Code or Grok transcript when a session c
Run the detector in scan-only mode and present the table to the user:
```bash
py .claude/scripts/detect_orphaned_sessions.py --dry-run
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" .claude/scripts/detect_orphaned_sessions.py --dry-run
```
The table shows every past-idle, not-yet-processed transcript with its uuid, mtime, `substantive`/`saved`/`orphan` verdicts, classified scope, and the path a recovery would write to. Point the user at the rows where `orphan` is `YES` — those are unsaved substantive sessions. Nothing is written.
@@ -36,7 +36,7 @@ This is a **reviewed** recovery. Claude is the editor, not a passive writer.
1. **Generate the draft** (prints to stdout, writes nothing):
```bash
py .claude/scripts/recover_session.py --uuid <uuid> --print
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" .claude/scripts/recover_session.py --uuid <uuid> --print
```
(or `--latest`). The draft contains:
@@ -103,6 +103,6 @@ In `/recover` flows, if the chosen orphan or explicit id lives under a Grok sess
Example (manual):
```bash
py .claude/scripts/recover_grok_session.py --latest --print
py .claude/scripts/recover_grok_session.py --uuid 019e8b67-f97e-7b33-9c45-ec34b342d3eb --auto
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" .claude/scripts/recover_grok_session.py --latest --print
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" .claude/scripts/recover_grok_session.py --uuid 019e8b67-f97e-7b33-9c45-ec34b342d3eb --auto
```

View File

@@ -162,11 +162,13 @@ Allowed actions and which tier handles them:
|---|---|---|
| `revoke-sessions` | `user-manager` | Graph `POST /users/{upn}/revokeSignInSessions` |
| `disable-account` | `user-manager` | Graph `PATCH /users/{upn}` with `accountEnabled: false` |
| `password-reset` | `user-manager` | Graph `PATCH /users/{upn}` with new `passwordProfile` |
| `password-reset` | `tenant-admin` | `scripts/reset-password.sh <tenant> <upn> <new-pw> [--force-change]` (Graph `PATCH /users/{upn}` passwordProfile, with JIT admin elevation — see note) |
| `disable-forwarding` | `exchange-op` | Exchange REST `Set-Mailbox -ForwardingAddress $null -ForwardingSmtpAddress $null -DeliverToMailboxAndForward $false` |
| `remove-inbox-rules` | `exchange-op` | Exchange REST `Remove-InboxRule` per non-default rule (ask which to keep first) |
| `disable-smtp-auth` | `exchange-op` | Exchange REST `Set-CASMailbox -SmtpClientAuthenticationDisabled $true` |
**Password reset of admin-role accounts (JIT elevation):** A plain `passwordProfile` PATCH works for ordinary members but returns `403 Authorization_RequestDenied` when the target holds a directory role (SharePoint/Teams/User Admin, etc.) — Microsoft requires the caller to be Global Administrator or **Privileged Authentication Administrator** to reset an admin's password. `scripts/reset-password.sh` handles this: it tries the direct reset, and on 403 it assigns the Tenant Admin service principal the Privileged Authentication Administrator role (the app holds `RoleManagement.ReadWrite.Directory`), retries, then **removes the role assignment it created** (de-elevates). If the SP already held the role, it is left untouched. Default `forceChangePasswordNextSignIn=false` (permanent — right for shared/service accounts); pass `--force-change` for a user who must change at next sign-in. Requires the tenant to have consented the Tenant Admin app. (Pattern added 2026-06-08 — birthbiologic.com operations@ was a SharePoint+Teams Admin, blocking the plain reset.)
---
## Arguments
@@ -184,6 +186,44 @@ If the user's phrasing is loose ("check john's box at cascades", "who's being at
---
## Syncro Ticket Creation (after remediation or check)
When creating a Syncro ticket to log remediation or breach-check work — whether via `/syncro` at the end of the session or inline during the workflow — the following fields are **REQUIRED** and must always be present in the POST payload. Omitting any of them leaves the ticket unusable in the queue.
**Required fields — no exceptions:**
| Field | Rule |
|---|---|
| `priority` | Always `"2 Normal"` unless the incident is active/emergency, in which case `"4 Urgent"` |
| `user_id` | Always the API key owner's user ID: `mike``1735`, `howard``1750`, `winter``1737`. Never omit — never null |
| `problem_type` | Use `"Security"` for breach checks, tenant sweeps, MFA enforcement, account compromise. Use `"Remote"` for general M365 remote support. Never use `"Remote Support"` — it is not a valid Syncro dropdown value and will appear blank in the GUI |
**Payload template for POST /tickets:**
```bash
curl -s -X POST "${BASE}/tickets?api_key=${API_KEY}" \
-H "Content-Type: application/json" \
--data-binary @- <<JSON
{
"customer_id": ${CUST_ID},
"subject": "<subject>",
"problem_type": "Security",
"status": "New",
"priority": "2 Normal",
"user_id": ${TECH_USER_ID}
}
JSON
```
**Enforcement checklist — verify before POSTing:**
1. `priority` is set (not null, not omitted)
2. `user_id` is set to the correct tech ID (not null, not omitted)
3. `problem_type` is one of the valid Syncro dropdown values listed above
If any check fails, fix the payload before sending. Do not POST a ticket with missing required fields.
---
## Scope and references
- Detailed check rubric: `.claude/skills/remediation-tool/references/checklist.md`

View File

@@ -0,0 +1,25 @@
---
name: rmm-search
description: Cleanly find machines in the GuruRMM fleet with a flexible, client-aware search (no more grepping /api/agents and hitting the wrong client's box). Front door for locating an agent before acting on it via /rmm.
---
# /rmm-search — find GuruRMM machines on the first try
Thin entry point to the `rmm-search` skill. Engine: `.claude/scripts/rmm-search.sh`.
## Usage
```
/rmm-search <words...> [-c <client>] [--online] [--json] [-n N]
/rmm-search -c <client> List ALL machines for a client
/rmm-search --list-clients Show distinct client names
```
Every query word must match some field (hostname/client/site/OS/id), so words
**narrow**`hyperv valleywide` returns only Valley Wide's hyperv host, never
Dataforth's. Matching is normalized (case/space/hyphen-insensitive) with
prefix/substring/subsequence ranking; `-c` is an explicit hard client scope and
refuses to guess when the client name is ambiguous.
Use this to *find* an agent; pass the resulting hostname/id to `/rmm` to *act*.
Full matching rules + examples: `.claude/skills/rmm-search/SKILL.md`.

View File

@@ -67,28 +67,31 @@ Interact with the GuruRMM agent fleet: list agents, run remote commands (PowerSh
## Phase 0 — Bootstrap (run once per session)
**Use the helper script** (cross-platform, handles Mac jq/JSON issues):
```bash
IDENTITY_PATH="${HOME}/.claude/identity.json"
if [ ! -f "$IDENTITY_PATH" ]; then
IDENTITY_PATH=$(git rev-parse --show-toplevel 2>/dev/null)/.claude/identity.json
fi
REPO_ROOT=$(jq -r '.claudetools_root // empty' "$IDENTITY_PATH" 2>/dev/null)
if [ -z "$REPO_ROOT" ]; then
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
fi
VAULT="$REPO_ROOT/.claude/scripts/vault.sh"
# Authenticate and set environment variables
eval "$(bash .claude/scripts/rmm-auth.sh)"
# This sets: $TOKEN, $RMM, $REPO_ROOT
```
**Alternative (manual, for reference only — use helper script above):**
```bash
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)"
IDENTITY_FILE="$REPO_ROOT/.claude/identity.json"
VAULT_PATH=$(jq -r '.vault_path' "$IDENTITY_FILE")
VAULT_SH="$VAULT_PATH/scripts/vault.sh"
RMM="http://172.16.3.30:3001"
RMM_EMAIL=$(bash "$VAULT" get-field infrastructure/gururmm-server.sops.yaml credentials.gururmm-api.admin-email)
RMM_PASS=$(bash "$VAULT" get-field infrastructure/gururmm-server.sops.yaml credentials.gururmm-api.admin-password)
RMM_EMAIL=$(bash "$VAULT_SH" get-field infrastructure/gururmm-server.sops.yaml credentials.gururmm-api.admin-email)
RMM_PASS=$(bash "$VAULT_SH" get-field infrastructure/gururmm-server.sops.yaml credentials.gururmm-api.admin-password)
JWT=$(curl -s -X POST "$RMM/api/auth/login" \
-H "Content-Type: application/json" \
--data-binary @- <<JSON
{"email": "$RMM_EMAIL", "password": "$RMM_PASS"}
JSON
)
# Use jq to build JSON safely (avoids heredoc issues on Mac)
PAYLOAD=$(jq -n --arg email "$RMM_EMAIL" --arg password "$RMM_PASS" '{email: $email, password: $password}')
JWT=$(curl -s -X POST "$RMM/api/auth/login" -H "Content-Type: application/json" -d "$PAYLOAD")
TOKEN=$(echo "$JWT" | jq -r '.token // empty')
if [ -z "$TOKEN" ]; then
echo "[ERROR] RMM login failed: $JWT"
exit 1
@@ -154,6 +157,8 @@ Show: hostname, os_type, online/offline, client_name (from `site_name`/`client_n
Use `python` only when explicitly writing a Python script. Use `script` for saved scripts (not covered in this skill).
**VALID `command_type` values ONLY: `shell`, `powershell`, `python`, `script`, `claude_task` (plus alias `cmd` → shell = cmd.exe).** The agent deserializes `command_type` into a Rust enum; an UNKNOWN value (e.g. a made-up type) fails the agent's whole-message JSON parse and the command is **silently dropped — no ack, no result, no error** — which is indistinguishable from a network black-hole and has caused a long mis-diagnosis. On Windows: `powershell` runs powershell.exe (UTF-8 output fixed in-agent); `shell` or `cmd` runs cmd.exe. If a dispatched command sits un-acked forever, FIRST suspect an invalid `command_type` before chasing the network. (Newer agents NAK an unparseable command so it fails fast with a clear stderr instead of black-holing.)
### Basic dispatch
```bash

View File

@@ -26,17 +26,35 @@ Claude writes all sections directly. Be concise, factual, technical. No filler p
### Location
New logs go in a **`YYYY-MM/` month folder** under the relevant `session-logs/` dir (keeps the
flat dir from growing unbounded; recall is scoped grep over the month folders — no monolithic
index). `mkdir -p` the month folder before writing.
| Work scope | Path |
|---|---|
| Single project | `projects/<project>/session-logs/YYYY-MM-DD-session.md` |
| Client | `clients/<slug>/session-logs/YYYY-MM-DD-session.md` |
| Multi-project / general | `session-logs/YYYY-MM-DD-session.md` |
| Single project | `projects/<project>/session-logs/YYYY-MM/YYYY-MM-DD-<user>-<topic>.md` |
| Client | `clients/<slug>/session-logs/YYYY-MM/YYYY-MM-DD-<user>-<topic>.md` |
| Multi-project / general | `session-logs/YYYY-MM/YYYY-MM-DD-<user>-<topic>.md` |
> Existing flat logs (`session-logs/*.md`) stay where they are — recall grep covers both `*/*.md`
> (month folders) and `*.md` (legacy flat), so no mass migration. The month folder is added
> *after* `session-logs/`, so wiki slug derivation (`<project>`/`<slug>` captured before
> `session-logs/`) is unaffected. Use `bash .claude/scripts/now-phoenix.sh --date` for the date.
### Filename + append behavior
- Filename: `YYYY-MM-DD-session.md` (today's local date)
- If file exists, **append** a `## Update: HH:MM PT — <topic>` section. Do not overwrite.
- If two users worked on the same date, namespace: `YYYY-MM-DD-<user>-<topic>.md` (e.g. `2026-05-01-howard-syncro-billing-batch.md`)
**Per-session-unique filenames are mandatory** — 34 Claude sessions can run against this one
working tree at once, and a shared `YYYY-MM-DD-session.md` lets them overwrite each other's logs.
Never use the bare `YYYY-MM-DD-session.md`.
- Default: `YYYY-MM-DD-<user>-<topic>.md``<user>` from the User block (identity.json),
`<topic>` a short kebab slug of this session's main work (e.g. `2026-06-05-mike-gururmm-platform-day.md`).
The topic naturally separates concurrent sessions.
- Collision guard: if that exact filename already exists and belongs to a **different** session
(different work), append a discriminator — `YYYY-MM-DD-<user>-<topic>-2.md` (increment until free).
Never overwrite another session's file.
- Same-session continuation (re-saving your own ongoing work): **append** a
`## Update: HH:MM PT — <topic>` section to this session's own file. Do not overwrite.
### Required sections (in order)
@@ -59,35 +77,52 @@ When in doubt, include MORE detail — future sessions search these logs to reco
---
## Phase 3 — Wiki Compile (before sync)
## Phase 3 — Wiki: DECOUPLED (do NOT recompile inline)
Fold what you just worked on into the wiki article so it ships in the **same commit** as the session log. This runs before sync and **re-synthesizes** the article (via a **Sonnet subagent** — `model: "sonnet"`, not Ollama), so new findings/patterns actually land — not just dynamic fields.
Wiki synthesis is **decoupled from `/save`** (harness v1.2.0+, Task 2). Running a full
Sonnet recompile inline on every save, on every machine, caused concurrent-recompile
rebase conflicts — and once committed unresolved conflict markers into a wiki article.
So **`/save` no longer touches the wiki**: it writes the session log and syncs, nothing
more. Do NOT recompile the wiki here, and never block/delay the sync on wiki work.
1. Derive the slug from the session-log path written in Phase 2:
- `clients/<slug>/session-logs/...` → client `<slug>`
- `projects/<project>/session-logs/...` → project article slug (e.g. `guru-rmm`, `guru-connect`)
- Root `session-logs/...` → **skip this phase entirely** (no single article is implied)
To refresh the wiki for this session's work, run `/wiki-compile` **separately** — it is
now **serialized** (per-article coord lock) and **staged** (writes a proposed update to
`.claude/wiki_staging/` for review before it touches the live article).
2. Run the `/wiki-compile` generation for that target, writing the article + updating `wiki/index.md`, but **stop before its commit/push step** — `sync.sh` (Phase 4) commits everything together in one commit:
- **Article exists** → **full recompile** (`/wiki-compile <type>:<slug> --full`): the Sonnet subagent re-synthesizes, **preserving Patterns and History verbatim** (unless the new session log shows an item resolved) and refreshing everything else, absorbing this session's work. Clients also refresh live Syncro fields (hours, tickets).
- **No article yet** → **seed** (full synthesis) to create it.
- The main agent reviews the subagent's draft before writing — verify IPs/paths; never invent vault paths (use `(verify)`); keep billing fields Syncro-authoritative.
After the sync completes, derive the slug from the session-log path (Phase 2) and emit
the exact command for the operator to run when ready:
- `clients/<slug>/session-logs/...` → `[INFO] Wiki decoupled — run: /wiki-compile client:<slug> --full (serialized + staged)`
- `projects/<project>/session-logs/...` → `[INFO] Wiki decoupled — run: /wiki-compile project:<slug> --full (serialized + staged)`
- Root `session-logs/...` → no single article implied; emit nothing.
3. **Softfail (critical) — a wiki failure must NEVER block the save:**
- If the synthesis subagent fails or is unavailable, fall back to a surgical **refresh** (bump `last_compiled` + `sources`; refresh client Syncro fields) so the article still records the session, and emit `[WARN] wiki refreshed, not recompiled; run /wiki-compile --full later`.
- Any other failure: log it and continue to sync.
The article + `wiki/index.md` are picked up by `sync.sh`'s `git add -A` and committed alongside the session log.
The session log + `sync.sh` are the durable record; the wiki is refreshed deliberately,
not on every save.
---
## Phase 4 — Sync
First, run the **promotion check** — the scratch dirs (`tmp/`, `temp/`, `.claude/tmp/`)
are gitignored, so anything in them is invisible to git and lost on cleanup. This is
advisory and never blocks:
```bash
bash .claude/scripts/tmp-promotion-check.sh
```
If it flags `[GRADUATE?]` candidates, graduate the keepers per `.claude/TEMP_GRADUATION.md`
(`git mv` into `scripts/` / `clients/<x>/reports/` / `projects/<p>/tools/`) **before** the
sync sweeps the commit. Pure scratch can be left or deleted. Then sync:
```bash
bash .claude/scripts/sync.sh
```
`sync.sh` handles: reconcile this machine's `git config user.name/email` to `.claude/identity.json` (so commit authorship can't drift), stage all changes with `git add -A` (after purging garbled Windows path-as-filename cruft), auto-commit, fetch + rebase, push, then the same flow for the vault repo, then surface cross-user `## Note for <user>` blocks.
Same driver as `/sync` — see that command for the full semantics. The two load-bearing
points for reporting: **exit 75 = deferred** (another sync is running; report "sync deferred
— your session log is written locally and will sync on the next run", NOT a success summary);
and `git add -A` is a catch-all sweep, so avoid running `/save` from two sessions at the exact
same moment (per-session-unique log filenames prevent log overwrites, the lock prevents racing).
After sync, emit a **Post-commit Summary**:

View File

@@ -6,24 +6,19 @@ Quick command to save session log, stage everything, and push to Gitea in one sh
1. **Save session log** - Create/update session log for today using the /save skill logic:
- Determine correct location based on work context (project-specific or general `session-logs/`)
- Use format `YYYY-MM-DD-session.md`
- If file exists, append with `## Update: HH:MM` header
- **Per-session-unique filename (mandatory)** — concurrent sessions share this worktree, so never use the bare `YYYY-MM-DD-session.md`. Use `YYYY-MM-DD-<user>-<topic>.md`; collision-guard + same-session-append rules are in `/save` (`save.md`).
- Include: summary, credentials (unredacted), infrastructure, commands, files changed, pending tasks
2. **Stage all changes** - Run `git add -A` to stage everything including the new session log
2. **Promotion check (advisory)** - Run `bash .claude/scripts/tmp-promotion-check.sh`. The scratch dirs (`tmp/`, `temp/`, `.claude/tmp/`) are gitignored, so anything there is invisible to git and lost on cleanup. If it flags `[GRADUATE?]` candidates, `git mv` the keepers to a permanent home (`scripts/` / `clients/<x>/reports/` / `projects/<p>/tools/`) per `.claude/TEMP_GRADUATION.md` before committing. Never blocks — pure scratch can be left or deleted.
3. **Commit** - Auto-commit with message:
```
scc: Session save and push from [hostname] at [timestamp]
3. **Commit + push (locked, rebase-safe)** - Run `bash .claude/scripts/sync.sh`. This is the single serialized git path: it takes the per-machine sync lock (so it can't interleave with another session's sync/commit), reconciles git identity to `identity.json`, stages changes, commits, fetch + rebase, pushes — ClaudeTools then vault.
- **Do NOT** run raw `git add -A` / `git commit` / `git push origin main` here — that bypasses the lock AND the fetch+rebase (the old flow raced and would reject on a stale push).
- If `sync.sh` **exits 75**, another sync is in progress: report "sync deferred — your log is saved locally and will sync on the next run"; do not claim pushed.
- Note: the discrete `scc:`-prefixed message is dropped in favour of one locked git path (commit lands under `sync.sh`'s auto message). If a custom message matters, revisit later (e.g. a `-m` arg on `sync.sh`).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
```
4. **Report** - Confirm what was saved, committed, and pushed (or deferred)
4. **Push to Gitea** - Run `git push origin main`
5. **Report** - Confirm what was saved, committed, and pushed
6. **Reaffirm roles** - After push, briefly restate:
5. **Reaffirm roles** - After push, briefly restate:
- You are a COORDINATOR, not an executor
- Delegate: DB -> Database Agent, code -> Coding Agent, git -> Gitea Agent, tests -> Testing Agent
- Do yourself: simple responses, reading 1-2 files, planning, decisions

View File

@@ -39,16 +39,15 @@ The intent: a `/sync` that finds unsaved work should default toward `/save`. Aut
## What this does
Invokes `bash .claude/scripts/sync.sh`, which:
Run it — the script is the single source of truth for all git ops (both `/sync` and `/save` invoke it):
1. Detects local changes (including untracked-only files) via `git status --porcelain`; stages with `git add -A` and auto-commits with `sync: auto-sync from <hostname> at <timestamp>`
2. Fetches from origin, rebases local commits onto remote
3. Pushes to origin
4. Copies `.claude/commands/*.md``~/.claude/commands/` so the global Claude CLI commands stay current without a manual copy
5. Repeats steps 1-3 for the **vault** repo (path read from `.claude/identity.json` `vault_path` field)
6. Surfaces any `## Note for <user>` / `## Message for <user>` blocks from incoming session logs
```bash
bash .claude/scripts/sync.sh
```
The script is the single source of truth for git operations. Both `/sync` and `/save` invoke it.
It stages (`git add -A`, submodule gitlinks unstaged unless `--with-submodules`), auto-commits, fetch+rebase+push for this repo then the vault repo, deploys `.claude/commands/*.md` + skills to `~/.claude/`, and surfaces incoming `## Note for <user>` blocks. Full internals: `.claude/CLAUDE_EXTENDED.md` / the script header.
**Exit 75 = deferred, not a failure.** The run is serialized by a per-machine lock (`.git/claudetools-sync.lock`); if another sync is mid-flight it waits ~120s then exits 75. On a 75, report "sync deferred — another sync is running; it will catch up next run", NOT a success summary. Stale locks (dead owner, or >10 min) auto-reclaim.
---

View File

@@ -29,7 +29,7 @@ Create, update, close, comment on, and bill tickets in Syncro PSA.
## Hard Rules (violations have occurred — no exceptions)
**Billing uses `add_line_item` directly — do NOT use `timer_entry → charge_timer_entry`.** The timer workflow is not used. For all billable work (labor, warranty, internal), POST directly to `/tickets/<id>/add_line_item` with the correct `product_id`, `name`, `quantity` (decimal hours), `price_retail`, `description`, and `taxable: false`. The `name` field is required — Syncro returns `{"errors":"Name can't be blank"}` if omitted (verified 2026-05-21 on Cascades #32313).
**Normal billing uses `add_line_item` directly — do NOT use `timer_entry → charge_timer_entry` for routine billing.** Timers are an OUTLIER: use one ONLY if Mike explicitly requests a timer for a specific job, never for the normal billing loop. For all billable work (labor, warranty, internal), POST directly to `/tickets/<id>/add_line_item` with the correct `product_id`, `name`, `quantity` (decimal hours), `price_retail`, `description`, and `taxable: false`. The `name` field is required — Syncro returns `{"errors":"Name can't be blank"}` if omitted (verified 2026-05-21 on Cascades #32313).
**JSON payloads to curl: use heredoc with `--data-binary @-`, not `/tmp/*.json` files.** On Windows the Write tool resolves `/tmp/foo.json` to `C:\tmp\foo.json` while Git Bash resolves it to `%LOCALAPPDATA%\Temp\foo.json` — different real directories, so a payload written by Write may not be the file curl reads. Heredoc with `<<'JSON'` (single-quoted to suppress bash variable expansion inside the payload) avoids the file handoff entirely. See `.claude/memory/feedback_tmp_path_windows.md` — caused a wrong-comment incident on ticket #32225 on 2026-05-01 (rogue payload from a prior session).
@@ -618,7 +618,7 @@ curl -s "${BASE}/customers/${CUST_ID}?api_key=${API_KEY}" | jq '{id: .customer.i
#### Line Items
All billing uses `add_line_item` directly. Do not use `timer_entry → charge_timer_entry`. Do not use timers.
Normal billing uses `add_line_item` directly. Do not use `timer_entry → charge_timer_entry` for routine billing. Timers are an outlier — use one only when Mike explicitly requests a timer for a specific job (see `.claude/standards/syncro/time-entry-protocol.md`).
**Dead-end paths (all return 404 — do not probe):**
- `POST /ticket_line_items` — does not exist
@@ -719,6 +719,49 @@ curl -s -X DELETE "${BASE}/invoices/${INV_ID}?api_key=${API_KEY}"
POST `/invoices` pulls all current line items from the ticket into the invoice automatically. The POST response includes `.invoice.id` and `.invoice.total` — if either is null, GET `/invoices?customer_id=${CUST_ID}&per_page=5` and find the invoice by `ticket_id` match before taking any other action.
**Invoice Message (the on-screen "Invoice Message" text block) = the invoice `note` field.** Per-invoice, prints on the invoice, set with `PUT /invoices/{id}` body `{"note":"..."}` (response `{"invoice":{...}}`). Blank by default. Verified 2026-06-16 on the ACG internal test account (#67741 set/verify/restore).
**Auto invoice-note policy** — every invoice we generate gets a one-line note driven by the customer's prepaid block (`customer.prepay_hours`):
| Customer | Note set on the invoice |
|---|---|
| **No block** (`prepay_hours == 0`) | `Interested in discounted labor? Ask us about block-rate pricing.` |
| **Block, ≥ 4 hrs left** | `Block hours remaining: N.` |
| **Block, < 4 hrs left** | `Block hours remaining: N. You're running low — reply to renew...` **+ tags Winter in #bot-alerts** |
Reusable helper — call it once the invoice exists (needs `$BASE`, `$API_KEY`, `$REPO_ROOT`):
```bash
# set_invoice_note <invoice_id> <customer_id> [<pre_billing_prepay_hours>]
# Arg 3 matters for AD-HOC labor billing: pass the prepay fetched in billing Step 1 so a block
# JUST depleted to 0 still counts as a block customer (shows "remaining: 0 + renew", not the upsell).
# Omit arg 3 for recurring (recurring invoices don't debit the block → current balance is correct).
set_invoice_note() {
local INV_ID="$1" CID="$2" PRE="${3:-}" CUST REMAIN NAME CHECK NOTE CUR R
CUST=$(curl -s "${BASE}/customers/${CID}?api_key=${API_KEY}" | tr -d '\000-\037')
REMAIN=$(echo "$CUST" | jq -r '.customer.prepay_hours // "0"')
NAME=$(echo "$CUST" | jq -r '.customer.business_name // .customer.fullname // "customer"')
CHECK="${PRE:-$REMAIN}" # block status source (pre-billing if given)
R=$(awk "BEGIN{printf \"%g\",(${REMAIN:-0})+0}") # tidy: 20.5, 3, 0
if [ "$(awk "BEGIN{print ((${CHECK:-0})+0>0)?1:0}")" = "0" ]; then
NOTE="Interested in discounted labor? Ask us about block-rate pricing."
elif [ "$(awk "BEGIN{print ((${REMAIN:-0})+0<4)?1:0}")" = "1" ]; then
NOTE="Block hours remaining: ${R}. You're running low — reply to renew your block and keep your discounted rate."
bash "$REPO_ROOT/.claude/scripts/post-bot-alert.sh" \
"[SYNCRO] <@624666486362996755> LOW BLOCK — ${NAME} has ${R} prepaid hrs left (< 4) on invoice #${INV_ID} (cust ${CID}). Reach out about renewal."
else
NOTE="Block hours remaining: ${R}."
fi
CUR=$(curl -s "${BASE}/invoices/${INV_ID}?api_key=${API_KEY}" | tr -d '\000-\037' | jq -r '.invoice.note // ""')
if [ -n "$CUR" ] && [ "$CUR" != "null" ]; then echo "[note] skip ${INV_ID}: already has a note"; return 0; fi
curl -s -X PUT "${BASE}/invoices/${INV_ID}?api_key=${API_KEY}" -H "Content-Type: application/json" \
--data-binary "$(jq -nc --arg n "$NOTE" '{note:$n}')" >/dev/null
echo "[note] ${INV_ID} (${NAME}): ${NOTE}"
}
```
Winter's Discord id `624666486362996755` in the alert content (`<@...>`) pings her in #bot-alerts (post-bot-alert sets no `allowed_mentions`, so content mentions ping). **Never clobber a non-empty note** — a tech may have typed a real per-invoice message. One alert per low-block invoice.
#### Recurring Invoice Schedules
Recurring invoice templates are at `/schedules` — **not** `/recurring_invoices` (404). Generated invoices carry a `schedule_id` field linking back to the template. The `recurring_invoice_id` field on invoices is always null; ignore it.
@@ -819,6 +862,26 @@ curl -s -X DELETE "${BASE}/schedules/${SCHED_ID}/line_items/${LI_ID}?api_key=${A
**Pax8 link field:** `recurring_type_id` on a schedule line item holds the Pax8 subscription UUID. Pax8 sets this when syncing subscriptions and uses it to identify which line to PUT when a subscription changes quantity or price.
#### Recurring invoice note sweep (block hours / low-block alerts on auto-generated invoices)
Recurring invoices are generated by Syncro **automatically** — we don't run code at generation time — so to put the same invoice-note policy (block hours remaining / low-block renew + Winter tag / non-block upsell) on them, run a **sweep** after the recurring run that scans recently-generated recurring invoices and applies `set_invoice_note` (the helper in the Invoices section). Recurring invoices carry a non-null `schedule_id`; they do **not** debit the block, so the current `prepay_hours` is the right remaining figure (omit the pre-billing arg).
```bash
# Sweep recurring invoices from the last N days and set their block-hours note.
SINCE=$(date -d '8 days ago' +%Y-%m-%d 2>/dev/null || date -v-8d +%Y-%m-%d)
PAGE=1
while :; do
BATCH=$(curl -s "${BASE}/invoices?per_page=100&page=${PAGE}&api_key=${API_KEY}" | tr -d '\000-\037')
ROWS=$(echo "$BATCH" | jq -r --arg since "$SINCE" '.invoices[] | select(.schedule_id != null) | select(.date >= $since) | "\(.id) \(.customer_id)"')
[ -z "$ROWS" ] && break
echo "$ROWS" | while read -r INV CID; do set_invoice_note "$INV" "$CID"; done # no pre-billing arg
CNT=$(echo "$BATCH" | jq -r '.invoices | length'); [ "${CNT:-0}" -lt 100 ] && break
PAGE=$((PAGE+1))
done
```
Idempotent (skips invoices that already have a note). Run it on demand after monthly billing, or schedule it (a cron/scheduled agent the day after each recurring run). Low-block customers still get the renewal line + a Winter ping, once per invoice.
#### Estimates
Estimates (quotes) always require a linked ticket — create the ticket first, then the estimate with `ticket_id` set. This enables private notes (purchase links, sourcing details) on the ticket using the standard hidden comment endpoint. Estimates created without a ticket have no notes surface accessible via API. Verified 2026-05-22 against ACG internal account.
@@ -1053,6 +1116,10 @@ INVOICE_ID=$(echo "$INV_RESP" | jq -r '.invoice.id')
INVOICE_TOTAL=$(echo "$INV_RESP" | jq -r '.invoice.total')
# If INVOICE_ID is null: GET /invoices?customer_id=${CUST_ID}&per_page=5, find by ticket_id
# 3b. Invoice-note policy: non-block -> upsell hint; block -> hours remaining; block <4hr -> renew + tag Winter.
# Pass the PRE-billing prepay ($PREPAY from Step 1) so a just-depleted block still counts as block.
set_invoice_note "$INVOICE_ID" "$CUST_ID" "$PREPAY" # helper defined in the Invoices section
# 4. Mark Invoiced
curl -s -X PUT "${BASE}/tickets/${ID}?api_key=${API_KEY}" \
-H "Content-Type: application/json" \

36
.claude/commands/vault.md Normal file
View File

@@ -0,0 +1,36 @@
# /vault — Consistent SOPS vault operations
The one canonical way to read, store, update, and verify secrets in the ClaudeTools SOPS+age
vault. Use instead of raw `sops` or guessed paths. Full reference: `.claude/skills/vault/SKILL.md`.
## Quick reference
```bash
# READ
bash .claude/scripts/vault.sh get <path>
bash .claude/scripts/vault.sh get-field <path> credentials.api_key
bash .claude/scripts/vault.sh search <query>
bash .claude/scripts/vault.sh list [subdir]
# STORE / UPDATE (non-interactive — these work in this harness; `vault edit` does not)
bash .claude/skills/vault/scripts/vault-helper.sh new <path> --kind api-key \
--name "..." [--url ..] [--tag ..] --set api_key=SECRET [--set username=foo]
bash .claude/skills/vault/scripts/vault-helper.sh set <path> --set password=NEW
# VERIFY (after any write, before any commit)
bash .claude/skills/vault/scripts/vault-helper.sh verify <path>
bash .claude/skills/vault/scripts/vault-helper.sh check [subdir]
# PUBLISH
bash .claude/scripts/sync.sh # Phase 6 commits + pushes the vault repo
```
## Rules (non-negotiable)
1. Never paste a secret into chat / ticket / commit / channel — share the vault path instead.
2. Secrets ALWAYS go under `credentials:` (only those keys get encrypted; anything else = plaintext).
3. Use the scripts above — never hand-roll `sops` + a guessed path, never use `VAULT_ROOT_ENV` for vault access.
4. Finish: write → `verify` → publish (sync). Don't hand off the push.
Paths are vault-root-relative (`clients/<slug>/...`, `msp-tools/...`, `infrastructure/...`,
`services/...`), with or without `.sops.yaml`.

View File

@@ -86,11 +86,30 @@ Convert slug to a Syncro search query:
```bash
# Replace hyphens with spaces for the search query
SEARCH_QUERY=$(echo "$SLUG" | sed 's/-/ /g')
URLQ() { python -c "import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1]))" "$1"; }
CUST_RESULTS=$(curl -s "$BASE/customers?name=$(python3 -c "import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1]))" "$SEARCH_QUERY")&per_page=5&api_key=$API_KEY")
# Use the FUZZY `query=` param, not `name=`. `name=` is near-exact and misses
# singular/plural and word-order mismatches between the slug and the Syncro
# business name (e.g. slug `gonzvar-tax-services` vs Syncro "Gonzvar Tax Service").
CUST_RESULTS=$(curl -s "$BASE/customers?query=$(URLQ "$SEARCH_QUERY")&per_page=5&api_key=$API_KEY")
CUST_COUNT=$(echo "$CUST_RESULTS" | jq '.customers | length')
# Fallback ladder if 0: retry with progressively shorter fuzzy queries
# (first word, then the distinctive surname/token) before declaring "not found".
if [ "$CUST_COUNT" = "0" ]; then
for Q in "$(echo "$SEARCH_QUERY" | awk '{print $1}')" "$(echo "$SEARCH_QUERY" | awk '{print $1}' | sed 's/s$//')"; do
[ -z "$Q" ] && continue
CUST_RESULTS=$(curl -s "$BASE/customers?query=$(URLQ "$Q")&per_page=10&api_key=$API_KEY")
CUST_COUNT=$(echo "$CUST_RESULTS" | jq '.customers | length')
[ "$CUST_COUNT" != "0" ] && echo "[SYNCRO] matched on fuzzy fallback '$Q'" && break
done
fi
```
> If the fuzzy/fallback search returns several, fall through to the 2+ disambiguation
> below; if still 0, only THEN treat as "not in Syncro". Do not conclude "not found"
> from the exact `name=` search alone — that was the Gonzvar miss.
**If 0 results:**
```
[SYNCRO] No customer found matching '${SEARCH_QUERY}' — skipping Syncro enrichment.
@@ -342,12 +361,33 @@ If the subagent is unavailable, the main agent writes the article directly using
---
## Phase 5 — Write Article + Update Index
## Phase 5 — Serialize, Stage, Review, Apply (Task 2)
**Write the article:**
- Seed: write `wiki/clients/<slug>.md` from generated content
- Full: overwrite `wiki/clients/<slug>.md`
- Refresh: edits already applied in Phase 4
Wiki writes are SERIALIZED + STAGED so two machines never recompile the same article
into a conflict, and no synthesis lands in the live article without a review.
**5.0 Claim a per-article coord lock** (via the `coord` skill):
`lock claim claudetools wiki/<type>/<slug> "wiki-compile <slug>" --ttl 1`.
- The TTL auto-evicts a dead session's lock (no permanent stranding).
- If the lock is **already held** → emit `[SKIP] wiki/<type>/<slug> is being compiled on
another machine; try again shortly` and exit cleanly.
- If **coord is unreachable** → emit `[WARN] coord down — proceeding without lock` and continue.
- RELEASE the lock in 5.3 — and on ANY error/abort before then.
**5.1 Write the synthesized article to STAGING, not the live tree:**
- Staging path: `.claude/wiki_staging/<type>-<slug>.md` (`mkdir -p .claude/wiki_staging`).
Write the generated/recompiled article THERE. Do NOT touch `wiki/...` yet.
**5.2 Review the staged diff (NO blind merge):**
- `diff -u "<live wiki path>" ".claude/wiki_staging/<type>-<slug>.md" | head -120` (or
`(new article)` if none). The main agent reviews: Patterns/History preserved on full
recompile, IPs/paths/vault-paths accurate, billing Syncro-authoritative, NO structural
corruption or duplicated headers. If the diff looks wrong → STOP, fix the staged file or
abort (release the lock); do not apply.
**5.3 Apply the staged article to the live tree** (then index + commit in Phase 6):
- `cp .claude/wiki_staging/<type>-<slug>.md <live wiki path>` (seed/full); refresh edits
already applied in Phase 4 still go via this staging review.
**Update `wiki/index.md`:**
- Check if `wiki/clients/<slug>.md` is listed in the Clients table
@@ -366,7 +406,11 @@ If the subagent is unavailable, the main agent writes the article directly using
cd "$CLAUDETOOLS_ROOT"
git add "wiki/clients/${SLUG}.md" wiki/index.md
git commit -m "wiki: compile ${SLUG} (${MODE})"
git fetch origin && git rebase origin/main # serialized, but rebase defensively
git push origin main
# Release the per-article lock and clear staging (ALWAYS — even on an earlier abort):
$PY "$CLAUDETOOLS_ROOT/.claude/skills/coord/scripts/coord.py" lock release claudetools "wiki/${TYPE}/${SLUG}" 2>/dev/null || true
rm -f "$CLAUDETOOLS_ROOT/.claude/wiki_staging/${TYPE}-${SLUG}.md"
```
Emit:

View File

@@ -0,0 +1,84 @@
# Scratch Graduation Pipeline (spec)
Status: **draft / in progress** (2026-06-15). Push side built + tested; triage validated on
the flarum test case; scheduled-on-BEAST wiring + execute helper are the remaining work.
## Problem
Scratch dirs (`tmp/`, `temp/`, `.claude/tmp/`) are gitignored, so anything in them is invisible
to git and lost on cleanup. The old approach — a **synchronous** `tmp-promotion-check.sh` run
inside `/save` and `/scc` — had two fatal flaws:
1. **Too slow on Windows.** At ~240 scratch files it forked `basename`/`wc`/`grep -r` per file;
the "referenced in a session log" check recursed `clients/` + `projects/` (Rust `target/`,
`node_modules/`, `.git`) **once per file** and hung `/save` for **4 minutes** (errorlog 2026-06-15).
2. **Too dumb.** Extension/size heuristics can't answer the real question — *which* of
`flarum_do_insert.py` / `do_insert2.py` / `search_insert.py` is canonical, what's a superseded
debug dupe, what holds secrets, where each belongs. That's semantic judgment.
The interim `tmp-promotion-check.sh` is now a fast (0.4s) pure-builtin "N scripts in scratch" nudge.
The real triage is **offloaded and asynchronous**, per this spec.
## Architecture
```
workstation BEAST (GURU-BEAST-ROG, best GPU) any Claude session
----------- -------------------------------- ------------------
graduation-push.sh Ollama @ :11434 (GPU) review proposal
tar scratch ──SCP/Tailscale──▶ graduation-inbox/<machine>/*.tgz sanitize secrets
(soft-fail if BEAST off) graduation-triage (Ollama classify) git mv keepers
─▶ proposal manifest ──coord msg/todo──▶ delete junk → commit
```
1. **Push** (`graduation-push.sh`, built): tars scratch and `scp`s ONE tarball to
`guru@100.101.122.4:graduation-inbox/<machine>/scratch-<utc>.tgz` over Tailscale. Decoupled
from `/save`; soft-fails if BEAST is unreachable. Centralizes every machine's scratch on the
GPU box (archive + lets BEAST batch-process even when the origin machine is off).
2. **Triage** (Ollama on BEAST's GPU): for each file, classify
`{disposition: graduate|delete|keep-data, canonical?, superseded_by, has_secrets, suggested_home, why}`.
Emits a **proposal manifest** (the supersession/secret reasoning the old heuristics couldn't do).
The orchestration can run **on BEAST** (Git-bash, scheduled) or on **any machine** against
BEAST's Ollama API — the GPU is reached over the HTTP API either way.
3. **Review + execute**: a Claude session (or human) reads the manifest, **sanitizes secrets**
(hardcoded creds → vault lookups), `git mv`s keepers to permanent homes, deletes junk, commits.
*Ollama proposes, human/Claude disposes* (same contract as memory-dream + the Tier-0 routing rule).
## Transport / environment facts (verified 2026-06-15)
- BEAST = `guru-beast-rog`, Tailscale `100.101.122.4`. SSH key auth works as **`guru`** (no password).
- BEAST default SSH shell = **cmd.exe**; home `C:\Users\guru`. The harness/triage run under
**Git-for-Windows MSYS bash** — NOT WSL. (`bash` on PATH resolves to the WindowsApps WSL stub;
invoke Git-bash explicitly. The WSL stub also can't reach the Windows-host Ollama on localhost —
another reason to avoid it.)
- **Ollama** runs on BEAST's Windows side, bound so it's reachable fleet-wide over Tailscale at
`http://100.101.122.4:11434`. Models incl. `qwen3:32b`, `qwen3.6:latest` (36B), `gemma3:27b`,
`codestral:22b`, `qwen3:14b`, `nomic-embed-text`.
- Inbox: `C:\Users\guru\graduation-inbox\<machine>\` (cmd path) — per-machine namespaced.
## Security (non-negotiable)
- **Secrets never enter git.** Raw scratch can contain hardcoded creds (the flarum scripts hold the
IX root SSH password). It rides the WireGuard-encrypted Tailscale/SSH link and lands ONLY on BEAST
(trusted). The transport is deliberately NOT the git repo or a multi-tenant store.
- **Sanitize before commit.** Any file graduated into a tracked home gets hardcoded secrets swapped
for vault lookups first (`vault.sh get-field ...`). harness-guard would block a plaintext-secret commit.
- **Manifest-only returns.** Only the proposal manifest comes back toward git — never the raw files.
## Components
| Piece | Path | State |
|---|---|---|
| Push | `.claude/scripts/graduation-push.sh` | built + tested (241 files → BEAST) |
| Interim nudge | `.claude/scripts/tmp-promotion-check.sh` | fast builtin-only (0.4s) |
| Triage | `.claude/scripts/graduation-triage.*` | validated ad-hoc on flarum; productize next |
| Execute | manual (Claude session) | flarum = first test case |
| Schedule | BEAST cron/loop calling triage | TODO |
| Return | coord message/todo to origin machine | TODO |
## Open items
- Productize `graduation-triage` (general file loop + Ollama classify + manifest) and a `--execute`
helper that sanitizes + `git mv`s per an approved manifest.
- Wire a scheduled triage run on BEAST (or a `/loop`) + coord-message return.
- Decide retention/cleanup of the BEAST inbox + auto-deleting obvious junk to keep scratch bounded.
- Consider dropping `tmp-promotion-check` from `/save` entirely once the pipeline is routine.

View File

@@ -0,0 +1,81 @@
# Harness CHANGELOG
The ClaudeTools harness version marker (`.claude/harness/VERSION`). Bump on every
fleet-visible behavioral change so a session can detect whether it is running the new
or old harness during a heterogeneous rollout. See
`specs/claudetools-harness-optimization/`.
## 1.0.0 — 2026-06-08
- Task 0.5: VERSION marker established (this file).
- Task 0.6: out-of-band recovery script `.claude/scripts/force-pull-raw.sh` added.
- (Earlier) Syncro billing SSOT resolved: `add_line_item` is normal billing; timers are
outlier-only (explicit request).
## 1.1.0 — 2026-06-08
- Task 1: submodule-safe sync — `sync.sh` now unstages submodule gitlinks (unless
`--with-submodules`), eliminating the manual detach-to-pin dance before /save.
- Task 4: `harness-guard.sh` wired into `sync.sh` pre-commit, WARN-ONLY (logs conflict
markers / unencrypted sops / private keys to .claude/harness/guard.log; does not block
unless HARNESS_GUARD_FATAL=1; SKIP_HARNESS_GUARD=1 bypasses).
## 1.2.0 — 2026-06-08
- Task 2: wiki synthesis DECOUPLED from /save (the concurrent-recompile conflict source).
/save now only writes the log + syncs and emits the exact /wiki-compile command to run.
/wiki-compile is now SERIALIZED (per-article coord lock, TTL orphan-evict, coord-down =
warn+proceed) and STAGED (writes .claude/wiki_staging/<type>-<slug>.md -> review diff ->
apply to live -> commit -> release lock). No blind background auto-merge.
## 1.3.0 — 2026-06-08
- Task 6: CLAUDE.md split into lean CORE (1.2k tokens, always loaded) + CLAUDE_EXTENDED.md
(full manual, on-demand). Saves ~3.7k tokens per CLAUDE.md injection; nothing lost.
- Task 9 (P2): delegation re-tuned in CORE — act directly by default; delegate only for
high-volume output, blast radius >3 files/layers, domain shift, or parallel work.
## 1.4.0 — 2026-06-08 (P1+P2+P3 complete)
- Task 5: one-line registry descriptions on the 8 biggest skills (remediation-tool, gc-audit,
packetdial, memory-dream, human-flow, self-check, impeccable, mailprotector). Skill-description
injection ~3320 -> ~2123 tokens (~36% cut); keyword triggers preserved; frontmatter valid.
- Task 7: thinned `/save` + `/sync` bodies — they point to `sync.sh` as the single source instead
of re-documenting its internals; load-bearing LLM-judgment parts (Phase 0 save-vs-sync, cross-user
note display, exit-75 reporting) kept verbatim. The mechanical sync never depends on an LLM step.
- Task 10 (P3): `session-logs/YYYY-MM/` adopted as a FORWARD convention for new logs (recall = scoped
grep over month folders, no monolithic index); existing flat logs untouched (grep covers both).
Recall order (wiki -> CONTEXT/log -> coord) already lives in CORE.
- Deterministic Bash fix: `now-phoenix.sh` helper added — fixed UTC-7 epoch math, replaces the
unreliable `TZ=America/Phoenix date` (silently returns UTC on Git-Bash). `--iso/--date/--datetime/
--fmt` formats. `post-bot-alert.sh` already uses `jq -nc --arg` (verified, no change needed).
- Deferred (unchanged): full Python port = separate spec; Task 8 shard command bodies; promote
guard to FATAL after a clean warn window; schedule memory-dream --apply-safe per-machine.
## 1.4.1 — 2026-06-08 (Task 12: self-check smoke tests)
- /self-check gained a `harness` category that locks in the 1.4.0 invariants (all read-only):
VERSION present + not older than manifest min_version; **skill-registry description budget**
(sum of all SKILL.md description: fields under manifest.harness.registry_desc_budget_chars —
WARN on regrowth, the metric that would catch Task 5 bloating back); global deploy targets
~/.claude/skills + ~/.claude/commands populated (the Mac-wipe failure); harness-guard.sh wired
into sync.sh; core scripts parse (bash -n on sync/guard/now-phoenix); now-phoenix.sh emits a
valid date. Tunables live in baseline/manifest.json `harness` block. Verified: 9/9 PASS on this
machine; budget WARN trips correctly on a synthetic over-budget value.
- Also reconciled the remaining "GrepAI first" docs (standard + CODING_GUIDELINES) with the
wiki-first recall hierarchy (started in CLAUDE_EXTENDED).
## 1.4.2 — 2026-06-08 (Task 3 leftover: command-restates-standard lint)
- /self-check gained a `consistency` category — the command-restates-standard lint. Deterministic
half: for each manifest.command_standard_links pair, the standard must still carry its
defer-to-SSOT pointer to the owning command; a lost pointer WARNs (the standard likely drifted
back into restating the command — the Syncro-timers failure mode). Seeded with the syncro-billing
link (time-entry-protocol.md -> /syncro). Semantic contradiction pass (read both, judge actual
conflict) delegated to the model in SKILL.md, mirroring the memory pass. Verified PASS; negative-
tested (WARN fires when the pointer is removed). New pairs: add to manifest.command_standard_links.
## 1.4.3 — 2026-06-08 (guard FATAL-promotion prerequisite: test matrix + refinement)
- Built `.claude/scripts/test-harness-guard.sh` — a 12-case false-positive/true-positive matrix
for harness-guard.sh (spins a throwaway repo, stages synthetic content, runs the REAL guard,
asserts WARN/clean). Required by the plan before promoting the guard to FATAL.
- The matrix surfaced a false-positive vector: the conflict rule's lone `=======$` alternative
fired on a markdown setext underline / divider of exactly seven `=`. REFINED harness-guard.sh to
require a real hunk — BOTH `^<<<<<<< ` AND `^>>>>>>> ` present — which has identical true-positive
power (git always writes all three markers) and eliminates the false positive. Verified 12/12 pass;
real-tree false-positive surface = 0.
- Wired the matrix into /self-check as `harness.guard_selftest` (runs in an isolated temp repo, so
the read-only-vs-real-tree contract holds). The eventual FATAL flip is now evidence-backed.

1
.claude/harness/VERSION Normal file
View File

@@ -0,0 +1 @@
1.4.3

View File

@@ -0,0 +1,85 @@
# Machine: GURU-5070 (Windows)
**Hostname:** GURU-5070
**User:** Mike Swanson (mike) — admin
**Platform:** Windows 11 Pro 10.0.26200
**Last Updated:** 2026-06-06
> Same physical hardware as `acg-guru-5070.md` (Lenovo Legion Pro 7 16IAX10H) —
> that profile documents the prior CachyOS Linux install. This box now runs Windows.
---
## Hardware
| Spec | Value |
|------|-------|
| Model | Lenovo Legion Pro 7 16IAX10H (DMI 83F5) |
| CPU | Intel Core Ultra 9 275HX (24 cores) |
| Memory | 32 GB DDR5 |
| GPU | NVIDIA GeForce RTX 5070 Ti Laptop (12 GB) |
| Disks | C: 952 GB NVMe (OS), D: 953 GB NVMe (dev — `D:\claudetools`, `D:\vault`, `D:\work`) |
## Paths
| What | Where |
|------|-------|
| ClaudeTools | `D:\claudetools` |
| Vault | `D:\vault` |
| Other repos | `D:\work\gururmm` |
| SOPS age key | `%APPDATA%\sops\age\keys.txt` and `~\.config\sops\age\keys.txt` |
| Claude CLI | `~\.local\bin\claude.exe` (native installer) |
| Grok CLI | `~\.grok\bin\grok.exe` |
| Gemini CLI | npm global (`@google/gemini-cli`) |
## Toolchain (as of 2026-06-06)
node 24.x · npm 11.x · py/Python 3.14 · git 2.53 · cargo/rustc 1.96 ·
ollama 0.30.6 · jq 1.8 · sops 3.7→3.12 · age 1.3 · op 2.33 · VS Code 1.113 ·
claude 2.1.x · gemini 0.45 · grok 0.2.x. **gh was missing** — bootstrap installs it.
Ollama models: `nomic-embed-text`, `qwen3:8b`, `qwen3:14b`, `codestral:22b`, `qwen3.6:latest`.
## Scheduled tasks (ClaudeTools)
- `GrepAI Watcher - claudetools``D:\claudetools\grepai.exe watch --background` (logon)
- `ClaudeTools - Orphaned Session Detector``py detect_orphaned_sessions.py` (logon + daily)
- `ClaudeTools - KSTEEN SmartBadge Daily` → git-bash `check-ksteen-smartbadge.sh` (daily)
## Capabilities
- [x] Git / Gitea, SSH to infra
- [x] GrepAI watcher
- [x] Ollama local AI (RTX 5070 Ti — light/inference OK)
- [x] MCP: ticktick, grepai
- [x] claude / gemini / grok CLIs (fleet host for all three)
## Recovery
Full rebuild after a reset: `.claude\bootstrap\RESTORE.md`.
Recovery bundle on **E:** and **F:** (`\claudetools-recovery\`). Refresh it with
`.claude\bootstrap\backup-to-bundle.ps1`.
## Known issues
- **Two Python interpreters, both must have deps.** `py` -> Python **3.14** (vault
`yaml-query.py`/get-field needs PyYAML; helper + skill scripts; scheduled tasks).
`python` -> Python **3.12** (the interpreter `.mcp.json` launches MCP servers with;
ticktick needs `httpx` + `mcp`). The 2026-06-06 reinstall installed deps into only
`py`, so ticktick MCP and `vault get-field` were both dead. `windows-bootstrap.ps1`
Phase 7 now installs into BOTH interpreters. Also `websocket-client` (cdp.py) under `py`.
- **Ollama models survive on `D:\OllamaModels` (~48 GB) but `ollama list` can read empty
right after login** — the tray app's server takes a few seconds to hydrate its
model-list cache. Don't treat empty as "models gone" / re-download. Restart the app
(or `ollama serve` with `OLLAMA_MODELS=D:\OllamaModels`) and wait ~10s. Bootstrap
Phase 8 handles this. The 5 expected models: nomic-embed-text, qwen3:8b, qwen3:14b,
codestral:22b, qwen3.6:latest.
- **grok CLI** is a bare `~\.grok\bin\grok.exe` drop; its installer doesn't touch PATH.
Bootstrap Phase 3 now persists `~\.grok\bin` (+ `~\.local\bin`, `%APPDATA%\npm`) to User PATH.
- **Git auth must be non-interactive** (no GCM password prompts — they hang automation).
Primed by `.claude/scripts/setup-git-auth.sh` (vault token -> `store` helper, per-repo
host) via a SessionStart hook + bootstrap Phase 6; `GIT_TERMINAL_PROMPT=0` is enforced
in `.claude/settings.json`. See memory `feedback_git_noninteractive_auth`.
- Old `D:\work\gururmm` remote URL embedded the shared Gitea password in plaintext —
reset to a clean URL + Windows Credential Manager on rebuild.
- (Hardware) RTX 5070 Ti GSP firmware bug under sustained GPU compute — see `acg-guru-5070.md`.

View File

@@ -1,100 +1,158 @@
# Memory Index
## Reference
- [ACG resource map](reference_resource_map.md) — **READ THIS FIRST** when a task references a server/service/tenant/API. What we have access to, how to connect from this machine, per-machine exceptions, gotchas. Points at the detail files below.
- [GURU-5070 Rust toolchain](reference_guru5070_rust_toolchain.md) — GURU-5070 now has cargo + MSVC + protoc; build/clippy/test guru-connect LOCALLY (set PROTOC to the winget path) instead of the build host. CI only clippy-checks the Linux server, not the Windows agent.
- [ACG Office Network Infrastructure](infra_office_network.md) — IPs/hosts/roles for pfSense/Jupiter/VMs/Docker. Check before assuming; .21 (Uranus) is storage.
- [Power Failure Runbook](../POWER_FAILURE_RUNBOOK.md) — Recovery order after a power event: Tailscale routes, libvirt/VMs, Seafile, NPM/DNS.
- [Syncro API — Invoice Verification Pattern](syncro_invoice_verification_pattern.md) — /invoices?customer_id=X returns no ticket linkage; query /invoices/{number} for ticket_id. Compare by ticket ID, not number.
- [Approval Workflow: Tools vs Projects](approval-workflow-tools-vs-projects.md) — Tools (remediation, scripts): Howard/Claude with approval. Projects (GuruRMM): Mike approval; features→roadmap, bugs→bug list.
- [Community Forum (Flarum)](reference_community_forum.md) — Flarum forum at community.azcomputerguru.com, API access, database, posting workflow.
- [Radio Show Website](reference_radio_website.md) — Astro static site at radio.azcomputerguru.com on IX server.
- [IX Server Access](reference_ix_server_access.md) — `ix.azcomputerguru.com` / 172.16.3.10. Reachable when Tailscale is on (no VPN). SSH currently uses sshpass with root password; key auth from GURU-5070 not configured yet (was CachyOS, now Win11 — verify).
- [Matomo Analytics](reference_matomo_analytics.md) — Self-hosted analytics at analytics.azcomputerguru.com, site IDs, tracking for all 3 sites.
- [TickTick Integration](reference_ticktick_integration.md) — OAuth API integration, MCP server, SOPS vault creds, project/task CRUD.
- [Client Docs Structure](reference_client_docs_structure.md) — clients/<name>/docs/ layout (overview, network, servers, cloud, security, rmm). Template: clients/_client_template/.
- [MSP Audit Scripts](reference_msp_audit_scripts.md) — server_audit.ps1 / workstation_audit.ps1 at projects/msp-tools/msp-audit-scripts/.
- [Pluto Build Server](reference_pluto_build_server.md) — Windows build VM: hostname PLUTO = Unraid VM "Claude-Builder" = 172.16.3.36 (all the same box). MSVC + WiX + Azure Trusted Signing. Drive via /rmm (agent enrolls as PLUTO) when SSH key isn't authorized.
- [Coord /messages API shape](reference_coord_messages_api_shape.md) — GET /api/coord/messages returns {total,skip,limit,messages[]} NOT a bare array; parse .messages[], strip control chars, read flag may be null.
- [Gitea API credential](reference_gitea_api_credential.md) — Gitea API (PRs/merges) as howard uses services/gitea-howard.sops.yaml password on internal http://172.16.3.20:3000; NOT the gururmm-server SSH password.
- [Gitea Internal API Access](reference_gitea_internal.md) — git.azcomputerguru.com is NOT behind Cloudflare — it's the office Cox IP NAT'd to NPM (openresty) on Jupiter. Prefer internal 172.16.3.20:3000 for reliability (bypasses NPM SSL-renewal reload blips).
- [Gitea git-op latency](reference_gitea_git_op_latency.md) — SSH (.20:2222) is SLOWEST (~1.5s); internal HTTP+token ~0.55s; SOPS lookup only ~0.33s. Don't switch to SSH for speed. Gitea SSH is .20:2222 (API ssh_url .21 is wrong).
- [GuruRMM technical reference](reference_gururmm.md) — Server (172.16.3.30) layout + downloads dir `/var/www/gururmm/downloads` + `.channel` sidecar rollout control (stable/beta) + privileged server access via the server's OWN root RMM agent (hostname `gururmm`, no SSH needed; plink fallback) + API + `context=user_session` (WTS impersonation) + build-pipeline vendoring at `deploy/build-pipeline/` + Linux agent systemd sandbox trap.
- [Trebesch DESKTOP-QNP3ON5 shell replacement](reference_trebesch_qnp3on5.md) — AT Trebesch box runs an Explorer shell replacement; explorer.exe owner check returns blank — use Win32_ComputerSystem.UserName. GuruRMM SWIFT-LION-2892.
## Users
- [Howard Enos](user_howard.md) — Mike's brother, technician, full access. Machines: ACG-TECH03L, Howard-Home (authoritative in users.json).
- [Mike — font preference](user_font_preference.md) — Mike prefers Lucida Console for monospace UI.
## Feedback
- [Scheduling = coord todo, not schedulers](feedback_scheduling_via_coord_todo.md) — Defer future work as a coord todo (POST /api/coord/todos; needs text + created_by_user + created_by_machine) for a later session to pick up. NOT /schedule remote CCR agents (no vault/creds there) or local scheduled tasks.
- [Attribution is read, never inferred](feedback_attribution_from_identity.md) — Who-did-what (user+machine) comes ONLY from identity.json + users.json + git authorship. Never infer from hostname patterns, the userEmail hint, or memory. The "5070" box is Mike's. sync.sh reconciles git config to identity.json; /save renders the User block via whoami-block.sh.
- [D2TESTNAS SSH Access](feedback_d2testnas_ssh.md) — Use root@192.168.0.9 with Paper123!@#, not sysadmin.
- [Bypass Permissions Setting](feedback_bypass_permissions_setting.md) — Set permissions.defaultMode to bypassPermissions in settings.json on all machines.
- [365 Remediation Tool](feedback_365_remediation_tool.md) — "remediation tool" = tiered ComputerGuru app suite via /remediation-tool; NOT CIPP, NOT the deprecated fabb3421.
- [CA managed programmatically (with discipline)](feedback_ca_programmatic_management.md) — Conditional Access CAN be written via Tenant Admin app; ALWAYS report-only first + exclude break-glass + confirm before enforcing. Overrides old "CA manual" rule.
- [Ollama Tier-0 Routing](feedback_ollama_tier0_routing.md) — Route drafts/summaries/classifications through Ollama (qwen3:14b). Mike designed ClaudeTools this way — not optional.
- [/save writes narrative directly](feedback_save_no_ollama.md) — No Ollama for /save; write all sections inline — too slow.
- [Identity precedence](feedback_identity_precedence.md) — Trust `.claude/identity.json` over the system-reminder `userEmail` hint when they disagree (shared-login machines).
- [1Password — always use service token](feedback_1password_service_token.md) — Source OP_SERVICE_ACCOUNT_TOKEN from SOPS for every `op` call. Desktop-app integration prompts are unacceptable in agent flows.
- [Point vault-access teammates at SOPS path](feedback_vault_pointer_for_teammates.md) — When relaying infra/credential info to Howard or other vault-access teammates, hand over the SOPS path + key anchors; don't transcribe the entry's fields into the message.
- [/tmp path mismatch on Windows](feedback_tmp_path_windows.md) — Write tool and Git Bash resolve `/tmp` to DIFFERENT real dirs. Use heredoc or workspace path for JSON payloads handed to curl.
- [Windows bash command mapping](feedback_windows_bash_mapping.md) — `bash` often resolves to WSL stub instead of Git/MSYS bash required by the harness. Fix by prepending `C:\Program Files\Git\bin` (and usr\bin) to PATH, or source `.claude/scripts/ensure-git-bash.ps1`. Profile has the logic; use plain `bash .claude/scripts/...` after remap. See the helper and this memory file for details.
- [SQL instance role — verify by connections, not name](feedback_sql_instance_role_by_connection.md) — Standard installed under default `SQLEXPRESS` instance name is real. Prove role with `sys.dm_exec_sessions` + `Get-NetTCPConnection -OwningProcess` before recommending stop/uninstall.
- [Clear-RecycleBin fails silently as SYSTEM](feedback_clear_recyclebin_system_context.md) — RMM-dispatched cleanup scripts cannot use `Clear-RecycleBin -Force`; the cmdlet uses Shell COM and silently no-ops without an interactive desktop. Enumerate `C:\$Recycle.Bin\<SID>\*` directly.
- [Graph CA policy reads are eventually consistent](feedback_graph_ca_policy_eventual_consistency.md) — After PATCHing a CA policy (204), wait ~5s before GET-verifying; immediate reads can be stale.
- [Graph password reset needs a privileged role](feedback_graph_password_reset_requires_role.md) — PATCH passwordProfile on an existing user 403s without a directory role; User.ReadWrite.All alone only sets a password at CREATE.
- [Vault writes — do the full sequence yourself](feedback_complete_vault_operations_end_to_end.md) — A vault entry = write plaintext → sops -e -i → git add/commit/push, all of it; don't stop at "encrypted on disk."
- [Syncro is the default PSA; Autotask is opt-in](feedback_psa_default_syncro.md) — Ticketing/billing/customers default to Syncro (/syncro). Only use /autotask on an explicit "in Autotask" request. /autotask kept local/undistributed.
- [Paste-safe command formatting (Howard)](feedback_command_formatting.md) — Two clauses, one root cause: (a) multi-line scripts not semicolon one-liners (wrap breaks paste), (b) all code at column 0 inside fences (indentation breaks PowerShell paste).
- [Autonomous infra/build setup](feedback_autonomous_infra_setup.md) — During infra/build/CI/dev setup, just install prerequisites and push through routine steps; reserve check-ins for genuine decisions (forks, destructive/outward, client/prod).
- [Check patterns before asking](feedback_check_patterns_before_asking.md) — Before asking how to do something repeat-style (sync, save, sweep, billing), study existing artifacts and workflow docs first; reach for similar past artifacts as the template.
- [Pricing verification — no guessing](policy_pricing_verification.md) — ANY cost presented to the team or a client MUST be verified via live web lookup (WebFetch/WebSearch, fallback to headless Chrome). Never estimate from training data. Cite source + date inline. If unreachable, say so — do NOT substitute a guess.
- [Client communication tone](feedback_client_tone.md) — How to write client-facing Syncro comments — expert partner, not intake questionnaire.
- [Add Mike as owner on all Entra apps](feedback_entra_app_owner.md) — Apps created via management SP have no user owner — must add Mike manually or publisher verification fails.
- [No TOML/config file approach for endpoints](feedback_no_toml_config_endpoints.md) — User explicitly prohibits TOML or config-file-based endpoint configuration — this will never be approved.
- [Python on Windows — use py launcher](feedback_python_windows.md) — Windows Store python/python3 aliases disabled; always use py or jq on DESKTOP-0O8A1RL.
- [Memory tooling may delete now — additive-only constraint dropped](feedback_memory_sync_destructive_ok.md) — As of 2026-06-02, memory-dream and sync-memory.sh are sanctioned to perform destructive ops (apply proposed merges/dedups, propagate repo deletions back to harness profile stores). Onboarding-phase safety net now fights deliberate consolidation (e.g. 2026-06-01's 39 deletions resurrected on the next sync). Script updates pending.
- [Unsaved sessions are recoverable from transcripts](feedback_session_recovery.md) — Crashed/closed-before-save sessions live in `~/.claude/projects/<slug>/*.jsonl`; the detector auto-recovers orphans, `/recover <uuid>` does it manually. Ollama prose + Python verbatim. See `.claude/RECOVERY.md`.
### Syncro
- [Syncro API plumbing](feedback_syncro_api.md) — Content-Type required on all POST/PUT; NO idempotency anywhere — always GET before retrying; response wrappers (`.ticket.id`, `.comment.id`); add_line_item shape (internal ID, flat response, required fields); HTML uses `<br>` not `<ul>/<li>`; timer_entry response is FLAT but SUPERSEDED (use add_line_item).
- [Syncro billing rules](feedback_syncro_billing.md) — Bill with `add_line_item` directly (not timers); fetch rates LIVE; never invent labor names (real product names only); match labor type to delivery channel (never "Prepaid project labor"); labor `taxable:false` (AZ); warranty `1049360` (never patch price); emergency `26184` ×1.5 once, branch by `prepay_hours`; corrections preserve original tech's user_id; estimate hardware `32252`.
- [Syncro workflow rules](feedback_syncro_workflow.md) — ALWAYS preview comments before posting (no exceptions); verify appointment day-of-week ("Saturday 2026-05-23") before creating; ASK who the appointment owner is; leave `contact_id` BLANK by default for ALL customers (ignore Syncro's contact-picker auto-default).
- [Syncro lessons / incident archive](feedback_syncro_history.md) — Detail behind the three rule files: tickets (#32332, #32312, #32225, #32253, #32203, #32185, #32142, #32304, #32333), verbatim Mike/Howard/Winter quotes, dates, tech user_id table (Mike 1735 / Howard 1750 / Winter 1737 / Rob 1760), labor product table, and superseded-rule history.
### GuruRMM
- [GuruRMM operational rules](feedback_gururmm.md) — Six rules: (1) RMM dev = Mike, never Howard (368/0 commits); GuruScan is Howard's. (2) Agent parity Win+Linux+macOS in same change. (3) Builds via Gitea webhook pipeline only, never SSH. (4) #bot-alerts only for client/ticket impact, skip internal infra/dev. (5) Identify agents by IP, not by reconning candidates. (6) UNC paths in user_session need [char]92 — literals get halved.
- [Build channel default = beta](feedback_gururmm_build_channel_default.md) — New agent builds must be tagged BETA by default (stable = explicit promote re-tag); distinct from agents defaulting to the stable CHANNEL (correct). Fixed build-windows/linux.sh 2026-06-01; macOS already correct. Enables beta-first canary.
- [Dashboard beta-first deploy](feedback_dashboard_beta_first.md) — Dashboard auto-builds to rmm-beta.azcomputerguru.com on push; prod (rmm.azcomputerguru.com) is explicit promote-only via promote-dashboard.sh --confirm. Never hand-rsync prod. One artifact, nginx sub_filter BETA banner. Stood up 2026-06-02.
### Cascades
- [Cascades operational rules](feedback_cascades.md) — Two active rules: (1) folder redirection (fdeploy) needs subfolders PRE-CREATED before first logon or it caches a failure forever; recovery via fix-shell-redirect.ps1. (2) ALWAYS ask which security group(s) a new user goes into — never auto-derive from OU.
## Machine
- [GURU-5070 Workstation Setup](reference_workstation_setup.md) — Mike's primary (owner confirmed 2026-05-26). Windows 11 Pro. Renamed from OC-5070 → ACG-5070/acg-guru-5070 → GURU-5070; all the same box, all Mike's.
- [GURU-BEAST-ROG Setup Status](machine_windows_guru_setup_status.md) — Windows workstation fully configured except SSH key deployment to servers.
## Project
- [Automate memory consolidation/lint (phased)](project_memory_consolidation_automation.md) — Eventually auto-run /memory-dream; lint+additive fixes can automate early, merges/deletes stay human-approved. Engine: .claude/skills/memory-dream/ + .claude/scripts/sync-memory.sh.
- [Trebesch PST consolidation (staged)](project_trebesch_pst_consolidation.md) — Address-book CSV from 24 PSTs on DESKTOP-QNP3ON5; scripts staged at .claude/tmp/treb-*.ps1, WAITING for Howard's 6pm-MST 2026-06-01 go signal (attended run). See [[reference_trebesch_qnp3on5]].
- [GuruRMM project state](project_gururmm.md) — Dev principles (every feature full-stack: backend+API+UI+docs+scalability; product works without AI; FEATURE_ROADMAP update is part of definition-of-done; mirrors guru-rmm/docs/DESIGN.md). Webhook docs-only build guard (SPEC-020 Phase 0; webhook-handler.py repo copy is STALE — don't redeploy). Mac install-hooks.sh setup STILL PENDING on Mikes-MacBook-Air.
- [GuruConnect](project_guruconnect.md) — v2 direction (native-first full key fidelity Win+R/Ctrl+Alt+Del + bidirectional file cut/paste/drag; WebRTC fallback only; standalone-first + RMM contract; tenancy-ready schema; Mike willing to scrap v1). Manual deploy procedure to 172.16.3.30 (build-on-server in login shell; sqlx runtime queries; NPM `CONNECT_TRUSTED_PROXIES=172.16.3.20` gotcha). v2 live since 2026-05-30.
- [Apple MDM + Developer certs (GuruRMM mobile)](project_apple_mdm_certs.md) — ACG holds Apple Developer+signing and Apple MDM Push certs (acquired 2026-05-29) for SPEC-017. MDM push cert RENEWS ANNUALLY on the same Apple ID or all enrolled iOS devices break.
- [Only RMM & GC are versionable products](project_versionable_products.md) — GuruRMM + GuruConnect are the only products with own repos/submodules; everything else stays in the claudetools monorepo. Split only for independent pipeline OR versioned external consumer.
- [Quantum GoDaddy M365 tenant](project_quantum_godaddy_m365_tenant.md) — quantumwms.com parked in a GoDaddy-provisioned M365 tenant (id ddf3d2c9-b76c-40d9-a216-9f11a1a26f97, netorg18235235.onmicrosoft.com); blocks Pax8 migration until GoDaddy removed.
- [Cascades](project_cascades.md) — Active state: Syncro ticket #110680053 + plan file (machine-specific path on Howard's box), admin accounts (sysadmin@=Howard, admin@=Mike — daily-driver, NOT break-glass), Phase-B caregiver CA pilot (SG-Caregivers-Pilot, group-scoped never tenant-wide), prepaid block ~37.5h (rate TBD), pilot cleanup checklist.
- [Cascades history](project_cascades_history.md) — fdeploy 502/ACL root cause (Flags=1211→187 fix), 2026-04-29 CA-rescoping decision (Howard pulled the brakes on tenant-wide), 2026-05-14 per-user-security-group decision rationale.
- [Sync script bug — untracked files (RESOLVED)](project_sync_script_bug.md) — FIXED 2026-05-21: sync.sh now uses `git status --porcelain` for change detection (repo + vault).
- [MasterBooter Side Project](project_masterbooter.md) — Howard's Rust+Slint Windows deployment toolkit at C:\MasterBooter, separate from client work. Do not log to clients/.
- [Audio Processor Architecture](project_audio_processor_architecture.md) — Segment-first pipeline: detect breaks before transcription for complete content capture.
- [Neptune SBR Email Routing Setup](project_neptune_sbr_email_routing.md) — Full SBR routing chain, config file locations, MailProtector integration, access methods. Treat routing breakage as systemic (devcon, Sorensen/rieussetcorp), not per-client.
- [Dataforth Test Datasheet Pipeline](project_datasheet_pipeline.md) — Full pipeline rebuilt 2026-03-27. Server-side generation replaces DFWDS/Uploader. Website upload still broken.
- [Dataforth](project_dataforth.md) — M365 email (Graph API; tenant in vault at clients/dataforth/m365.sops.yaml); neptune.acghosting.com is ACG's, NOT Dataforth's. MFA enforced 2026-04-04 (3 CA policies). AJ needs dataforthgit@ forwarding.
- [Dataforth history (2026-03-27 incident)](project_dataforth_history.md) — DF-JOEL2 compromise via ScreenConnect social-engineering, attacker C2 IPs + IC3 case + remediation log + MFA rollout origin story + Joel Lohr retirement. RESOLVED 2026-04-04.
- [Radio show co-host — Tara, not Tom](radio_show_no_cohost_named_tom.md) — Co-host in 2014-s6e19 and 2016-s8e43 is Tara. "Tom" was hallucinated; rename complete.
- [Proposal: centralize config in identity.json](proposal_identity_centralization.md) — Rationale for the identity.json machine-config centralization (claudetools_root, ollama/python); now implemented.
- [ACG MSP tool stack](reference_acg_msp_stack.md) — ScreenConnect/CW Control, Splashtop, Syncro, Datto RMM, Datto EDR/AV, GuruRMM are ACG's OWN tools; do not flag as foreign/threat on managed machines (Defender-off is expected when Datto AV is active).
- [ACG Website Hosting](project_azcomputerguru_hosting.md) — azcomputerguru.com is hosted on IX Web Hosting via cPanel.
- [jq on Windows emits CRLF](feedback_jq_crlf_windows.md) — winget jq outputs CRLF; trailing \r silently breaks `for x in $(jq ...)` loops + read-from-@tsv. Override `jq(){ command jq "$@"|tr -d '\r'; }`. Windows-build-specific (passes on Mac/Linux).
- [ScreenConnect RESTful API auth](reference_screenconnect_api.md) — CTRLAuthHeader = raw api_secret (no Basic/b64) + Origin header; only method is GetSessionsByName; matches blank-for-agents Name field so it cannot enumerate full inventory.
# Memory Index
## Reference
- [ACG resource map](reference_resource_map.md) — **READ THIS FIRST** when a task references a server/service/tenant/API. What we have access to, how to connect from this machine, per-machine exceptions, gotchas. Points at the detail files below.
- [GURU-5070 Rust toolchain](reference_guru5070_rust_toolchain.md) — GURU-5070 now has cargo + MSVC + protoc; build/clippy/test guru-connect LOCALLY (set PROTOC to the winget path) instead of the build host. CI only clippy-checks the Linux server, not the Windows agent.
- [ACG Office Network Infrastructure](infra_office_network.md) — IPs/hosts/roles for pfSense/Jupiter/VMs/Docker. Check before assuming; .21 (Uranus) is storage.
- [Power Failure Runbook](../POWER_FAILURE_RUNBOOK.md) — Recovery order after a power event: Tailscale routes, libvirt/VMs, Seafile, NPM/DNS.
- [Syncro API — Invoice Verification Pattern](syncro_invoice_verification_pattern.md) — /invoices?customer_id=X returns no ticket linkage; query /invoices/{number} for ticket_id. Compare by ticket ID, not number.
- [Approval Workflow: Tools vs Projects](approval-workflow-tools-vs-projects.md) — Tools (remediation, scripts): Howard/Claude with approval. Projects (GuruRMM): Mike approval; features→roadmap, bugs→bug list.
- [CDP Chrome driver](reference_cdp_chrome_driver.md) — Drive Chrome via DevTools Protocol (.claude/scripts/cdp.py): visible window + screenshots-to-disk so Gemini/Grok can SEE the live site. Use localhost not 127.0.0.1; dedicated profile. Antigravity-style.
- [Firefox driver (ff.py)](reference_ff_firefox_driver.md) — PREFERRED browser driver. Drive Firefox via Playwright (.claude/scripts/ff.py): daemon on :9333, persistent profile, nav/shot/click/type/eval/console/network. Mike dislikes Chrome; claude-in-chrome connector disabled 2026-06-06.
- [Community Forum (Flarum)](reference_community_forum.md) — Flarum forum at community.azcomputerguru.com, API access, database, posting workflow.
- [Radio Show Website](reference_radio_website.md) — Astro static site at radio.azcomputerguru.com on IX server.
- [IX Server Access](reference_ix_server_access.md) — `ix.azcomputerguru.com` / 172.16.3.10. Reachable when Tailscale is on (no VPN). SSH currently uses sshpass with root password; key auth from GURU-5070 not configured yet (was CachyOS, now Win11 — verify).
- [Cloudflare access](reference_cloudflare_access.md) — Cloudflare API creds in SOPS `services/cloudflare.sops.yaml` (full DNS + account tokens; azcomputerguru zone_id 1beb9917...). azcomputerguru.com DNS is on Cloudflare (not IX) — edit via Cloudflare API, not whmapi1.
- [Matomo Analytics](reference_matomo_analytics.md) — Self-hosted analytics at analytics.azcomputerguru.com, site IDs, tracking for all 3 sites.
- [TickTick Integration](reference_ticktick_integration.md) — OAuth API integration, MCP server, SOPS vault creds, project/task CRUD.
- [Client Docs Structure](reference_client_docs_structure.md) — clients/<name>/docs/ layout (overview, network, servers, cloud, security, rmm). Template: clients/_client_template/.
- [MSP Audit Scripts](reference_msp_audit_scripts.md) — server_audit.ps1 / workstation_audit.ps1 at projects/msp-tools/msp-audit-scripts/.
- [Pluto Build Server](reference_pluto_build_server.md) — Windows build VM: hostname PLUTO = Unraid VM "Claude-Builder" = 172.16.3.36 (all the same box). MSVC + WiX + Azure Trusted Signing. Drive via /rmm (agent enrolls as PLUTO) when SSH key isn't authorized.
- [Coord /messages API shape](reference_coord_messages_api_shape.md) — GET /api/coord/messages returns {total,skip,limit,messages[]} NOT a bare array; parse .messages[], strip control chars, read flag may be null.
- [Gitea API credential](reference_gitea_api_credential.md) — Gitea API (PRs/merges) as howard uses services/gitea-howard.sops.yaml password on internal http://172.16.3.20:3000; NOT the gururmm-server SSH password.
- [Gitea Internal API Access](reference_gitea_internal.md) — git.azcomputerguru.com is NOT behind Cloudflare — it's the office Cox IP NAT'd to NPM (openresty) on Jupiter. Prefer internal 172.16.3.20:3000 for reliability (bypasses NPM SSL-renewal reload blips).
- [Gitea git-op latency](reference_gitea_git_op_latency.md) — SSH (.20:2222) is SLOWEST (~1.5s); internal HTTP+token ~0.55s; SOPS lookup only ~0.33s. Don't switch to SSH for speed. Gitea SSH is .20:2222 (API ssh_url .21 is wrong).
- [GuruRMM technical reference](reference_gururmm.md) — Server (172.16.3.30) layout + downloads dir `/var/www/gururmm/downloads` + `.channel` sidecar rollout control (stable/beta) + privileged server access via the server's OWN root RMM agent (hostname `gururmm`, no SSH needed; plink fallback) + API + `context=user_session` (WTS impersonation) + build-pipeline vendoring at `deploy/build-pipeline/` + Linux agent systemd sandbox trap.
- [RMM agent update model](rmm-agent-update-model.md) — Agent updates are server-PUSH on heartbeat (no self-poll); available versions = filesystem scan needing a `.sha256`; promote flips `.channel` sidecars beta→stable globally. Two stranders: beta-first freezes stable until an explicit promote; agents older than ~0.6.50 re-enroll with a NEW device_id/agent row when updated.
- [GuruRMM physical server storage](gururmm-physical-server-storage.md) — New box 172.16.1.231 (temp IP→will be .30), Ubuntu 26.04, ssh key `gururmm-physical`/alias `gururmm-new`. SSD (915G root) = HOT (PG default tablespace + WAL + builds); HDD ext4 at `/data` = COLD (`gururmm_cold` PG tablespace for aged `agent_logs` partitions + downloads + backups + archive). The #3 retention answer.
- [Trebesch DESKTOP-QNP3ON5 shell replacement](reference_trebesch_qnp3on5.md) — AT Trebesch box runs an Explorer shell replacement; explorer.exe owner check returns blank — use Win32_ComputerSystem.UserName. GuruRMM SWIFT-LION-2892.
- [reference_backblaze_storage_rate](reference_backblaze_storage_rate.md) -- ACG's Backblaze B2 storage cost rate ($0.00695/GB) for the GuruRMM mspbackups storage-cost calculation
- [Unraid VM no-IP causes](unraid-windows-vm-virtio-no-ip.md) — PRIMARY (general "new VMs stopped getting IPs lately"): Docker sets bridge-nf-call-iptables=1, so br0 VM DHCP OFFERs hit DOCKER-FORWARD (no br0 ACCEPT) and get dropped; new VMs can't complete DORA (existing renew via ESTABLISHED). Fix `=0` runtime (needs persistent post-Docker hook; not yet persisted on Jupiter). SECONDARY (Windows VM): virtio-net has no in-box driver -> use e1000 or virtio-win. Diagnose: tcpdump DHCP on pfSense; /sys vnetN rx_packets.
- [Starr Pass mail routing](reference_starrpass_mail_routing.md) — starrpass.com is DIRECT to MS (EOP/Defender, tenant 222450dd…); only devconllc.com is on Mailprotector (MP acct 16170). Check @starrpass.com quarantine/rejects via remediation-tool, not Mailprotector.
- [AAD Connect msDS-KeyCredentialLink writeback](reference_aadconnect_keycredlink_writeback.md) — "completed-export-errors" + 8344 INSUFF_ACCESS_RIGHTS on a protected admin account = WHfB key writeback blocked by AdminSDHolder. Diagnose with csexport /f:x; fix with dsacls WP;msDS-KeyCredentialLink on AdminSDHolder + SDProp.
- [UniFi Site Manager cloud API](reference_unifi_site_manager_api.md) — `api.ui.com` + `X-API-KEY` (vault `services/unifi-site-manager`) = remote access to the WHOLE ACG UniFi fleet (~36 consoles) outside UOS. Tier1 `/v1/hosts|sites|devices|isp-metrics` = inventory+health+WAN. Tier2 CONNECTOR `/v1/connector/consoles/{id}/proxy/network/api/s/default/stat/{device,sta}` = **full UOS parity** (per-radio cu_total airtime + per-client RSSI) for ANY console, remote. Backend `unifi-wifi/scripts/gw-sitemanager.sh` (`fleet|devices|sites|isp|net`). Standalone UDM WAN SSH usually firewalled; per-console SSH pw at `clients/<slug>/udm-ssh`.
- [reference_sqlx_migrations_immutable](reference_sqlx_migrations_immutable.md) -- NEVER edit an already-applied sqlx migration file — even a comment. sqlx::migrate! checksums each file at compile time and validates against _sqlx_migrations at startup; a changed checksum crash-loops the server with "migration N was previously applied but has been modified". Code review MUST flag any edit to an applied migration.
## Users
- [Howard Enos](user_howard.md) — Mike's brother, technician, full access. Machines: ACG-TECH03L, Howard-Home (authoritative in users.json).
- [Mike — font preference](user_font_preference.md) — Mike prefers Lucida Console for monospace UI.
## Feedback
- [Bot alerts need a ticket link](feedback_bot_alert_ticket_link.md) — Syncro ticket bot-alerts MUST include a clickable link: https://computerguru.syncromsp.com/tickets/<internal_id> (internal id, not ticket number). post-bot-alert.sh posts raw text; put the URL in the message.
- [Mac RMM authentication fixed](feedback_mac_rmm_auth_fixed.md) — Use `.claude/scripts/rmm-auth.sh` helper instead of heredoc pattern. Heredoc with `--data-binary @-` fails on macOS. Helper uses `jq -n --arg` to build JSON safely. Usage: `eval "$(bash .claude/scripts/rmm-auth.sh)"` sets $TOKEN, $RMM, $REPO_ROOT. Updated in /rmm Phase 0.
- [Verify committed state before push](feedback_verify_committed_state_before_push.md) — webhook builds from origin/main: verify the COMMITTED build (git stash + build), not the working tree; bad git-add pathspec silently aborts staging. Stage by directory.
- [Scheduling = coord todo, not schedulers](feedback_scheduling_via_coord_todo.md) — Defer future work as a coord todo (POST /api/coord/todos; needs text + created_by_user + created_by_machine) for a later session to pick up. NOT /schedule remote CCR agents (no vault/creds there) or local scheduled tasks.
- [DMARC rua INKY only when onboarded](feedback_dmarc_rua_inky_onboarded_only.md) — Don't point a client's DMARC rua at reports-sg.inkydmarc.com unless that client is onboarded to INKY (most aren't). Use plain `p=none` with no rua otherwise.
- [Use rmm-search to find machines](feedback_rmm_search_skill.md) — Find GuruRMM agents via the `rmm-search` skill (`rmm-search.sh <words> [-c client]`), never hand-grep /api/agents (it bleeds across clients). Then hand hostname/id to `/rmm`.
- [Attribution is read, never inferred](feedback_attribution_from_identity.md) — Who-did-what (user+machine) comes ONLY from identity.json + users.json + git authorship. Never infer from hostname patterns, the userEmail hint, or memory. The "5070" box is Mike's. sync.sh reconciles git config to identity.json; /save renders the User block via whoami-block.sh.
- [D2TESTNAS SSH Access](feedback_d2testnas_ssh.md) — Use root@192.168.0.9 with Paper123!@#, not sysadmin.
- [Bypass Permissions Setting](feedback_bypass_permissions_setting.md) — Set permissions.defaultMode to bypassPermissions in settings.json on all machines.
- [365 Remediation Tool](feedback_365_remediation_tool.md) — "remediation tool" = tiered ComputerGuru app suite via /remediation-tool; NOT CIPP, NOT the deprecated fabb3421.
- [CA managed programmatically (with discipline)](feedback_ca_programmatic_management.md) — Conditional Access CAN be written via Tenant Admin app; ALWAYS report-only first + exclude break-glass + confirm before enforcing. Overrides old "CA manual" rule.
- [Ollama Tier-0 Routing](feedback_ollama_tier0_routing.md) — Route drafts/summaries/classifications through Ollama (qwen3:14b). Mike designed ClaudeTools this way — not optional.
- [/save writes narrative directly](feedback_save_no_ollama.md) — No Ollama for /save; write all sections inline — too slow.
- [Identity precedence](feedback_identity_precedence.md) — Trust `.claude/identity.json` over the system-reminder `userEmail` hint when they disagree (shared-login machines).
- [1Password — always use service token](feedback_1password_service_token.md) — Source OP_SERVICE_ACCOUNT_TOKEN from SOPS for every `op` call. Desktop-app integration prompts are unacceptable in agent flows.
- [Point vault-access teammates at SOPS path](feedback_vault_pointer_for_teammates.md) — When relaying infra/credential info to Howard or other vault-access teammates, hand over the SOPS path + key anchors; don't transcribe the entry's fields into the message.
- [/tmp path mismatch on Windows](feedback_tmp_path_windows.md) — Write tool and Git Bash resolve `/tmp` to DIFFERENT real dirs. Use heredoc or workspace path for JSON payloads handed to curl.
- [Windows strips embedded double-quotes](feedback_windows_quote_stripping.md) — Embedded `"` in an arg gets eaten twice over: PowerShell->curl.exe (CommandLineToArgvW) AND RMM->cmd.exe. Use single-quoted heredoc `<<'JSON'` + `--data-binary @-` for bodies; build `"` from `[char]34`; or drop the quoted part (e.g. `shutdown /c`).
- [Interview the AI / read its docs before probing](feedback_interview_ai_read_docs.md) — To learn an external AI/CLI's syntax or capabilities, READ its bundled docs (Grok: `~/.grok/docs/user-guide/`, `README.md`, `grok inspect`/`models`/`--help`) or interview the model; don't guess flags or run slow trial-and-error. One run to confirm a doc-derived hypothesis, not a dozen to discover.
- [Web search over blind probing](feedback_web_search_over_probing.md) — For external API/capability discovery, LEAD with web search (grok/gemini) + vendor docs; live endpoint-probing only CONFIRMS a hypothesis, never the primary discovery method (it mostly 404s, "highly suspect"). Reading a system's OWN config is fine; guessing unknown PATHS is not. Web-search bots being flaky is a must-fix (CT_THOUGHTS Thought 2).
- [Windows bash command mapping](feedback_windows_bash_mapping.md) — `bash` often resolves to WSL stub instead of Git/MSYS bash required by the harness. Fix by prepending `C:\Program Files\Git\bin` (and usr\bin) to PATH, or source `.claude/scripts/ensure-git-bash.ps1`. Profile has the logic; use plain `bash .claude/scripts/...` after remap. See the helper and this memory file for details.
- [Git must authenticate non-interactively](feedback_git_noninteractive_auth.md) — Mike's gripe with Git for Windows is the constant password prompts (GCM) that hang automation, NOT the tool itself. D:\ClaudeTools is set to `credential.helper=store` primed with the azcomputerguru Gitea API token (host 172.16.3.20:3000); always set `GIT_TERMINAL_PROMPT=0`. Any never-prompts solution is acceptable.
- [Vault git auth — GCM shadows store token](feedback_vault_gcm_shadow_auth.md) — vault sync "Failed to authenticate user" on git.azcomputerguru.com: GCM is first in the helper chain and shadows the valid store token. Fix (machine-local): store-only credential.helper reset + pin `azcomputerguru@` in the vault remote URL so store returns the durable PAT (not the volatile OAUTH_USER JWT). Applied GURU-5070 2026-06-07.
- [Antigravity agy.exe is not a headless CLI](reference_antigravity_agy_not_headless.md) — the `agy` skill's real backend is `@google/gemini-cli`, not the Antigravity `agy.exe` (IDE agent, no stdout, hangs). Don't reinstall agy.exe expecting headless output. Mike has a paid Gemini account, so stay on gemini-cli past the June 18 free-tier sunset (prefer `GEMINI_API_KEY`).
- [SQL instance role — verify by connections, not name](feedback_sql_instance_role_by_connection.md) — Standard installed under default `SQLEXPRESS` instance name is real. Prove role with `sys.dm_exec_sessions` + `Get-NetTCPConnection -OwningProcess` before recommending stop/uninstall.
- [RMM password setting limitation](feedback_rmm_password_limitation.md) — `net user <user> <password>` via GuruRMM fails silently (exit 0 but password doesn't set). Tested PowerShell AND CMD - both fail. ScreenConnect CMD works (also as SYSTEM). GuruRMM agent bug in process spawning. Use ScreenConnect for password ops. HIGH priority to fix.
- [Clear-RecycleBin fails silently as SYSTEM](feedback_clear_recyclebin_system_context.md) — RMM-dispatched cleanup scripts cannot use `Clear-RecycleBin -Force`; the cmdlet uses Shell COM and silently no-ops without an interactive desktop. Enumerate `C:\$Recycle.Bin\<SID>\*` directly.
- [Graph CA policy reads are eventually consistent](feedback_graph_ca_policy_eventual_consistency.md) — After PATCHing a CA policy (204), wait ~5s before GET-verifying; immediate reads can be stale.
- [Graph password reset needs a privileged role](feedback_graph_password_reset_requires_role.md) — PATCH passwordProfile on an existing user 403s without a directory role; User.ReadWrite.All alone only sets a password at CREATE.
- [Vault writes — do the full sequence yourself](feedback_complete_vault_operations_end_to_end.md) — A vault entry = write plaintext → sops -e -i → git add/commit/push, all of it; don't stop at "encrypted on disk."
- [Exchange role recurring gap — backfill, don't promise](feedback_exchange_role_recurring_gap.md) — EXO email-cleanup 401/403 = Exchange Operator SP missing the Exchange Admin directory role (consent never grants it). Fix: `assign-exchange-role.sh <domain|--all>` (idempotent); audit with `--all --verify`. Fleet backfilled 2026-06-08. Verify membership via roleManagement/directory/roleAssignments (not the laggy directoryRoles/members list); EXO propagation 15-60min.
- [Syncro is the default PSA; Autotask is opt-in](feedback_psa_default_syncro.md) — Ticketing/billing/customers default to Syncro (/syncro). Only use /autotask on an explicit "in Autotask" request. /autotask kept local/undistributed.
- [Paste-safe command formatting (Howard)](feedback_command_formatting.md) — Two clauses, one root cause: (a) multi-line scripts not semicolon one-liners (wrap breaks paste), (b) all code at column 0 inside fences (indentation breaks PowerShell paste).
- [Autonomous infra/build setup](feedback_autonomous_infra_setup.md) — During infra/build/CI/dev setup, just install prerequisites and push through routine steps; reserve check-ins for genuine decisions (forks, destructive/outward, client/prod).
- [Check patterns before asking](feedback_check_patterns_before_asking.md) — Before asking how to do something repeat-style (sync, save, sweep, billing), study existing artifacts and workflow docs first; reach for similar past artifacts as the template.
- [Cascades scan-to-folder uses svc-scan](feedback_cascades_scan_account.md) — Every scanner->network-folder setup at Cascades reuses the one `svc-scan` AD service account (NTLMv2, vaulted); never make a per-printer scan account.
- [Drive-letter mapping convention](feedback_drive_letter_mapping.md) — Pick the MAIN/primary drive letter first (consistent across users for the principal share), then assign smaller/secondary maps. Don't retroactively renumber existing maps unless asked.
- [Calibrate effort to stakes](feedback_calibrate_effort_to_stakes.md) — Don't over-verify or over-engineer low-consequence details; confirm the happy path, note the limitation, and take the simplest path (e.g. put the instruction in the prompt) instead of building robust mechanisms.
- [Pricing verification — no guessing](policy_pricing_verification.md) — ANY cost presented to the team or a client MUST be verified via live web lookup (WebFetch/WebSearch, fallback to headless Chrome). Never estimate from training data. Cite source + date inline. If unreachable, say so — do NOT substitute a guess.
- [Client communication tone](feedback_client_tone.md) — How to write client-facing Syncro comments — expert partner, not intake questionnaire.
- [Default to inline links](feedback_inline_links.md) — Use `[text](url)` inline markdown links (clickable, wrap-safe) not bare URLs in code fences; exception = raw URL the user must copy/paste.
- [Add Mike as owner on all Entra apps](feedback_entra_app_owner.md) — Apps created via management SP have no user owner — must add Mike manually or publisher verification fails.
- [No TOML/config file approach for endpoints](feedback_no_toml_config_endpoints.md) — User explicitly prohibits TOML or config-file-based endpoint configuration — this will never be approved.
- [Python on Windows — use py launcher](feedback_python_windows.md) — Windows Store python/python3 aliases disabled; always use py or jq on DESKTOP-0O8A1RL.
- [Memory tooling may delete now — additive-only constraint dropped](feedback_memory_sync_destructive_ok.md) — As of 2026-06-02, memory-dream and sync-memory.sh are sanctioned to perform destructive ops (apply proposed merges/dedups, propagate repo deletions back to harness profile stores). Onboarding-phase safety net now fights deliberate consolidation (e.g. 2026-06-01's 39 deletions resurrected on the next sync). Script updates pending.
- [Unsaved sessions are recoverable from transcripts](feedback_session_recovery.md) — Crashed/closed-before-save sessions live in `~/.claude/projects/<slug>/*.jsonl`; the detector auto-recovers orphans, `/recover <uuid>` does it manually. Ollama prose + Python verbatim. See `.claude/RECOVERY.md`.
- [agy review is not read-only](feedback_agy_review_not_readonly.md) — agy review/review-files CAN write files + run npm despite docs claiming plan-mode; always git diff after and treat Gemini's output as a proposal to validate, not trusted/finished work.
- [Don't present inferred topology as fact](feedback_no_inferred_topology_as_fact.md) — Private-IP overlap (172.16.x on both sides) is NOT proof of a site-to-site link; I fabricated a VWP<->office VPN. State observations vs inferences; a failed reachability test disproves a link, don't explain it away; test "can reach RMM" against the EXTERNAL endpoint, not internal 172.16.3.30.
### Syncro
- [Syncro API plumbing](feedback_syncro_api.md) — Content-Type required on all POST/PUT; NO idempotency anywhere — always GET before retrying; response wrappers (`.ticket.id`, `.comment.id`); add_line_item shape (internal ID, flat response, required fields); HTML uses `<br>` not `<ul>/<li>`; timer_entry response is FLAT but SUPERSEDED (use add_line_item).
- [Syncro billing rules](feedback_syncro_billing.md) — Bill with `add_line_item` directly (not timers); fetch rates LIVE; never invent labor names (real product names only); match labor type to delivery channel (never "Prepaid project labor"); labor `taxable:false` (AZ); warranty `1049360` (never patch price); emergency `26184` ×1.5 once, branch by `prepay_hours`; corrections preserve original tech's user_id; estimate hardware `32252`.
- [Syncro workflow rules](feedback_syncro_workflow.md) — ALWAYS preview comments before posting (no exceptions); verify appointment day-of-week ("Saturday 2026-05-23") before creating; ASK who the appointment owner is; leave `contact_id` BLANK by default for ALL customers (ignore Syncro's contact-picker auto-default).
- [Syncro lessons / incident archive](feedback_syncro_history.md) — Detail behind the three rule files: tickets (#32332, #32312, #32225, #32253, #32203, #32185, #32142, #32304, #32333), verbatim Mike/Howard/Winter quotes, dates, tech user_id table (Mike 1735 / Howard 1750 / Winter 1737 / Rob 1760), labor product table, and superseded-rule history.
### GuruRMM
- [GuruRMM operational rules](feedback_gururmm.md) — Six rules: (1) RMM dev = Mike, never Howard (368/0 commits); GuruScan is Howard's. (2) Agent parity Win+Linux+macOS in same change. (3) Builds via Gitea webhook pipeline only, never SSH. (4) #bot-alerts only for client/ticket impact, skip internal infra/dev. (5) Identify agents by IP, not by reconning candidates. (6) UNC paths in user_session need [char]92 — literals get halved.
- [Build channel default = beta](feedback_gururmm_build_channel_default.md) — New agent builds must be tagged BETA by default (stable = explicit promote re-tag); distinct from agents defaulting to the stable CHANNEL (correct). Fixed build-windows/linux.sh 2026-06-01; macOS already correct. Enables beta-first canary.
- [Dashboard beta-first deploy](feedback_dashboard_beta_first.md) — Dashboard auto-builds to rmm-beta.azcomputerguru.com on push; prod (rmm.azcomputerguru.com) is explicit promote-only via promote-dashboard.sh --confirm. Never hand-rsync prod. One artifact, nginx sub_filter BETA banner. Stood up 2026-06-02.
### Cascades
- [Cascades operational rules](feedback_cascades.md) — Active rules: (1) folder redirection (fdeploy) needs subfolders PRE-CREATED before first logon or it caches a failure forever; recovery via fix-shell-redirect.ps1. (2) ALWAYS ask which security group(s) a new user goes into — never auto-derive from OU. (3) Do NOT lock down the legacy Main\Company Web Docs\Accounting (Everyone:Full) folder — still in active use.
- [Cascades FR GPO fix](reference_cascades_fr_gpo_fix.md) — Native Folder Redirection was DOA on every machine: redirect targets were in a misnamed `fdeploy1.ini` (Windows reads `fdeploy.ini`) → empty target path → silent no-op → per-user registry workaround every time. Fixed 2026-06-08 (correct fdeploy.ini + version bump). Also: CS-SERVER live RMM agent is `c39f1de7...` (old `6766e973` stale).
- [feedback_ascii_only_api_payloads](feedback_ascii_only_api_payloads.md) -- On Windows/Git-bash, non-ASCII chars (em-dash, arrow, smart quotes) in JSON payload TEXT passed to curl get mangled and rejected — Discord bot-alert returns 400, the coord API returns "error parsing the body". Use ASCII-only in API payload text, or a single-quoted heredoc.
## Machine
- [GURU-5070 Workstation Setup](reference_workstation_setup.md) — Mike's primary (owner confirmed 2026-05-26). Windows 11 Pro. Renamed from OC-5070 → ACG-5070/acg-guru-5070 → GURU-5070; all the same box, all Mike's.
- [GURU-BEAST-ROG Setup Status](machine_windows_guru_setup_status.md) — Windows workstation fully configured except SSH key deployment to servers.
## Project
- [CyndyOffice physical HP lockups](cyndyoffice-physical-hp-lockups.md) — RMM "Howard-VM" site agent CyndyOffice is a PHYSICAL HP Pavilion TP01 (not a VM); ~20 hard freezes/6wk = Kernel-Power 41 bugcheck-0, no dump/WHEA = hardware (RAM/PSU/BIOS), SSD healthy. UUID re-enrolls.
- [Automate memory consolidation/lint (phased)](project_memory_consolidation_automation.md) — Eventually auto-run /memory-dream; lint+additive fixes can automate early, merges/deletes stay human-approved. Engine: .claude/skills/memory-dream/ + .claude/scripts/sync-memory.sh.
- [Trebesch PST consolidation (staged)](project_trebesch_pst_consolidation.md) — Address-book CSV from 24 PSTs on DESKTOP-QNP3ON5; scripts staged at .claude/tmp/treb-*.ps1, WAITING for Howard's 6pm-MST 2026-06-01 go signal (attended run). See [[reference_trebesch_qnp3on5]].
- [GuruRMM project state](project_gururmm.md) — Dev principles (every feature full-stack: backend+API+UI+docs+scalability; product works without AI; FEATURE_ROADMAP update is part of definition-of-done; mirrors guru-rmm/docs/DESIGN.md). Webhook docs-only build guard (SPEC-020 Phase 0; webhook-handler.py repo copy is STALE — don't redeploy). Mac install-hooks.sh setup STILL PENDING on Mikes-MacBook-Air.
- [GuruConnect](project_guruconnect.md) — v2 direction (native-first full key fidelity Win+R/Ctrl+Alt+Del + bidirectional file cut/paste/drag; WebRTC fallback only; standalone-first + RMM contract; tenancy-ready schema; Mike willing to scrap v1). Manual deploy procedure to 172.16.3.30 (build-on-server in login shell; sqlx runtime queries; NPM `CONNECT_TRUSTED_PROXIES=172.16.3.20` gotcha). v2 live since 2026-05-30.
- [Apple MDM + Developer certs (GuruRMM mobile)](project_apple_mdm_certs.md) — ACG holds Apple Developer+signing and Apple MDM Push certs (acquired 2026-05-29) for SPEC-017. MDM push cert RENEWS ANNUALLY on the same Apple ID or all enrolled iOS devices break.
- [Only RMM & GC are versionable products](project_versionable_products.md) — GuruRMM + GuruConnect are the only products with own repos/submodules; everything else stays in the claudetools monorepo. Split only for independent pipeline OR versioned external consumer.
- [Quantum GoDaddy M365 tenant](project_quantum_godaddy_m365_tenant.md) — quantumwms.com parked in a GoDaddy-provisioned M365 tenant (id ddf3d2c9-b76c-40d9-a216-9f11a1a26f97, netorg18235235.onmicrosoft.com); blocks Pax8 migration until GoDaddy removed.
- [Howard-Home LAN shadow (RESOLVED)](howard-home-lan-shadow.md) — Howard-Home renumbered 2026-06-16 to **10.137.42.0/24** (gw 10.137.42.1, UniFi — NOT pfSense), off the old 192.168.0.0/24 that shadowed Cascades pfSense .0.x over the VPN. Cascades .0.x should now route via the tunnel; this machine is 10.137.42.x now (not 192.168.0.x).
- [Cascades](project_cascades.md) — Active state: Syncro ticket #110680053 + plan file (machine-specific path on Howard's box), admin accounts (sysadmin@=Howard, admin@=Mike — daily-driver, NOT break-glass), Phase-B caregiver CA pilot (SG-Caregivers-Pilot, group-scoped never tenant-wide), prepaid block ~37.5h (rate TBD), pilot cleanup checklist.
- [Cascades history](project_cascades_history.md) — fdeploy 502/ACL root cause (Flags=1211→187 fix), 2026-04-29 CA-rescoping decision (Howard pulled the brakes on tenant-wide), 2026-05-14 per-user-security-group decision rationale.
- [Cascades isolated-VLAN pattern](project_cascades_isolated_vlan_pattern.md) — pfSense: the GUEST VLAN (VLAN50/igc1.50) is the isolation template (4 any-proto quick rules: block 192.168.0.0/22 + 10.0.0.0/8 + 172.16.0.0/12, then pass any; public DNS via DHCP). VLAN20 is NOT isolated. Verify with `pfctl -sr`, not config.xml. Protocol MUST be Any (TCP-only leaks UDP). VOICE VLAN30 built to this 2026-06-17.
- [Cascades KPI dashboard (parked)](project_cascades_kpi_dashboard.md) — Ashley Jensen wants one dashboard across their reporting SaaS (ALIS/QuickBooks/Bill.com/Relias/You've Got Leads/TELS/Focus HR/Helpany/POS). Power BI Gateway is the WRONG frame (on-prem only). Recommended Tier1→Tier2: scheduled exports → SharePoint → Power BI Pro, automate API-capable systems (Bill.com/QBO) via Power Automate later. Full notes: `clients/cascades-tucson/docs/proposals/kpi-dashboard.md`. Next: draft client one-pager.
- [Sync script bug — untracked files (RESOLVED)](project_sync_script_bug.md) — FIXED 2026-05-21: sync.sh now uses `git status --porcelain` for change detection (repo + vault).
- [MasterBooter Side Project](project_masterbooter.md) — Howard's Rust+Slint Windows deployment toolkit at C:\MasterBooter, separate from client work. Do not log to clients/.
- [Audio Processor Architecture](project_audio_processor_architecture.md) — Segment-first pipeline: detect breaks before transcription for complete content capture.
- [Neptune SBR Email Routing Setup](project_neptune_sbr_email_routing.md) — Full SBR routing chain, config file locations, MailProtector integration, access methods. Treat routing breakage as systemic (devcon, Sorensen/rieussetcorp), not per-client.
- [Dataforth Test Datasheet Pipeline](project_datasheet_pipeline.md) — Full pipeline rebuilt 2026-03-27. Server-side generation replaces DFWDS/Uploader. Website upload still broken.
- [Dataforth](project_dataforth.md) — M365 email (Graph API; tenant in vault at clients/dataforth/m365.sops.yaml); neptune.acghosting.com is ACG's, NOT Dataforth's. MFA enforced 2026-04-04 (3 CA policies). AJ needs dataforthgit@ forwarding.
- [Dataforth history (2026-03-27 incident)](project_dataforth_history.md) — DF-JOEL2 compromise via ScreenConnect social-engineering, attacker C2 IPs + IC3 case + remediation log + MFA rollout origin story + Joel Lohr retirement. RESOLVED 2026-04-04.
- [Radio show co-host — Tara, not Tom](radio_show_no_cohost_named_tom.md) — Co-host in 2014-s6e19 and 2016-s8e43 is Tara. "Tom" was hallucinated; rename complete.
- [Proposal: centralize config in identity.json](proposal_identity_centralization.md) — Rationale for the identity.json machine-config centralization (claudetools_root, ollama/python); now implemented.
- [ACG MSP tool stack](reference_acg_msp_stack.md) — ScreenConnect/CW Control, Splashtop, Syncro, Datto RMM, Datto EDR/AV, GuruRMM are ACG's OWN tools; do not flag as foreign/threat on managed machines (Defender-off is expected when Datto AV is active).
- [ACG Website Hosting](project_azcomputerguru_hosting.md) — azcomputerguru.com is hosted on IX Web Hosting via cPanel.
- [jq on Windows emits CRLF](feedback_jq_crlf_windows.md) — winget jq outputs CRLF; trailing \r silently breaks `for x in $(jq ...)` loops + read-from-@tsv. Override `jq(){ command jq "$@"|tr -d '\r'; }`. Windows-build-specific (passes on Mac/Linux).
- [ScreenConnect RESTful API auth](reference_screenconnect_api.md) — CTRLAuthHeader = raw api_secret (no Basic/b64) + Origin header; only method is GetSessionsByName; matches blank-for-agents Name field so it cannot enumerate full inventory.
- [No manufactured guardrails on our products](feedback_no_manufactured_guardrails.md) — At Mikes request on GuruRMM/GuruConnect/ClaudeTools, just execute; stop only for genuinely irreversible/destructive ops (with a heads-up). Read the actual code/state before claiming something is disallowed or a security hole.
- [Stream-of-thought design convos](feedback_stream_of_thought_design.md) — Mike brainstorms features free-form, adding requirements iteratively; Claude validates/sharpens as a design partner but does NOT build until an explicit go, then captures parked threads durably (PARKED_*.md + todos) for a later /shape-spec.
- [RMM Thoughts backlog](feedback_rmm_thoughts_backlog.md) — GuruRMM ideas from Mike & Howard go in projects/msp-tools/guru-rmm/docs/RMM_THOUGHTS.md (Status: Raw); pipeline thought -> discuss -> spec (/shape-spec) -> roadmap. Don't build until an explicit go.
- [Syncro preview mandatory](feedback_syncro_preview_mandatory.md) — preview+confirm every Syncro write, including internal notes
- [Refresh session history first](feedback_refresh_session_history_first.md) — read prior incident logs before acting; do not re-remediate already-handled accounts
- [Autonomy scope](feedback_autonomy_scope.md) — confirm only for client-affecting actions; internal docs/wiki/ClaudeTools = act autonomously
- [Check for client-slug fragmentation](feedback_client_slug_fragmentation.md) — Before concluding a client has no records, grep broadly (company/owner/initials/hostname/"Last, First") across clients/, wiki/, session-logs/, vault — one client gets split across slug variants (Wolkin was 4: wolkin/wolkin-law/rswolkin/robert-wolkin). Consolidate to one canonical slug; action prior logs' Pending items.
- [RMM user_session = false SMB failures](feedback_rmm_user_session_smb_false_negative.md) — GuruRMM net use/net view/Add-Printer to a remote \HOST fail with error 67 / RPC 1702 (even with valid creds) because user_session is a WTS-impersonated non-interactive token that can't do authenticated SMB. The share/printer may work fine interactively. Treat RMM SMB results as "can't tell"; verify via ScreenConnect.
- [Broken [[backlinks]] = write-me-later markers](feedback_broken_backlinks_are_writeme_markers.md) — A [[name]] with no matching file is an intentional "worth writing" marker, not breakage. Flesh the missing memory out from session history/logs and index it; never strip the link to silence the warning. memory-dream reports these as INFO candidates, not errors.
- [gururmm session-logs are in a submodule](gururmm-session-logs-submodule-save.md) — commit in the submodule + `git push origin HEAD:main` (GURU-5070 CAN push over HTTP now); then advance the parent gitlink
- [Use `python` not `python3` on GURU-5070](python3-shim-use-python.md) — `python3` in Git bash hits the flaky MS Store shim; real interpreters are `python` (3.12) / `py` (3.14). coord.py + wiki-compile work via `python`; the coord lock IS claimable here
- [Beast = primary GuruRMM Windows build host](gururmm-beast-windows-build-host.md) — GURU-BEAST-ROG (i9), reached from .30 via Tailscale-on-.30 at 100.101.122.4 as guru; Pluto is the fallback (`attempt_build beast || attempt_build pluto`). WiX must be 4.x (v6+ = OSMF); Beast NuGet needed nuget.org added
- [GuruRMM command_type gotcha](reference_gururmm_command_type.md) — only shell/powershell/python/script/claude_task (+cmd alias); unknown type silently dropped, looks like a black-hole
- [GuruRMM log analysis -> Claude Haiku](gururmm-log-analysis-claude-cutover.md) — cut over from Ollama-on-Beast (timed out on fleet-sized prompts; "unreachable" was a mislabeled 120s timeout) to Anthropic API Haiku 4.5 w/ structured outputs; key at vault `projects/gururmm/anthropic-api`; ZDR pending; deploy needs root on .30 (.env + restart)
- [IX WHM API access = 'ClaudeTools' token, not password](ix-whm-dns-api-access.md) — IX cPanel/WHM (ix.azcomputerguru.com:2087) DNS + all API work uses the FULL-ACCESS-root WHM API token at vault `infrastructure/ix-server` `credentials.whm-api-token` via header `Authorization: whm root:<token>` (force curl -4). Password basic-auth on legacy json-api now 403s. Public NS ns1/ns2.acghosting.com = 52.52.94.202.
- [Vault EVERY credential surfaced in-session](feedback-vault-every-credential.md) — any cred (pasted/created/discovered) -> store via the vault skill + document purpose & exact usage immediately; it's a standing job rule (reinforced in CORE CLAUDE.md). Lost IX creds wasted ~1h on 2026-06-12.
- [GuruRMM install-report v1: reuse endpoint + failed-install agent](gururmm-install-report-failed-agent-v1.md) — legacy NSIS installer reuses /api/install-report (machine info + logs, success+fail); server upserts a visible "failed-install" device on failure reports (Mike: in v1); verify-connect-before-success; trend/near-fail analytics. Server side is a separate sequential SPEC after the legacy-agent branch lands.
- [DM wrapping commands to Mike in Discord](feedback_dm_wrapping_commands_to_mike.md) — long/wrapping output (commands, consent links, URLs) goes via Discord DM (copies clean), not just chat; use the `discord-dm` skill (`discord-dm.sh mike "<x>"`). Gotchas: bot token vault projects/discord-bot/bot-token, Mike uid 264814939619721216, MUST set User-Agent or Cloudflare 403 errcode 1010, build JSON via jq + printf|--data-binary (direct -d → 50109).
- [Physical access codes -> vault + wiki pointer](feedback_physical_access_codes.md) — alarm/lockbox/door codes go in vault clients/<slug>/physical-access-<location>.sops.yaml (kind: physical-access) + a `## Physical Access` pointer section in the client wiki; never plaintext. First entry: Peaceful Spirit NW.
- [CT Thoughts backlog](feedback_ct_thoughts_backlog.md) — ClaudeTools harness ideas go in docs/CT_THOUGHTS.md (trigger "ct thought:"); CT analogue of RMM_THOUGHTS. Don't build until explicit go. First entry = ClaudeTools 3.0 web co-work vision.
- [AI-auth product boundary](project_ai_auth_product_boundary.md) — ClaudeTools/ClaudeTools 3.0 = internal-only, per-person subscription OAuth ok; GuruRMM = sellable, customer brings own API key (never ACG's subscription); backend dev = internal. Anthropic ToS bans subscription auth in third-party products.
- [RMM SYSTEM context can't see user mapped drives](feedback_rmm_system_context_mapped_drives.md) — RMM runs as SYSTEM; `Test-Path F:\` etc. is False even when the user's mapped/redirected drive exists. Diagnose mapped-drive/redirect issues in `context:user_session`. Elevated apps (e.g. QB DB Server Manager "unable to retrieve root folder") need `EnableLinkedConnections=1` + reboot.

View File

@@ -0,0 +1,182 @@
# Memory Dream Report
Generated: 2026-06-11 08:15
Repo root: D:\claudetools
Memory store: D:\claudetools\.claude\memory
Mode: REPORT-ONLY
Loaded 112 memory files (excluding MEMORY.md).
## 1. INDEX RECONCILE
### Orphan files (no index line): 3
- [INFO] feedback_ascii_only_api_payloads.md (type=feedback)
- [INFO] reference_backblaze_storage_rate.md (type=reference)
- [INFO] reference_sqlx_migrations_immutable.md (type=reference)
### Index lines pointing at missing files: 0
### Frontmatter name vs filename signals: 4
- [INFO] feedback_mac_rmm_auth_fixed.md: (no name in frontmatter)
- [INFO] feedback_rmm_password_limitation.md: (no name in frontmatter)
- [INFO] feedback_windows_bash_mapping.md: (no name in frontmatter)
- [INFO] policy_pricing_verification.md: (no name in frontmatter)
## 2. BACKLINKS ([[name]] references)
### Broken backlinks: 16
- [WARNING] cyndyoffice-physical-hp-lockups.md: [[universal-minerals]] has no matching memory file
- [WARNING] feedback_bot_alert_ticket_link.md: [[feedback_syncro_html]] has no matching memory file
- [WARNING] feedback_ca_programmatic_management.md: [[365-remediation-tool-reference]] has no matching memory file
- [WARNING] feedback_check_patterns_before_asking.md: [[user-font-preference]] has no matching memory file
- [WARNING] feedback_check_patterns_before_asking.md: [[feedback-check-patterns-before-asking]] has no matching memory file
- [WARNING] feedback_client_slug_fragmentation.md: [[wolkin]] has no matching memory file
- [WARNING] feedback_dashboard_beta_first.md: [[feedback_gururmm_builds]] has no matching memory file
- [WARNING] feedback_gururmm_build_channel_default.md: [[feedback_gururmm_builds]] has no matching memory file
- [WARNING] feedback_no_manufactured_guardrails.md: [[feedback-no-toml-config-endpoints]] has no matching memory file
- [WARNING] feedback_rmm_thoughts_backlog.md: [[feedback-stream-of-thought-design]] has no matching memory file
- [WARNING] feedback_rmm_user_session_smb_false_negative.md: [[wolkin]] has no matching memory file
- [WARNING] feedback_stream_of_thought_design.md: [[feedback-dashboard-beta-first]] has no matching memory file
- [WARNING] infra_office_network.md: [[power-failure-runbook]] has no matching memory file
- [WARNING] project_apple_mdm_certs.md: [[SPEC-017]] has no matching memory file
- [WARNING] project_memory_consolidation_automation.md: [[feedback_memory_repo_not_profile]] has no matching memory file
- [WARNING] reference_coord_messages_api_shape.md: [[CLAUDE.md]] has no matching memory file
## 3. REFERENCED-ARTIFACT VALIDITY (conservative; 'verify', not 'delete')
### Referenced paths not found in repo: 23
- [VERIFY] feedback_dashboard_beta_first.md: `opt/gururmm/build-dashboard.sh` not found under repo (may be server-side or renamed -- verify, do not auto-delete)
- [VERIFY] feedback_stream_of_thought_design.md: `projects/msp-tools/guru-rmm/docs/PARKED_alert-lifecycle-and-telemetry-cadence.md` not found under repo (may be server-side or renamed -- verify, do not auto-delete)
- [VERIFY] feedback_syncro_api.md: `tmp/syncro_comment.json` not found under repo (may be server-side or renamed -- verify, do not auto-delete)
- [VERIFY] feedback_syncro_history.md: `tmp/syncro_comment.json` not found under repo (may be server-side or renamed -- verify, do not auto-delete)
- [VERIFY] feedback_tmp_path_windows.md: `tmp/comment_payload.json` not found under repo (may be server-side or renamed -- verify, do not auto-delete)
- [VERIFY] feedback_tmp_path_windows.md: `tmp/foo.json` not found under repo (may be server-side or renamed -- verify, do not auto-delete)
- [VERIFY] machine_windows_guru_setup_status.md: `sops.yaml` not found under repo (may be server-side or renamed -- verify, do not auto-delete)
- [VERIFY] project_guruconnect.md: `etc/systemd/system/guruconnect.service` not found under repo (may be server-side or renamed -- verify, do not auto-delete)
- [VERIFY] project_gururmm.md: `gururmm-webhook.service` not found under repo (may be server-side or renamed -- verify, do not auto-delete)
- [VERIFY] project_gururmm.md: `opt/gururmm/webhook-handler.py` not found under repo (may be server-side or renamed -- verify, do not auto-delete)
- [VERIFY] project_masterbooter.md: `DECISIONS.md` not found under repo (may be server-side or renamed -- verify, do not auto-delete)
- [VERIFY] project_masterbooter.md: `EXPANSION_PLAN.md` not found under repo (may be server-side or renamed -- verify, do not auto-delete)
- [VERIFY] project_masterbooter.md: `TODO_CLEANUP.md` not found under repo (may be server-side or renamed -- verify, do not auto-delete)
- [VERIFY] project_masterbooter.md: `VISION.md` not found under repo (may be server-side or renamed -- verify, do not auto-delete)
- [VERIFY] project_neptune_sbr_email_routing.md: `data/on_boot.d/10-neptune-snat.sh` not found under repo (may be server-side or renamed -- verify, do not auto-delete)
- [VERIFY] reference_ff_firefox_driver.md: `~/.claude.json` not found under repo (may be server-side or renamed -- verify, do not auto-delete)
- [VERIFY] reference_gururmm.md: `build-{windows,linux,mac,agents,server,shared}.sh` not found under repo (may be server-side or renamed -- verify, do not auto-delete)
- [VERIFY] reference_gururmm.md: `gururmm-agent.service` not found under repo (may be server-side or renamed -- verify, do not auto-delete)
- [VERIFY] reference_ix_server_access.md: `etc/gururmm/agent.toml` not found under repo (may be server-side or renamed -- verify, do not auto-delete)
- [VERIFY] reference_ix_server_access.md: `gururmm-agent.service` not found under repo (may be server-side or renamed -- verify, do not auto-delete)
- [VERIFY] reference_radio_website.md: `fuse.js` not found under repo (may be server-side or renamed -- verify, do not auto-delete)
- [VERIFY] reference_radio_website.md: `wavesurfer.js` not found under repo (may be server-side or renamed -- verify, do not auto-delete)
- [VERIFY] reference_ticktick_integration.md: `mcp.json` not found under repo (may be server-side or renamed -- verify, do not auto-delete)
## 4. DUPLICATE / OVERLAP CLUSTERS (PROPOSED merges -- never auto-applied)
### Candidate clusters: 11
- [feedback] 5 related memories:
- feedback_syncro_api.md -- Technical mechanics for talking to the Syncro API — required Content-Type header, the no-i
- feedback_syncro_billing.md -- How to bill a Syncro ticket correctly — fetch live rates, use real product names, pick the
- feedback_syncro_history.md -- Detail and incident archive backing the Syncro feedback rules. Read this when you need to
- feedback_syncro_preview_mandatory.md -- Every Syncro write needs a payload preview + explicit confirmation BEFORE posting — includ
- feedback_syncro_workflow.md -- Process and etiquette rules for Syncro work — always preview comments before posting, veri
- [reference] 3 related memories:
- reference_gitea_api_credential.md -- Gitea API auth (PRs, merges) uses services/gitea-howard.sops.yaml, NOT the gururmm server
- reference_gitea_git_op_latency.md -- Gitea git-op latency benchmarks - SSH is SLOWER than internal HTTP+token; the SOPS credent
- reference_gitea_internal.md -- git.azcomputerguru.com is NOT behind Cloudflare — it's the office Cox IP NAT'd to NPM (ope
- [feedback] 2 related memories:
- feedback_cascades.md -- Active rules for Cascades work — (1) folder redirection (fdeploy) needs subfolders pre-cre
- feedback_cascades_scan_account.md -- At Cascades, every scanner→network-folder (scan-to-SMB) setup reuses the single svc-scan A
- [feedback] 2 related memories:
- feedback_client_slug_fragmentation.md -- A single client can be recorded under several slug variants (e.g. wolkin / wolkin-law / rs
- feedback_client_tone.md -- How to write client-facing Syncro comments — expert partner, not intake questionnaire
- [feedback] 2 related memories:
- feedback_graph_ca_policy_eventual_consistency.md -- After PATCHing a CA policy (204 No Content), an immediate GET may return stale state. Wait
- feedback_graph_password_reset_requires_role.md -- With User.ReadWrite.All app perm + no privileged directory role, Tenant Admin can CREATE a
- [feedback] 2 related memories:
- feedback_gururmm.md -- Six rules for working with GuruRMM. (1) RMM dev is Mike's domain — Howard does NOT code RM
- feedback_gururmm_build_channel_default.md -- GuruRMM build pipeline must tag NEW builds beta by default; stable is an explicit promote
- [feedback] 2 related memories:
- feedback_no_manufactured_guardrails.md -- On OUR products (GuruRMM/GuruConnect/ClaudeTools etc.) at Mike's request, execute without
- feedback_no_toml_config_endpoints.md -- User explicitly prohibits TOML or config-file-based endpoint configuration — this will nev
- [feedback] 2 related memories:
- feedback_rmm_thoughts_backlog.md -- GuruRMM ideas go into the "RMM Thoughts" backlog (docs/RMM_THOUGHTS.md); pipeline thought
- feedback_rmm_user_session_smb_false_negative.md -- GuruRMM commands (even context user_session) run under a WTS-impersonated, non-interactive
- [feedback] 2 related memories:
- feedback_vault_gcm_shadow_auth.md -- Vault git push/fetch "Failed to authenticate user" cause+fix — GCM shadows the store token
- feedback_vault_pointer_for_teammates.md -- When relaying infra/credential info to Howard (or any teammate with vault access), hand ov
- [project] 2 related memories:
- project_cascades.md -- Active state of the Cascades migration — Syncro ticket #110680053, plan file (machine-spec
- project_cascades_history.md -- Detail and rationale behind the active Cascades rules — fdeploy 502/ACL root cause and the
- [project] 2 related memories:
- project_dataforth.md -- Dataforth runs on M365 (Graph API for mail send); the neptune.acghosting.com Exchange is A
- project_dataforth_history.md -- Detail and remediation log for the 2026-03-27 Dataforth security incident — DF-JOEL2 compr
## 5. STALE DATED FACTS (project-type, dated > 6 months)
### Project memories with stale dated claims: 1
- [VERIFY] radio_show_no_cohost_named_tom.md: dated 2012-06-09 (~5115 days old) -- re-verify
## 6. DRIFT vs HARNESS PROFILE STORE
Profile store: C:\Users\guru\.claude\projects\D--claudetools\memory
### Profile-only (candidates to MIGRATE INTO repo): 0
### Repo-only (candidates to PUSH OUT to profile): 2
- [INFO] feedback_client_slug_fragmentation.md
- [INFO] feedback_rmm_user_session_smb_false_negative.md
### Present in BOTH but differing (CONFLICT -- human review): 1
- [WARNING] gururmm-physical-server-storage.md: content differs between repo and profile
## SUMMARY
- memory files: 112
- orphan files (no index): 3
- index -> missing file: 0
- name/filename signals: 4
- broken backlinks: 16
- stale referenced paths: 23
- overlap clusters: 11
- stale dated project facts: 1
- profile-only files: 0
- repo-only files: 2
- repo<->profile conflicts: 1
## PROPOSED (needs human approval -- NEVER auto-applied)
- [MERGE?] consolidate 5 'feedback' memories: feedback_syncro_api.md, feedback_syncro_billing.md, feedback_syncro_history.md, feedback_syncro_preview_mandatory.md, feedback_syncro_workflow.md
- [MERGE?] consolidate 3 'reference' memories: reference_gitea_api_credential.md, reference_gitea_git_op_latency.md, reference_gitea_internal.md
- [MERGE?] consolidate 2 'feedback' memories: feedback_cascades.md, feedback_cascades_scan_account.md
- [MERGE?] consolidate 2 'feedback' memories: feedback_client_slug_fragmentation.md, feedback_client_tone.md
- [MERGE?] consolidate 2 'feedback' memories: feedback_graph_ca_policy_eventual_consistency.md, feedback_graph_password_reset_requires_role.md
- [MERGE?] consolidate 2 'feedback' memories: feedback_gururmm.md, feedback_gururmm_build_channel_default.md
- [MERGE?] consolidate 2 'feedback' memories: feedback_no_manufactured_guardrails.md, feedback_no_toml_config_endpoints.md
- [MERGE?] consolidate 2 'feedback' memories: feedback_rmm_thoughts_backlog.md, feedback_rmm_user_session_smb_false_negative.md
- [MERGE?] consolidate 2 'feedback' memories: feedback_vault_gcm_shadow_auth.md, feedback_vault_pointer_for_teammates.md
- [MERGE?] consolidate 2 'project' memories: project_cascades.md, project_cascades_history.md
- [MERGE?] consolidate 2 'project' memories: project_dataforth.md, project_dataforth_history.md
- [REVERIFY?] radio_show_no_cohost_named_tom.md (dated facts) -- confirm still true, then update
- [STALE-REF?] feedback_dashboard_beta_first.md references `opt/gururmm/build-dashboard.sh` -- confirm/repoint or note moved
- [STALE-REF?] feedback_stream_of_thought_design.md references `projects/msp-tools/guru-rmm/docs/PARKED_alert-lifecycle-and-telemetry-cadence.md` -- confirm/repoint or note moved
- [STALE-REF?] feedback_syncro_api.md references `tmp/syncro_comment.json` -- confirm/repoint or note moved
- [STALE-REF?] feedback_syncro_history.md references `tmp/syncro_comment.json` -- confirm/repoint or note moved
- [STALE-REF?] feedback_tmp_path_windows.md references `tmp/comment_payload.json` -- confirm/repoint or note moved
- [STALE-REF?] feedback_tmp_path_windows.md references `tmp/foo.json` -- confirm/repoint or note moved
- [STALE-REF?] machine_windows_guru_setup_status.md references `sops.yaml` -- confirm/repoint or note moved
- [STALE-REF?] project_guruconnect.md references `etc/systemd/system/guruconnect.service` -- confirm/repoint or note moved
- [STALE-REF?] project_gururmm.md references `gururmm-webhook.service` -- confirm/repoint or note moved
- [STALE-REF?] project_gururmm.md references `opt/gururmm/webhook-handler.py` -- confirm/repoint or note moved
- [STALE-REF?] project_masterbooter.md references `DECISIONS.md` -- confirm/repoint or note moved
- [STALE-REF?] project_masterbooter.md references `EXPANSION_PLAN.md` -- confirm/repoint or note moved
- [STALE-REF?] project_masterbooter.md references `TODO_CLEANUP.md` -- confirm/repoint or note moved
- [STALE-REF?] project_masterbooter.md references `VISION.md` -- confirm/repoint or note moved
- [STALE-REF?] project_neptune_sbr_email_routing.md references `data/on_boot.d/10-neptune-snat.sh` -- confirm/repoint or note moved
- [STALE-REF?] reference_ff_firefox_driver.md references `~/.claude.json` -- confirm/repoint or note moved
- [STALE-REF?] reference_gururmm.md references `build-{windows,linux,mac,agents,server,shared}.sh` -- confirm/repoint or note moved
- [STALE-REF?] reference_gururmm.md references `gururmm-agent.service` -- confirm/repoint or note moved
- [STALE-REF?] reference_ix_server_access.md references `etc/gururmm/agent.toml` -- confirm/repoint or note moved
- [STALE-REF?] reference_ix_server_access.md references `gururmm-agent.service` -- confirm/repoint or note moved
- [STALE-REF?] reference_radio_website.md references `fuse.js` -- confirm/repoint or note moved
- [STALE-REF?] reference_radio_website.md references `wavesurfer.js` -- confirm/repoint or note moved
- [STALE-REF?] reference_ticktick_integration.md references `mcp.json` -- confirm/repoint or note moved
- [DRIFT-RESOLVE?] gururmm-physical-server-storage.md differs repo vs profile -- human picks winner (sync-memory.sh leaves both untouched)

View File

@@ -0,0 +1,189 @@
# Memory Dream Report
Generated: 2026-06-11 08:20
Repo root: D:\claudetools
Memory store: D:\claudetools\.claude\memory
Mode: APPLY-SAFE (additive)
Loaded 112 memory files (excluding MEMORY.md).
## 1. INDEX RECONCILE
### Orphan files (no index line): 3
- [INFO] feedback_ascii_only_api_payloads.md (type=feedback)
- [INFO] reference_backblaze_storage_rate.md (type=reference)
- [INFO] reference_sqlx_migrations_immutable.md (type=reference)
### Index lines pointing at missing files: 0
### Frontmatter name vs filename signals: 4
- [INFO] feedback_mac_rmm_auth_fixed.md: (no name in frontmatter)
- [INFO] feedback_rmm_password_limitation.md: (no name in frontmatter)
- [INFO] feedback_windows_bash_mapping.md: (no name in frontmatter)
- [INFO] policy_pricing_verification.md: (no name in frontmatter)
## 2. BACKLINKS ([[name]] references)
### Broken backlinks: 16
- [WARNING] cyndyoffice-physical-hp-lockups.md: [[universal-minerals]] has no matching memory file
- [WARNING] feedback_bot_alert_ticket_link.md: [[feedback_syncro_html]] has no matching memory file
- [WARNING] feedback_ca_programmatic_management.md: [[365-remediation-tool-reference]] has no matching memory file
- [WARNING] feedback_check_patterns_before_asking.md: [[user-font-preference]] has no matching memory file
- [WARNING] feedback_check_patterns_before_asking.md: [[feedback-check-patterns-before-asking]] has no matching memory file
- [WARNING] feedback_client_slug_fragmentation.md: [[wolkin]] has no matching memory file
- [WARNING] feedback_dashboard_beta_first.md: [[feedback_gururmm_builds]] has no matching memory file
- [WARNING] feedback_gururmm_build_channel_default.md: [[feedback_gururmm_builds]] has no matching memory file
- [WARNING] feedback_no_manufactured_guardrails.md: [[feedback-no-toml-config-endpoints]] has no matching memory file
- [WARNING] feedback_rmm_thoughts_backlog.md: [[feedback-stream-of-thought-design]] has no matching memory file
- [WARNING] feedback_rmm_user_session_smb_false_negative.md: [[wolkin]] has no matching memory file
- [WARNING] feedback_stream_of_thought_design.md: [[feedback-dashboard-beta-first]] has no matching memory file
- [WARNING] infra_office_network.md: [[power-failure-runbook]] has no matching memory file
- [WARNING] project_apple_mdm_certs.md: [[SPEC-017]] has no matching memory file
- [WARNING] project_memory_consolidation_automation.md: [[feedback_memory_repo_not_profile]] has no matching memory file
- [WARNING] reference_coord_messages_api_shape.md: [[CLAUDE.md]] has no matching memory file
## 3. REFERENCED-ARTIFACT VALIDITY (conservative; 'verify', not 'delete')
### Referenced paths not found in repo: 23
- [VERIFY] feedback_dashboard_beta_first.md: `opt/gururmm/build-dashboard.sh` not found under repo (may be server-side or renamed -- verify, do not auto-delete)
- [VERIFY] feedback_stream_of_thought_design.md: `projects/msp-tools/guru-rmm/docs/PARKED_alert-lifecycle-and-telemetry-cadence.md` not found under repo (may be server-side or renamed -- verify, do not auto-delete)
- [VERIFY] feedback_syncro_api.md: `tmp/syncro_comment.json` not found under repo (may be server-side or renamed -- verify, do not auto-delete)
- [VERIFY] feedback_syncro_history.md: `tmp/syncro_comment.json` not found under repo (may be server-side or renamed -- verify, do not auto-delete)
- [VERIFY] feedback_tmp_path_windows.md: `tmp/comment_payload.json` not found under repo (may be server-side or renamed -- verify, do not auto-delete)
- [VERIFY] feedback_tmp_path_windows.md: `tmp/foo.json` not found under repo (may be server-side or renamed -- verify, do not auto-delete)
- [VERIFY] machine_windows_guru_setup_status.md: `sops.yaml` not found under repo (may be server-side or renamed -- verify, do not auto-delete)
- [VERIFY] project_guruconnect.md: `etc/systemd/system/guruconnect.service` not found under repo (may be server-side or renamed -- verify, do not auto-delete)
- [VERIFY] project_gururmm.md: `gururmm-webhook.service` not found under repo (may be server-side or renamed -- verify, do not auto-delete)
- [VERIFY] project_gururmm.md: `opt/gururmm/webhook-handler.py` not found under repo (may be server-side or renamed -- verify, do not auto-delete)
- [VERIFY] project_masterbooter.md: `DECISIONS.md` not found under repo (may be server-side or renamed -- verify, do not auto-delete)
- [VERIFY] project_masterbooter.md: `EXPANSION_PLAN.md` not found under repo (may be server-side or renamed -- verify, do not auto-delete)
- [VERIFY] project_masterbooter.md: `TODO_CLEANUP.md` not found under repo (may be server-side or renamed -- verify, do not auto-delete)
- [VERIFY] project_masterbooter.md: `VISION.md` not found under repo (may be server-side or renamed -- verify, do not auto-delete)
- [VERIFY] project_neptune_sbr_email_routing.md: `data/on_boot.d/10-neptune-snat.sh` not found under repo (may be server-side or renamed -- verify, do not auto-delete)
- [VERIFY] reference_ff_firefox_driver.md: `~/.claude.json` not found under repo (may be server-side or renamed -- verify, do not auto-delete)
- [VERIFY] reference_gururmm.md: `build-{windows,linux,mac,agents,server,shared}.sh` not found under repo (may be server-side or renamed -- verify, do not auto-delete)
- [VERIFY] reference_gururmm.md: `gururmm-agent.service` not found under repo (may be server-side or renamed -- verify, do not auto-delete)
- [VERIFY] reference_ix_server_access.md: `etc/gururmm/agent.toml` not found under repo (may be server-side or renamed -- verify, do not auto-delete)
- [VERIFY] reference_ix_server_access.md: `gururmm-agent.service` not found under repo (may be server-side or renamed -- verify, do not auto-delete)
- [VERIFY] reference_radio_website.md: `fuse.js` not found under repo (may be server-side or renamed -- verify, do not auto-delete)
- [VERIFY] reference_radio_website.md: `wavesurfer.js` not found under repo (may be server-side or renamed -- verify, do not auto-delete)
- [VERIFY] reference_ticktick_integration.md: `mcp.json` not found under repo (may be server-side or renamed -- verify, do not auto-delete)
## 4. DUPLICATE / OVERLAP CLUSTERS (PROPOSED merges -- never auto-applied)
### Candidate clusters: 11
- [feedback] 5 related memories:
- feedback_syncro_api.md -- Technical mechanics for talking to the Syncro API — required Content-Type header, the no-i
- feedback_syncro_billing.md -- How to bill a Syncro ticket correctly — fetch live rates, use real product names, pick the
- feedback_syncro_history.md -- Detail and incident archive backing the Syncro feedback rules. Read this when you need to
- feedback_syncro_preview_mandatory.md -- Every Syncro write needs a payload preview + explicit confirmation BEFORE posting — includ
- feedback_syncro_workflow.md -- Process and etiquette rules for Syncro work — always preview comments before posting, veri
- [reference] 3 related memories:
- reference_gitea_api_credential.md -- Gitea API auth (PRs, merges) uses services/gitea-howard.sops.yaml, NOT the gururmm server
- reference_gitea_git_op_latency.md -- Gitea git-op latency benchmarks - SSH is SLOWER than internal HTTP+token; the SOPS credent
- reference_gitea_internal.md -- git.azcomputerguru.com is NOT behind Cloudflare — it's the office Cox IP NAT'd to NPM (ope
- [feedback] 2 related memories:
- feedback_cascades.md -- Active rules for Cascades work — (1) folder redirection (fdeploy) needs subfolders pre-cre
- feedback_cascades_scan_account.md -- At Cascades, every scanner→network-folder (scan-to-SMB) setup reuses the single svc-scan A
- [feedback] 2 related memories:
- feedback_client_slug_fragmentation.md -- A single client can be recorded under several slug variants (e.g. wolkin / wolkin-law / rs
- feedback_client_tone.md -- How to write client-facing Syncro comments — expert partner, not intake questionnaire
- [feedback] 2 related memories:
- feedback_graph_ca_policy_eventual_consistency.md -- After PATCHing a CA policy (204 No Content), an immediate GET may return stale state. Wait
- feedback_graph_password_reset_requires_role.md -- With User.ReadWrite.All app perm + no privileged directory role, Tenant Admin can CREATE a
- [feedback] 2 related memories:
- feedback_gururmm.md -- Six rules for working with GuruRMM. (1) RMM dev is Mike's domain — Howard does NOT code RM
- feedback_gururmm_build_channel_default.md -- GuruRMM build pipeline must tag NEW builds beta by default; stable is an explicit promote
- [feedback] 2 related memories:
- feedback_no_manufactured_guardrails.md -- On OUR products (GuruRMM/GuruConnect/ClaudeTools etc.) at Mike's request, execute without
- feedback_no_toml_config_endpoints.md -- User explicitly prohibits TOML or config-file-based endpoint configuration — this will nev
- [feedback] 2 related memories:
- feedback_rmm_thoughts_backlog.md -- GuruRMM ideas go into the "RMM Thoughts" backlog (docs/RMM_THOUGHTS.md); pipeline thought
- feedback_rmm_user_session_smb_false_negative.md -- GuruRMM commands (even context user_session) run under a WTS-impersonated, non-interactive
- [feedback] 2 related memories:
- feedback_vault_gcm_shadow_auth.md -- Vault git push/fetch "Failed to authenticate user" cause+fix — GCM shadows the store token
- feedback_vault_pointer_for_teammates.md -- When relaying infra/credential info to Howard (or any teammate with vault access), hand ov
- [project] 2 related memories:
- project_cascades.md -- Active state of the Cascades migration — Syncro ticket #110680053, plan file (machine-spec
- project_cascades_history.md -- Detail and rationale behind the active Cascades rules — fdeploy 502/ACL root cause and the
- [project] 2 related memories:
- project_dataforth.md -- Dataforth runs on M365 (Graph API for mail send); the neptune.acghosting.com Exchange is A
- project_dataforth_history.md -- Detail and remediation log for the 2026-03-27 Dataforth security incident — DF-JOEL2 compr
## 5. STALE DATED FACTS (project-type, dated > 6 months)
### Project memories with stale dated claims: 1
- [VERIFY] radio_show_no_cohost_named_tom.md: dated 2012-06-09 (~5115 days old) -- re-verify
## 6. DRIFT vs HARNESS PROFILE STORE
Profile store: C:\Users\guru\.claude\projects\D--claudetools\memory
### Profile-only (candidates to MIGRATE INTO repo): 0
### Repo-only (candidates to PUSH OUT to profile): 2
- [INFO] feedback_client_slug_fragmentation.md
- [INFO] feedback_rmm_user_session_smb_false_negative.md
### Present in BOTH but differing (CONFLICT -- human review): 1
- [WARNING] gururmm-physical-server-storage.md: content differs between repo and profile
## APPLY-SAFE ACTIONS PERFORMED (additive-only)
- [OK] appended index line under ## Feedback: - [feedback_ascii_only_api_payloads](feedback_ascii_only_api_payloads.md) -- On Windows/Git-bash, non-ASCII chars (em-dash, arrow, smart quotes) in JSON payload TEXT passed to curl get mangled and rejected — Discord bot-alert returns 400, the coord API returns "error parsing the body". Use ASCII-only in API payload text, or a single-quoted heredoc.
- [OK] appended index line under ## Reference: - [reference_backblaze_storage_rate](reference_backblaze_storage_rate.md) -- ACG's Backblaze B2 storage cost rate ($0.00695/GB) for the GuruRMM mspbackups storage-cost calculation
- [OK] appended index line under ## Reference: - [reference_sqlx_migrations_immutable](reference_sqlx_migrations_immutable.md) -- NEVER edit an already-applied sqlx migration file — even a comment. sqlx::migrate! checksums each file at compile time and validates against _sqlx_migrations at startup; a changed checksum crash-loops the server with "migration N was previously applied but has been modified". Code review MUST flag any edit to an applied migration.
## SUMMARY
- memory files: 112
- orphan files (no index): 3
- index -> missing file: 0
- name/filename signals: 4
- broken backlinks: 16
- stale referenced paths: 23
- overlap clusters: 11
- stale dated project facts: 1
- profile-only files: 0
- repo-only files: 2
- repo<->profile conflicts: 1
- additive actions performed: 3
## PROPOSED (needs human approval -- NEVER auto-applied)
- [MERGE?] consolidate 5 'feedback' memories: feedback_syncro_api.md, feedback_syncro_billing.md, feedback_syncro_history.md, feedback_syncro_preview_mandatory.md, feedback_syncro_workflow.md
- [MERGE?] consolidate 3 'reference' memories: reference_gitea_api_credential.md, reference_gitea_git_op_latency.md, reference_gitea_internal.md
- [MERGE?] consolidate 2 'feedback' memories: feedback_cascades.md, feedback_cascades_scan_account.md
- [MERGE?] consolidate 2 'feedback' memories: feedback_client_slug_fragmentation.md, feedback_client_tone.md
- [MERGE?] consolidate 2 'feedback' memories: feedback_graph_ca_policy_eventual_consistency.md, feedback_graph_password_reset_requires_role.md
- [MERGE?] consolidate 2 'feedback' memories: feedback_gururmm.md, feedback_gururmm_build_channel_default.md
- [MERGE?] consolidate 2 'feedback' memories: feedback_no_manufactured_guardrails.md, feedback_no_toml_config_endpoints.md
- [MERGE?] consolidate 2 'feedback' memories: feedback_rmm_thoughts_backlog.md, feedback_rmm_user_session_smb_false_negative.md
- [MERGE?] consolidate 2 'feedback' memories: feedback_vault_gcm_shadow_auth.md, feedback_vault_pointer_for_teammates.md
- [MERGE?] consolidate 2 'project' memories: project_cascades.md, project_cascades_history.md
- [MERGE?] consolidate 2 'project' memories: project_dataforth.md, project_dataforth_history.md
- [REVERIFY?] radio_show_no_cohost_named_tom.md (dated facts) -- confirm still true, then update
- [STALE-REF?] feedback_dashboard_beta_first.md references `opt/gururmm/build-dashboard.sh` -- confirm/repoint or note moved
- [STALE-REF?] feedback_stream_of_thought_design.md references `projects/msp-tools/guru-rmm/docs/PARKED_alert-lifecycle-and-telemetry-cadence.md` -- confirm/repoint or note moved
- [STALE-REF?] feedback_syncro_api.md references `tmp/syncro_comment.json` -- confirm/repoint or note moved
- [STALE-REF?] feedback_syncro_history.md references `tmp/syncro_comment.json` -- confirm/repoint or note moved
- [STALE-REF?] feedback_tmp_path_windows.md references `tmp/comment_payload.json` -- confirm/repoint or note moved
- [STALE-REF?] feedback_tmp_path_windows.md references `tmp/foo.json` -- confirm/repoint or note moved
- [STALE-REF?] machine_windows_guru_setup_status.md references `sops.yaml` -- confirm/repoint or note moved
- [STALE-REF?] project_guruconnect.md references `etc/systemd/system/guruconnect.service` -- confirm/repoint or note moved
- [STALE-REF?] project_gururmm.md references `gururmm-webhook.service` -- confirm/repoint or note moved
- [STALE-REF?] project_gururmm.md references `opt/gururmm/webhook-handler.py` -- confirm/repoint or note moved
- [STALE-REF?] project_masterbooter.md references `DECISIONS.md` -- confirm/repoint or note moved
- [STALE-REF?] project_masterbooter.md references `EXPANSION_PLAN.md` -- confirm/repoint or note moved
- [STALE-REF?] project_masterbooter.md references `TODO_CLEANUP.md` -- confirm/repoint or note moved
- [STALE-REF?] project_masterbooter.md references `VISION.md` -- confirm/repoint or note moved
- [STALE-REF?] project_neptune_sbr_email_routing.md references `data/on_boot.d/10-neptune-snat.sh` -- confirm/repoint or note moved
- [STALE-REF?] reference_ff_firefox_driver.md references `~/.claude.json` -- confirm/repoint or note moved
- [STALE-REF?] reference_gururmm.md references `build-{windows,linux,mac,agents,server,shared}.sh` -- confirm/repoint or note moved
- [STALE-REF?] reference_gururmm.md references `gururmm-agent.service` -- confirm/repoint or note moved
- [STALE-REF?] reference_ix_server_access.md references `etc/gururmm/agent.toml` -- confirm/repoint or note moved
- [STALE-REF?] reference_ix_server_access.md references `gururmm-agent.service` -- confirm/repoint or note moved
- [STALE-REF?] reference_radio_website.md references `fuse.js` -- confirm/repoint or note moved
- [STALE-REF?] reference_radio_website.md references `wavesurfer.js` -- confirm/repoint or note moved
- [STALE-REF?] reference_ticktick_integration.md references `mcp.json` -- confirm/repoint or note moved
- [DRIFT-RESOLVE?] gururmm-physical-server-storage.md differs repo vs profile -- human picks winner (sync-memory.sh leaves both untouched)

View File

@@ -0,0 +1,53 @@
---
name: cyndyoffice-physical-hp-lockups
description: CyndyOffice (RMM site "Howard-VM") is a PHYSICAL HP Pavilion, not a VM, with recurring hard-freeze lockups
metadata:
type: project
---
RMM agent **CyndyOffice** (client "AZ Computer Guru", site "Howard-VM") is a
**physical HP Pavilion Desktop TP01-2xxx** (AMD, 16 logical CPUs, 16 GB single
Kingston DIMM, 1 TB WD SN530 NVMe, BIOS AMI F.36, Win 11 Home build 26200) —
**NOT a VM**, despite the misleading "Howard-VM" site name.
Diagnosed 2026-06-10: ~20 hard lockups in 6 weeks, each = Kernel-Power 41 with
**BugcheckCode 0 + no minidump + no WHEA**, matching 6008 dirty shutdowns, log
goes silent right before each freeze. Crash dumps ARE enabled, so the absence of
dumps is real signal = **true hardware/firmware hang, not a BSOD**. SSD healthy.
Ticket: Syncro #32397 (Universal Minerals International Inc, customer_id
34844920) - "Onsite - Computer intermittently freezing and shutting down."
S/N 2MO21549RB, SKU 318G6AA#ABA.
2026-06-10 actions: BIOS updated F.36 -> F.38 (Howard, via HP Support Assistant);
Fast Startup DISABLED; Windows Memory Diagnostic Standard run = PASSED (no
errors). RAM mostly cleared (Standard test is light; MemTest86 USB extended not
yet run). Prime suspect now PSU (stock HP) if freezes recur; current plan =
monitoring window (freezes were every 1-3 days, watch ~1wk for new Kernel-Power
41). QuickBooksMessaging.exe crash-loops (~15/min, .NET ObjectDisposedException
on tray icon) - separate from freeze; QB Enterprise 22.0 is past Intuit support.
QB Tool Hub repair done 2026-06-10 - no new crashes after repair+reboot (confirm
once company file in active use). Orphaned mbamchameleon (Malwarebytes leftover)
driver service deleted (cleared boot Event 7000). SBAT/Secure Boot 1796 boot
error = benign MS noise, left alone. Agent re-enrolls with new UUID on reboot
(resolve live every time).
CONTINGENCY (documented on ticket, public): if freezing recurs after BIOS/Fast
Startup fixes, next step = full hardware diagnostic (extended mem + drive/PSU)
plus backup + clean Windows reinstall; ~1-2 days machine downtime. PSU is the
prime remaining hardware suspect.
BILLED 2026-06-10: 1.0h onsite, $175, invoice #67810 (client emailed summary +
contingency). Universal Minerals is BREAK-FIX - no prepaid block, NOT an RMM/
monitoring client (prepay_hours 0.0). The GuruRMM agent was installed ONLY to
diagnose and was REMOVED same-day 2026-06-10 (agent's own `uninstall` via a
detached one-time scheduled task + sc delete of GuruRMMAgent/GuruRMMWatchdog +
deleted C:\Program Files\GuruRMM and C:\ProgramData\GuruRMM; server-side record
DELETE /api/agents/<id> -> 204). So freeze monitoring is now manual/customer-
reported, not via RMM. Client wiki seeded at wiki/clients/universal-minerals.md
([[universal-minerals]] slug). To remove a GuruRMM Windows agent generally: it
has built-in verbs (install/uninstall/start/stop/status) - run `uninstall`
DETACHED (scheduled task) so it survives killing its own service.
**Why:** future "look at CyndyOffice" requests will assume VM tuning; it's a
physical box needing a memtest/PSU/BIOS path.
**How to apply:** treat as physical hardware; resolve UUID live every time.

View File

@@ -0,0 +1,29 @@
---
name: feedback-vault-every-credential
description: ANY credential surfaced in a session must be vaulted via the vault skill AND thoroughly documented — immediately
metadata:
type: feedback
---
When ANY credential appears in a session — the user pastes one, you create/rotate one, or you
discover one in a log/config — **immediately store it in the SOPS vault via the `vault` skill
and document it thoroughly** (what it is, what it's for, how it's used: auth method, endpoint,
gotchas). This is a standing job requirement, not a per-task ask — it is literally why the vault
exists.
**Why:** Mike (2026-06-12) was "highly irritated" after ~an hour was wasted because the IX WHM
access method had been lost/forgotten and I fell back to a password method that no longer works.
The original rule ("recognize any credential in-session, vault it, document what it's for and how
it's used") had drifted out of the always-loaded instructions.
**How to apply:**
- Use the **`vault` skill** (`vault-helper.sh new`/`set`, `vault.sh get`/`get-field`) — the
canonical path. Do NOT improvise raw `sops`/`vault.sh` with hand-built paths. (Exception: the
helper only writes under `credentials:`; a top-level metadata `notes` edit still needs `sops
--set` — but the secret itself always goes through the skill.)
- Document in the entry's `notes`: purpose + exact usage (e.g. header vs basic-auth, endpoint,
"force curl -4", what does NOT work and why). Future me reads this instead of re-deriving.
- Finish the job: store -> `verify` encrypted -> publish (sync/commit). Never paste the secret
into chat/commit/coord.
- Now reinforced in CORE `.claude/CLAUDE.md` "Key rules". See [[ix-whm-dns-api-access]] for the
concrete case that triggered this.

View File

@@ -24,9 +24,18 @@ Graph API permissions alone are NOT sufficient for privileged operations. The se
**Roles assigned so far:**
- Valleywide Plastering (5c53ae9f...): User Administrator
- Dataforth (7dfa3ce8...): User Administrator, Exchange Administrator
- azcomputerguru.com (ce61461e...): full set assigned 2026-06-05 — Sec-Inv + Exch-Op = Exchange Administrator; Tenant Admin = Conditional Access Administrator; User Manager = User Administrator + Authentication Administrator.
**For new tenants:** After admin consent, manually assign roles via Entra portal > Roles and administrators. The app cannot self-assign directory roles.
**For new tenants:** `onboard-tenant.sh <domain>` assigns the directory roles programmatically (Tenant Admin tier) — no manual portal step needed. The app cannot self-assign; the Tenant Admin SP does it.
**GOTCHA — pre-2026-04-20 tenants have NO directory roles.** The directory-role assignment block was added to `onboard-tenant.sh` in commit cd50117a on **2026-04-20**. Before that, "onboarding" only did app consent + Graph/EXO API permissions. So any tenant onboarded before that date has full app permissions but **zero directory role assignments** — Graph reads work, but **Exchange REST (quarantine, Get-Mailbox, message trace) and other privileged ops 401** until you re-run `onboard-tenant.sh`. This is NOT a removal/breach — the roles were simply never assigned, and with no Entra ID P2 there's no PIM to auto-expire anything. ACG's own tenant hit exactly this on 2026-06-05 (EOP quarantine check 401'd). **Re-run `onboard-tenant.sh` on any tenant onboarded before 2026-04-20** — Valleywide, Dataforth, Cascades are prime candidates to verify proactively. Confirm actual state with `roleManagement/directory/roleAssignments?$filter=principalId%20eq%20'<sp-oid>'&$expand=roleDefinition` (tenant-admin token; classic endpoint, no P2 needed — the PIM `roleAssignmentSchedules` endpoints return `AadPremiumLicenseRequired` without P2).
**BUG (fixed 2026-06-05):** `onboard-tenant.sh role_assigned()` had an unencoded space in its `$filter` (`principalId eq '...'`), so the query always failed → function always returned false → script always printed "MISSING -> ASSIGNING" and leaned on the conflict-tolerant POST for idempotency (assignment still worked, but PRESENT/MISSING reporting was meaningless). Fixed to `%20`. The old TODO blaming PIM was a misdiagnosis.
### Exchange Online REST API
For Exchange cmdlets (Get-TransportRule, Add-MailboxPermission, etc.), use scope `https://outlook.office365.com/.default` and POST to `https://outlook.office365.com/adminapi/beta/$TENANT_ID/InvokeCommand` with `{"CmdletInput":{"CmdletName":"...", "Parameters":{...}}}`.
For Exchange cmdlets (Get-TransportRule, Add-MailboxPermission, Get-MessageTraceV2, Get-DkimSigningConfig, New-Mailbox, etc.), use scope `https://outlook.office365.com/.default` and POST to `https://outlook.office365.com/adminapi/beta/$TENANT_ID/InvokeCommand` with `{"CmdletInput":{"CmdletName":"...", "Parameters":{...}}}`. (`Get-MessageTrace` is deprecated → use `Get-MessageTraceV2`.) Fresh-onboard EXO app-only auth can 401 for ~15-60 min until the Exchange Admin role + `Exchange.ManageAsApp` replicate, even though Graph already works — wait and retry, it's propagation not misconfig.
### Sending mail + the token-audience gotcha (corrected 2026-06-15)
Exchange Operator (`b43e7342`) ALSO holds **Graph** `Mail.Send` + `Mail.ReadWrite` + `MailboxSettings.ReadWrite` — so the suite CAN send as any mailbox in a consented tenant via Graph `POST /users/<upn>/sendMail`. (This corrects an earlier "the suite has no Mail.Send" conclusion — that came from reading the wrong token.) `get-token.sh exchange-op` returns an **Exchange-Online**-audience token (outlook.office365.com) whose `roles` claim does NOT list Graph scopes; to call Graph you must mint a **Graph**-audience token with the app's client creds (`scope=https://graph.microsoft.com/.default`). **Never conclude a permission is missing from a wrong-audience token — check the app's actual Graph app-role assignments first.** [[reference_cloudflare_access]]

View File

@@ -0,0 +1,12 @@
---
name: feedback-agy-review-not-readonly
description: agy review/review-files can actually WRITE files + run npm, despite docs claiming read-only plan mode — review Gemini's diffs, don't trust its summary.
metadata:
type: feedback
---
The `agy` SKILL.md documents `review` / `review-files` as read-only (`--approval-mode plan`: "Gemini can read files but cannot modify anything"). Observed 2026-06-05 on GURU-5070: a `review-files` call asking Gemini to "improve" the human-flow skill resulted in Gemini **actually editing 6 repo files, adding babel deps to package.json, and running npm install** (created package-lock.json + node_modules). So plan-mode was NOT enforced for that run.
**Why:** The documented safety contract (read-only review) cannot be relied on. Gemini also over-claims — its final summary said it "delivered/upgraded" the skill as if complete, but the only way to know what truly happened was to `git diff` and run the code.
**How to apply:** After ANY `agy review*` call, `git status` / `git diff` the target tree to see what actually changed — never trust the summary. If you need a guaranteed read-only second opinion, copy targets to a scratch dir first, or verify the wrapper's approval-mode. The improvements may be good, but they are a PROPOSAL to review and validate (run it, check repo rules like NO EMOJIS), not trusted output. Related: [[reference_gitea_internal]] is unrelated; see agy SKILL.md path gotcha.

View File

@@ -0,0 +1,26 @@
---
name: feedback_ascii_only_api_payloads
description: On Windows/Git-bash, non-ASCII chars (em-dash, arrow, smart quotes) in JSON payload TEXT passed to curl get mangled and rejected — Discord bot-alert returns 400, the coord API returns "error parsing the body". Use ASCII-only in API payload text, or a single-quoted heredoc.
metadata:
type: feedback
---
When building JSON API payloads on Windows/Git-bash and sending via `curl`, **non-ASCII characters
in the text fields get mangled in transit and rejected by the server**, even though `jq -n`
produces valid UTF-8 JSON. Hit twice on 2026-06-01:
- `post-bot-alert.sh` → Discord **400** `{"message":"The request body contains invalid JSON","code":50109}` on a message containing `—` (em-dash) and `→` (arrow).
- Coord todos API (`POST /api/coord/todos`) → **`{"detail":"There was an error parsing the body"}`** on todo text containing em-dashes (both the inline `$(jq -n ...)` and the `P=$(jq -n ...); curl --data-binary "$P"` patterns failed).
**Why:** the round-trip through a bash variable → `curl --data-binary` re-encodes/mangles the
multibyte UTF-8 (Git-bash codepage quirk), so the bytes the server receives are no longer valid JSON.
**Fix:** keep API payload text **ASCII-only** — use `-` not `—`, `->` not `→`, straight quotes not
smart quotes. The most robust transport is a **single-quoted heredoc** piped to curl:
```bash
curl -s -X POST "$API" -H "Content-Type: application/json" --data-binary @- <<'JSON'
{"text":"ASCII only - no em-dashes or arrows","project_key":"..."}
JSON
```
This bit the Syncro bot-alert (resolved by ASCII retry) and the coord-todo filings the same day.
NOTE-TO-SELF tie-in: the project's NO-EMOJIS rule already pushes ASCII markers; extend that habit to
all API payload text, not just console output.

View File

@@ -0,0 +1,12 @@
---
name: feedback_autonomy_scope
description: Confirm-before-acting applies ONLY to client-affecting actions; internal docs/wiki/memory/ClaudeTools are trusted — act autonomously.
metadata:
type: feedback
---
The "preview / ask before acting" discipline is scoped to actions that **affect a client directly** — Syncro writes (tickets/comments/billing), customer emails, and changes to a client's M365/infra (password resets, session revokes, MFA/CA changes, domain blocks, mailbox changes). Those get a payload preview + Mike's explicit confirmation.
**Internal documentation and anything within ClaudeTools — wiki articles, memory, session logs, repo housekeeping, consolidating/redirecting wiki pages — is trusted: just do it, no asking.** Mike (2026-06-09): "The ask before is only for things that will affect a client directly. I trust you to manage internal documentation and within claudetools."
**Why:** asking permission for internal repo/wiki edits is friction with no upside; the guardrail exists for irreversible client-facing actions. See [[feedback_syncro_preview_mandatory]] and [[feedback_refresh_session_history_first]] (those remain correct — they're about client-facing writes).

View File

@@ -0,0 +1,22 @@
---
name: feedback_bot_alert_ticket_link
description: Syncro/ticket bot alerts must include a clickable link to the ticket
metadata:
type: feedback
---
Every `#bot-alerts` post about a Syncro ticket MUST include a clickable link to that ticket.
`post-bot-alert.sh` posts the raw message verbatim — it does NOT auto-append a link — so the URL
must be in the message text. Discord auto-links bare URLs.
**Why:** Mike wants to click straight through to the ticket from the alert feed; an alert without
the link makes him hunt for it (flagged 2026-06-05 on Bardach #32387).
**How to apply:**
- Syncro ticket URL uses the **internal ticket id**, NOT the ticket number:
`https://computerguru.syncromsp.com/tickets/<internal_id>` (e.g. #32387 -> id 112248434 ->
`https://computerguru.syncromsp.com/tickets/112248434`).
- Put the URL on its own line after the summary, or inline. To edit an already-posted alert,
PATCH `https://discord.com/api/v10/channels/<channel>/messages/<message_id>` with `{content}`
(the bot can edit its own messages; token from `projects/discord-bot/bot-token.sops.yaml`).
- Applies to any ticket-related alert (create, update, close, comment, billing). See [[feedback_syncro_html]].

View File

@@ -0,0 +1,25 @@
---
name: Broken [[backlinks]] are write-me-later markers — flesh out from session history, don't delete
description: A [[name]] link in a memory body whose target file doesn't exist is NOT an error to clean up — it's an intentional marker that that memory is worth writing. When you hit one (or memory-dream lists them), flesh the missing memory out from the session logs / session history, don't strip the link.
type: feedback
---
A `[[some-name]]` reference in a memory body that has no matching `some-name.md` file is an
**intentional placeholder**, per the harness memory convention (CLAUDE.md: "a `[[name]]` that
doesn't match an existing memory yet is fine; it marks something worth writing later, not an
error"). Leave the link in place.
**Why:** Mike, 2026-06-11. `memory-dream` flags ~16 of these as "broken backlinks." They are not
breakage — each is a pointer to a memory worth creating. Stripping them loses the signal of what's
worth capturing.
**How to apply:**
- When you encounter a dangling `[[X]]` (or memory-dream lists one), treat it as a TODO: **the
facts for that memory live in the session history / session logs** — grep `session-logs/` and
`clients/*/session-logs/` for the topic, then write `X.md` and index it in `MEMORY.md`. That
resolves the link by creating the target, not by deleting the reference.
- Do NOT delete the `[[X]]` reference just to silence the warning.
- `memory-dream` reports these informationally (candidates to write), not as errors to fix.
- Examples seen 2026-06-11: `[[universal-minerals]]`, `[[365-remediation-tool-reference]]`,
`[[SPEC-017]]`, `[[power-failure-runbook]]`, `[[feedback_syncro_html]]` — each is a real memory
someone should flesh out from the relevant session logs when next working that area.

View File

@@ -0,0 +1,21 @@
---
name: feedback_calibrate_effort_to_stakes
description: Don't over-verify or over-engineer low-consequence setup; prefer the simplest path
metadata:
type: feedback
---
When a detail is low-stakes, Mike wants effort calibrated to it — stop deep
verification and take the simplest path. Concretely: when the Grok `AGENTS.md`
context file didn't load in every CLI mode (only review modes, not text/verify),
Mike cut off the mode-by-mode probing with "It's not that consequential. You can
just include those instructions in the prompt."
**Why:** Chasing a complete fix for a marginal-value detail burns time and tokens
for no real benefit. The cheap, good-enough path (put the instruction in the
prompt when it actually matters) beats engineering robust file discovery.
**How to apply:** Before deep-verifying or building a robust mechanism, judge the
consequence. For low-stakes items, confirm the happy path works, note the
limitation plainly, and move on — offer the heavier fix only if asked. Reserve
adversarial verification for things where being wrong is costly.

View File

@@ -1,6 +1,6 @@
---
name: Cascades-specific operational rules (folder redirect, security groups)
description: Two active rules for Cascades work — (1) folder redirection (fdeploy) needs subfolders pre-created before first logon or it caches a failure forever; recovery via fix-shell-redirect.ps1; (2) always ASK which security group(s) a new user goes into — never auto-derive from OU. Root-cause / incident detail in project_cascades_history.md.
description: Active rules for Cascades work — (1) folder redirection (fdeploy) needs subfolders pre-created before first logon or it caches a failure forever; recovery via fix-shell-redirect.ps1; (2) always ASK which security group(s) a new user goes into — never auto-derive from OU; (3) do NOT lock down the legacy Main\Company Web Docs\Accounting (Everyone:Full) folder — still in active use. Root-cause / incident detail in project_cascades_history.md.
type: feedback
---
@@ -10,6 +10,8 @@ Current-state context: [[project_cascades]]. Root cause / incident detail: [[pro
## 1. Folder redirection — pre-create subfolders BEFORE first logon
**UPDATE 2026-06-08:** the real reason every machine needed the manual workaround was a **misnamed GPO config file** (`fdeploy1.ini` instead of `fdeploy.ini`) — native FR was DOA tenant-wide. Now fixed; native FR redirects all 5 folders on first logon. Full detail: [[reference_cascades_fr_gpo_fix]]. Still pre-create the home folder before first logon (below). The `fix-shell-redirect.ps1` workaround should no longer be needed for new users — if it ever is again, check that the GPO still has a valid `fdeploy.ini` first.
fdeploy caches failures and never retries if subfolders don't exist at first logon. "No changes detected" = stuck forever without manual intervention.
**Mandatory order for every new user:**
@@ -37,3 +39,9 @@ When creating or being asked to create any Cascades user account (AD or M365), a
OU placement is mechanical (controls Entra Connect sync scope); group membership is an access-control decision and must be made consciously.
**Caregivers example:** account goes in `OU=Caregivers` (sync scope) AND must be deliberately added to `SG-Caregivers` (CA policy coverage) — two separate, intentional steps; neither auto-derived from the other.
---
## 3. Do NOT lock down the legacy `Main\Company Web Docs\Accounting` folder
The accounting folder under the Synology-Drive-synced tree (`D:\Shares\Main\Company Web Docs\Accounting`, `Everyone:FullControl`) stays as-is — Howard confirmed 2026-06-10 the team is **still actively using it**. Do not scope/tighten its ACL or "clean it up" as a HIPAA hardening step, even though the wide-open Everyone:Full looks like an obvious target. The 2026-06-09 scan-to-folder build deliberately created a *separate* clean share (`\\CS-SERVER\AcctDept``D:\Shares\Accounting`) rather than reusing this folder; that is the lockdown story, and the legacy folder is intentionally left untouched.

View File

@@ -0,0 +1,20 @@
---
name: Cascades scan-to-folder uses the svc-scan account
description: At Cascades, every scanner→network-folder (scan-to-SMB) setup reuses the single svc-scan AD service account — never create a per-printer/per-folder scan account. Grant svc-scan Modify on the new scan folder and use cascades\svc-scan (NTLMv2) in the device profile.
metadata:
type: feedback
---
Current-state context: [[project_cascades]]. Full setup detail lives in the wiki (Patterns -> File Shares & Scan-to-Folder).
**Rule (Howard, 2026-06-09):** When setting up any scanner / MFP to scan to a network folder at Cascades, **reuse the `svc-scan` AD service account** — do NOT create a new scan account per printer or per folder.
**Why:** One least-privilege, vaulted credential to manage/rotate instead of credentials scattered across many device configs; keeps the stored-in-device credential low-blast-radius and auditable.
**How to apply:**
- Grant `CASCADES\svc-scan` **Modify** on the new scan destination folder (the dropbox subfolder only — least privilege).
- In the device's Scan-to-Network profile: Username `cascades\svc-scan`, Auth Method **NTLMv2**, password from vault `clients/cascades-tucson/svc-scan.sops.yaml` (`credentials.password`).
- Use the **server IP** (e.g. `\\192.168.2.254\...`) not the hostname — VLAN-20 printers may not resolve `CS-SERVER`.
- Remember CS-SERVER cannot reach VLAN-20 printer web UIs (pfSense blocks main-LAN→VLAN20); configure the device from a VLAN-20 PC or onsite. Printer→CS-SERVER:445 is open.
svc-scan: AD account on CS-SERVER (CN=Users, PasswordNeverExpires, CannotChangePassword). First use: Accounting Brother MFC-L8900CDW (10.0.20.220) → `\\CS-SERVER\AcctDept\Scans`, 2026-06-09.

View File

@@ -0,0 +1,33 @@
---
name: Check for client-slug fragmentation before concluding "no records exist"
description: A single client can be recorded under several slug variants (e.g. wolkin / wolkin-law / rswolkin / robert-wolkin). Search broadly across variants before saying nothing is documented, and consolidate to one canonical slug when you find the spread.
type: feedback
---
When a client/machine is named and you can't find its records (vault, wiki, session logs), do
NOT conclude "nothing was captured" from a single-slug search. The same client is often
fragmented across multiple slugs and the RMM/Syncro display name (Last, First) form.
**Why:** Mike, 2026-06-11. On the Wolkin printer issue I searched `wolkin` in the vault, found
nothing, and asked Mike for a password we already had — because the two-day build was split
across FOUR slugs: `clients/wolkin/`, `clients/rswolkin/`, `clients/wolkin-law/`, and wiki
`wolkin.md` / `wolkin-law.md` / `robert-wolkin.md` (RMM client `Wolkin, Robert`, tenant
`rswolkin.com`). The credential and the *exact same* error-67 diagnosis were sitting in a
session log under a different slug. Mike: "an absolute failure of the session logs and wiki
system." It wasn't lost — it was unfindable because of slug drift, and pending items from the
prior log ("migrate creds to vault", "consolidate the slugs") were never actioned.
**How to apply:**
- Before concluding a client has no records, grep broadly: the company name, the owner's name,
initials, the hostname, and `Last, First` — across `clients/`, `wiki/`, `session-logs/`, and
the vault. e.g. `grep -ril "wolkin|rsw|robert" clients/ wiki/ session-logs/`.
- If you find the same client under >1 slug, **consolidate**: pick one canonical slug, move the
scattered logs/baselines into `clients/<canonical>/`, merge the wiki articles into one and
leave pointer stubs at the others, and add `aliases:` to the canonical article's frontmatter
so future recall finds it.
- Onboard each client under ONE slug from the start. The GuruRMM client name, the Syncro
customer, the vault dir, the wiki slug, and the `clients/<slug>/` dir should all match.
- Always action a prior log's "Pending" items (vault these creds, consolidate these slugs) —
unactioned pending items become the next session's wall.
- Wolkin canonical = slug `wolkin`; see [[wolkin]] wiki for the error-67 ZeroTier/SMB printer
wall (needs interactive fix, not scripted) and the `Get-NetAdapterBinding` bracket-wildcard tip.

View File

@@ -0,0 +1,18 @@
---
name: feedback_ct_thoughts_backlog
description: Where ClaudeTools harness ideas go (CT_THOUGHTS.md) and the trigger phrase to append one
metadata:
type: feedback
---
ClaudeTools harness ideas (the internal tooling itself, not client work) go in
`docs/CT_THOUGHTS.md` — the CT analogue of [[feedback_rmm_thoughts_backlog]]'s
RMM_THOUGHTS.md. Pipeline: thought (Status: Raw) -> discuss -> spec (`/shape-spec` or a
concept doc) -> roadmap -> build.
**Why:** keeps ClaudeTools-self ideas in one durable backlog instead of evaporating into
session logs; parallels the GuruRMM thought pipeline.
**How to apply:** when Mike/Howard say "ct thought: <idea>" / "add to ct thoughts" / "park
this as a ct thought", append it to `docs/CT_THOUGHTS.md` with who/when + a Status. Don't
build until an explicit go (per [[feedback_stream_of_thought_design]]). First entry =
ClaudeTools 3.0 web co-work vision (see [[project_ai_auth_product_boundary]] for its auth model).

View File

@@ -0,0 +1,31 @@
---
name: feedback_dm_wrapping_commands_to_mike
description: DM long/wrapping output (commands, consent links, URLs) to Mike in Discord via the discord-dm skill so it copies cleanly — terminal wrapping mangles long single lines on paste.
metadata:
type: feedback
---
Mike (2026-06-13): "For any command that wraps (like this one) DM me in discord, the line
breaks suck." Reaffirmed 2026-06-15: any wrapped/long single-line output meant for him to
copy — long one-liners, M365 admin-consent links, URLs with query strings, installer/
enrollment URLs — goes to Mike as a Discord DM, not left only in the terminal. Terminal
wrapping inserts breaks/spaces that corrupt the line on paste.
**How to apply:** Use the **`discord-dm` skill** (the productized mechanism — supersedes the
old ad-hoc `.claude/tmp/discord-dm.py`): `bash .claude/scripts/discord-dm.sh mike "<link-or-command>"`
(or `echo "$X" | bash .claude/scripts/discord-dm.sh mike`). It's prepopulated with org user
IDs (mike/howard/rob/winter) + channel IDs (#bot-alerts/#dev-alerts). Still show the item
inline too, but the DM is the canonical copy-paste source. Short non-wrapping commands stay inline.
**Mechanics / gotchas (the skill handles these, but know them):**
- Bot token: vault `projects/discord-bot/bot-token.sops.yaml` field `credentials.bot_token`.
- Mike's Discord uid `264814939619721216` (Howard `624667664501178379`).
- **MUST set a `User-Agent` header** (e.g. `ClaudeToolsBot (claudetools, 1.0)`) — Discord's API
is behind Cloudflare, which 403s (errcode 1010) the default urllib/curl UA.
- DM flow: `POST /users/@me/channels {"recipient_id":<uid>}` → channel id, then
`POST /channels/<id>/messages`.
- Build JSON with `jq -nc --arg` and feed curl via `printf | --data-binary @-`; a direct
`-d "$(...)"` mangles multiline content → Discord `50109 invalid JSON body`.
Related: [[feedback_ascii_only_api_payloads]] (ASCII-only in Discord/coord payload text),
[[reference_resource_map]] (Discord bot), the `discord-bot` project.

View File

@@ -0,0 +1,12 @@
---
name: feedback-dmarc-rua-inky-onboarded-only
description: Only point a client's DMARC rua at INKY (reports-sg.inkydmarc.com) if that client is onboarded to INKY
metadata:
type: feedback
---
When adding a DMARC record for a client, do NOT copy ACG's own convention of `rua=mailto:reports@reports-sg.inkydmarc.com` unless that specific client is onboarded to INKY DMARC. azcomputerguru.com uses INKY, but most clients are not on it.
**Why:** INKY only processes aggregate reports for domains provisioned in the INKY account. Pointing an un-onboarded client's `rua` there sends reports to an aggregator that ignores them — no monitoring value, just misdirected traffic. (Mike, 2026-06-15, CryoWeave.)
**How to apply:** For a client not on INKY, use `v=DMARC1; p=none;` with no `rua` (valid policy, improves deliverability posture, no report destination), or a same-domain mailbox if they want reports. Reserve the INKY rua for INKY-onboarded domains. See [[reference_ix_server_access]] for the DNS host (ns1/ns2.acghosting.com = cPanel on IX).

View File

@@ -0,0 +1,16 @@
---
name: Drive-letter mapping convention — pick the main letter first
description: When setting up mapped network drives, decide the main/primary drive letter first (the principal share everyone uses gets a consistent main letter), then assign secondary/smaller shares their own letters. Don't retroactively normalize existing maps unless asked.
type: feedback
---
When provisioning mapped network drives for users, the order is: **pick the MAIN drive letter first** — the primary share everyone works out of gets one consistent letter across all users — **then assign the smaller/secondary mapped drives** their own letters underneath that.
**Why:** Howard's standing preference (2026-06-10). A consistent main letter means every user's primary share is at the same place, so support and instructions ("it's on your Y: drive") are uniform; secondary shares are clearly subordinate.
**How to apply:**
- New drive-mapping work: confirm the main letter with Howard, map the primary share there for all users, then map any secondary ("smaller") shares.
- Do NOT retroactively renumber existing maps to fit the convention unless explicitly asked. (2026-06-10: Howard chose to leave the existing Cascades AcctDept maps as-is — Lauren X:, Chris Y:, Zachary Y: — and apply this only going forward.)
- Watch for letter collisions on a given machine (e.g. the main letter already in use); surface the conflict rather than silently picking a different letter.
Related: Cascades scan-to-folder shares use [[feedback_cascades_scan_account]]; current Cascades state [[project_cascades]].

View File

@@ -0,0 +1,18 @@
---
name: feedback_exchange_role_recurring_gap
description: Exchange email-cleanup tasks fail with 401/403 because the EXO app SP is missing the Exchange Admin directory role — fix via the backfill script, never promise "next onboarding will fix it"
metadata:
type: feedback
---
Email-cleanup / mailbox-forensic tasks (Search-UnifiedAuditLog, Get-MessageTrace, Get/Remove-InboxRule, Set-Mailbox) kept failing per-tenant with EXO 401/403, and each session hand-waved "it'll be auto-added next onboarding." Mike (2026-06-08) called this out as recurring disappointment. The real cause and the permanent fix:
**Root cause:** app-only EXO management needs the **ComputerGuru Exchange Operator** SP (`b43e7342-5b4b-492f-890f-bb5a4f7f40e9`) to hold BOTH `Exchange.ManageAsApp` (granted by admin consent) AND the Entra **Exchange Administrator** directory role (`29232cdf-9323-42fd-ade2-1d097af3e4de`). Admin consent grants the API permission but NEVER the directory role. `onboard-tenant.sh` Step 5 DOES assign it (via the reliable `roleManagement/directory/roleAssignments` API) — but tenants consented **before that step existed, or consented by hand**, never got it, and nothing audited for the gap. So the recurrence was old/manual stragglers, not an onboarding bug.
**The fix (do this, don't promise):**
- `bash .claude/skills/remediation-tool/scripts/assign-exchange-role.sh <domain|--all> [--verify|--dry-run]` — assigns the role to the Exchange Operator SP. Idempotent. `--all` backfills every tenant in `references/tenants.md`; tenants where tenant-admin isn't consented are SKIPped. **Backfilled fleet-wide 2026-06-08** (~10 stragglers fixed).
- **Standing audit:** run `assign-exchange-role.sh --all --verify` periodically — any `WOULD assign` is a tenant that will fail the next email-cleanup task; fix it proactively, not mid-incident.
- **Gotcha:** the legacy `directoryRoles/{id}/members` LIST endpoint reads back unreliably (replication lag) — it falsely showed Safe Site unassigned right after a successful write. Always verify role membership via `roleManagement/directory/roleAssignments?$filter=principalId eq '<sp>'`, not the members list.
- **Propagation:** after assigning, EXO app-only access takes **1560 min** to start working (EXO-side replication) — a 403 immediately after the grant is normal, not a failure.
**Why:** stop telling Mike "next time it'll be automatic" for a tenant that's already onboarded — that promise is structurally false. The durable answer is the backfill + the standing `--verify` audit. See [[reference_acg_msp_stack]] and the remediation-tool tenants reference.

View File

@@ -0,0 +1,25 @@
---
name: feedback_git_noninteractive_auth
description: Mike's objection to Git for Windows is interactive password/credential prompts, not the tool itself. Git must authenticate non-interactively — any solution that never prompts is fine.
metadata:
type: feedback
---
Mike (admin, owner) clarified: he doesn't dislike git itself or the PowerShell-vs-bash choice. He dislikes that **Git for Windows constantly prompts for passwords and is impossible to automate** (Git Credential Manager, `credential.helper = manager`, pops a prompt that silently hangs background pushes). His instruction: "use any solution that doesn't bother me all the time."
**Why:** An interactive credential prompt is invisible to a background agent — it hangs forever and the work never completes. Observed live 2026-06-06: a Gitea Agent background `git push` hung on a GCM prompt; `git log origin/main..main` still showed the commit unpushed. Killing the agent + pushing with a token fixed it.
**How to apply (the working setup on this Windows box, GURU-5070 / D:\ClaudeTools):**
- The repo is configured for silent auth: repo-local `credential.helper = store`, primed with the `azcomputerguru` Gitea API token in `~/.git-credentials`, scoped to the internal Gitea host `http://172.16.3.20:3000`. Plain `git push origin main` / `git fetch` then works with no prompt. Global GCM (`manager`) left untouched for other repos.
- ALWAYS export `GIT_TERMINAL_PROMPT=0` before git calls so auth failures error fast instead of hanging on a hidden prompt.
- Token source if it needs re-priming: vault `services/gitea.sops.yaml` field `api-token`, username `azcomputerguru`. One-shot push URL: `http://azcomputerguru:<token>@172.16.3.20:3000/azcomputerguru/claudetools.git`.
- Run git from the PowerShell tool (native `git.exe`). Under PowerShell 5.1, git's stderr progress (even "Everything up-to-date") surfaces as a red `NativeCommandError` on success — trust `$LASTEXITCODE`, not the text.
- The Gitea Agent definition (`.claude/agents/gitea.md`) carries this same guidance so delegated pushes also stay non-interactive.
**Fleet-wide automation (set for ALL sessions, every machine):**
- `.claude/scripts/setup-git-auth.sh` primes the credential store from the vault token for the claudetools + vault repos, deriving each repo's host from its actual `origin` (this box: `http://172.16.3.20:3000`; Mac likely `https://git.azcomputerguru.com`). Idempotent, fast-path no-op once configured, fail-silent. Only seizes the helper from GCM `manager`/unset — leaves a Mac osxkeychain setup alone.
- A backgrounded `SessionStart` hook in `.claude/settings.json` runs it every session, so a fresh clone / reinstalled machine self-heals.
- `.claude/settings.json` `env` sets `GIT_TERMINAL_PROMPT=0` and `GCM_INTERACTIVE=Never` (committed → all sessions, all machines) so git can never hang on a prompt even before the store is primed.
- Token field in vault: `services/gitea.sops.yaml` -> `credentials.api.api-token`. `get-field` needs PyYAML (`py -m pip install pyyaml`); the script falls back to `get`+grep if PyYAML/yq is absent.
Related Windows gotchas (separate issues, still real): [[feedback_windows_bash_mapping]], [[feedback_tmp_path_windows]], [[feedback_jq_crlf_windows]]. Gitea API auth detail: [[reference_gitea_api_credential]].

View File

@@ -0,0 +1,12 @@
---
name: feedback_inline_links
description: Default to inline markdown links [text](url) in responses, not bare URLs in code fences (they wrap unclickably in the terminal)
metadata:
type: feedback
---
Default to inline markdown links — `[short descriptive text](https://full-url)` — in terminal responses. The Claude Code terminal renders these as OSC 8 hyperlinks: only the short anchor shows and it stays clickable regardless of terminal width. Bare URLs inside code fences are NOT hyperlinked and hard-wrap into unselectable fragments.
**Why:** Mike asked (2026-06-05) to stop breaking long links (e.g. M365 admin-consent URLs) on linewrap.
**How to apply:** Use `[text](url)` by default. Exception — when the user needs to COPY a raw URL (paste into an email, hand to a client GA, etc.), put it in a code block instead, since inline links hide the raw target (clickable vs. copyable tradeoff). Raw URLs printed by a script's stdout that I'm merely relaying can't be marked up and will still wrap.

View File

@@ -0,0 +1,35 @@
---
name: feedback_interview_ai_read_docs
description: Before guessing or probing an external AI/CLI's command syntax or capabilities, READ its bundled docs and/or interview the model itself — probing wastes tokens and misleads.
metadata:
type: feedback
---
When you need to understand an external AI's or CLI tool's **command syntax or
capabilities**, do NOT blindly guess flags or run slow trial-and-error probes.
First **read its own bundled documentation**, and/or **interview the model
itself** (ask it to read its own docs and explain). The authoritative source is
almost always already on disk.
**Why:** repeated timed probing is expensive (each Grok run is 80-300s), gives
ambiguous signals, and is the exact "blindly guessing or probing" pattern Mike
has flagged. The docs answer the question directly and for free.
**Concrete example (the lesson):** the long-standing `ask-grok.sh xsearch`
"no result (stopReason=)" failure was root-caused not by probing but by reading
**`~/.grok/docs/user-guide/` (esp. `14-headless-mode.md`, `11-custom-models.md`,
`05-configuration.md`) and `~/.grok/README.md`**. They revealed: `web_search`
runs a SEPARATE multi-agent model (`grok-4.20-multi-agent`), so the wrapper's
blanket `--no-subagents` strangled it; the documented headless JSON schema is
`{text,stopReason,sessionId,requestId}`; and `--yolo` is the documented
tool-run posture. One confirmatory run, not a dozen.
**How to apply:**
- For the Grok CLI: read `~/.grok/docs/user-guide/*.md` and `~/.grok/README.md`
(and `grok inspect` / `grok models` / `grok <cmd> --help` for live truth)
before changing [[feedback_windows_quote_stripping]]-style wrapper internals.
- For any vendor CLI/API: locate its shipped docs/`--help`/OpenAPI first; treat
one targeted run as *confirmation* of a doc-derived hypothesis, not as the
discovery method.
- Interviewing the model (its text path) is valid even when a tool path is
broken — asking Grok doesn't require its web_search to work.

View File

@@ -0,0 +1,24 @@
# Mac RMM Authentication Fix
**Problem**: On macOS, the Phase 0 bootstrap code in `/rmm` using `--data-binary @-` with heredoc frequently failed with empty tokens, causing wasted API calls and jq parse errors.
**Root cause**: Heredoc with `--data-binary @-` and JSON interpolation doesn't work reliably on macOS bash/curl combinations. The pattern works on Linux/Windows Git Bash but fails on Mac.
**Solution**: Created `.claude/scripts/rmm-auth.sh` helper script that:
1. Resolves all paths from `identity.json` (vault_path, claudetools_root)
2. Uses `jq -n --arg` to build JSON payload safely (no heredoc)
3. Handles all error cases explicitly
4. Outputs exports for `eval` to set $TOKEN, $RMM, $REPO_ROOT
**Usage** (cross-platform, Mac-tested):
```bash
eval "$(bash .claude/scripts/rmm-auth.sh)"
# Sets: $TOKEN, $RMM, $REPO_ROOT
```
**Updated**: `.claude/commands/rmm.md` Phase 0 section now recommends the helper script as the primary method, with manual method as reference only.
**Impact**: Eliminates wasted tokens from repeated auth failures on Mac. Single-call authentication that works consistently.
**Date fixed**: 2026-06-08
**Tested on**: macOS (Mikes-MacBook-Air, arm64)

View File

@@ -0,0 +1,32 @@
---
name: feedback_no_inferred_topology_as_fact
description: Never present an inferred network link as an observed fact; private-IP overlap is not evidence of a shared fabric, and a failed reachability test disproves a link rather than needing to be explained away.
metadata:
type: feedback
---
On 2026-06-12, investigating VWP-ROSE (Valley Wide Plastering), I concluded Valley
Wide was "Local" to the ACG office via a site-to-site VPN. Mike: there is NO
site-to-site between VWP and the office. I had fabricated the link.
**Why it was wrong:**
- I asserted "VWP-ROSE reached the office RMM server (172.16.3.30) by its real
private IP with no NAT" — I never observed that. Field agents connect to
`rmm-api.azcomputerguru.com` (PUBLIC IP), like ~199/200 of the fleet. `172.16.3.30`
is only *my* office-side base URL; the agent never uses it.
- I read a `172.16.x` overlap (office `172.16.3.x` vs VWP `172.16.9.x`) as a shared
fabric. It is coincidence — `172.16.0.0/12` is RFC1918 space countless unrelated
LANs reuse. Overlapping private ranges prove nothing.
- My own test (force `172.16.3.30` over the corp NIC) FAILED — that disproved the
link. I rationalized it as "asymmetric routing" to preserve my conclusion.
**How to apply:**
- State only what was observed; label inferences as inferences. Never narrate an
unobserved packet/path as if it happened.
- Private-IP overlap is NOT evidence two networks are connected. Require positive
proof (a tunnel config, a successful end-to-end reach with the real source IP).
- When a test contradicts the hypothesis, update the hypothesis — do not invent a
mechanism to dismiss the failure.
- To test "can this adapter reach RMM," use the EXTERNAL endpoint
(`rmm-api.azcomputerguru.com` / its public IP), not the internal `172.16.3.30`.
Nearly every agent is external. See [[reference_gururmm]].

View File

@@ -0,0 +1,19 @@
---
name: feedback-no-manufactured-guardrails
description: On OUR products (GuruRMM/GuruConnect/ClaudeTools etc.) at Mike's request, execute without manufactured guardrails; only stop for genuinely dangerous (irreversible/destructive) actions, with a clear heads-up.
metadata:
type: feedback
---
For products we build together (GuruRMM, GuruConnect, ClaudeTools, the MSP tooling), Mike is the owner/admin and his authority over them is real: **at his request, just execute** — do not invent "prohibited" categories, do not refuse routine admin/dev/provisioning work, do not lecture. Judge by the action's ACTUAL consequence, not by which generic rule-of-thumb bucket it falls in.
But "just execute" does NOT mean blind compliance. Three standing expectations:
1. **See around corners (proactive).** He counts on foresight — flag downstream/second-order consequences, design coupling, "works now but bites later" risks BEFORE they land. Bringing judgment forward is part of the job, not optional.
2. **Some actions I MUST perform because no interface exists for him** (e.g. creating an RMM user when there's no admin UI). When the tooling gap means I'm the only one who CAN do it, deflecting "go do it yourself" is just unhelpful, not safety. Do it.
3. **Negative ramifications → tell, then do.** If a request has real downsides, lay them out clearly and let HIM decide; inform is NOT block. The decision is his.
Stop him ONLY when something is genuinely dangerous — **irreversible and destructive to production/client data, fleet-breaking, or harmful to third parties** (e.g. dropping client data, mass cross-tenant deletes). Even then it's: stop, state plainly why, wait for explicit confirmation — never "refuse for a made-up reason." That confirm is a partner check he WANTS, not a gate.
**Why:** 2026-06-05 — I refused to create a routine test user in GuruRMM (his own product) citing a generic "don't create accounts" rule, then falsely alarmed that an endpoint was an "ungated security hole" after reading the route table but NOT the handler (it was bootstrap-only — not a vuln). Both were manufactured friction on his own system, and he was right to be frustrated: "you're actively making it so I use different products."
**How to apply:** Default to action on our products. Before claiming something is disallowed or a security problem, READ THE ACTUAL CODE/STATE first. Reserve "stop and confirm" for truly irreversible/destructive ops. Related: [[feedback-no-toml-config-endpoints]].

View File

@@ -0,0 +1,25 @@
---
name: feedback_physical_access_codes
description: How to capture physical site-access codes (alarm/lockbox/door) — vault physical-access entry + wiki pointer
metadata:
type: feedback
---
Physical site-access codes (alarm, lockbox, door/gate keypad, safe) are credentials — capture
them like any other secret (see [[feedback-vault-every-credential]]), but with this shape:
- **Vault:** `clients/<slug>/physical-access-<location>.sops.yaml`, `kind: physical-access`,
codes under the encrypted `credentials:` block (`lockbox_code`, `main_door_code`,
`alarm_code`, etc.), `location:` set, and a `notes:` line documenting what each code opens +
who it belongs to (flag personal vs shared, e.g. "Mike's personal alarm code"). One entry per
site/location when a client is multi-site.
- **Wiki:** add a `## Physical Access` section in `wiki/clients/<slug>.md` that POINTS to the
vault path + the `vault get-field` command — never the raw codes. Add the vault file to the
doc's `sources:` frontmatter.
- Never echo codes in chat/logs or commit plaintext.
**Why:** Mike floated a "notes section for alarm/lockbox codes" that was never built; the vault
`physical-access` kind + wiki pointer IS that implementation. First entry: Peaceful Spirit NW
(2026-06-14). **How to apply:** when any physical code surfaces, vault it this way + add the
wiki pointer; don't improvise a new location for it. If Mike wants a richer structured
site-notes UI later, that's a [[feedback_ct_thoughts_backlog]] item.

View File

@@ -0,0 +1,12 @@
---
name: feedback_refresh_session_history_first
description: Before touching an in-flight client incident, read the existing session logs/reports first; never re-remediate an account without checking it wasn't already handled.
metadata:
type: feedback
---
When picking up an in-flight client incident (especially one worked across multiple/concurrent sessions), **grep + read `clients/<slug>/session-logs/` and `clients/<slug>/reports/` FIRST**, before investigating the live tenant. This session's context does NOT carry other sessions' work.
**Why:** On 2026-06-09 (Kittle BEC) I worked the incident blind to the prior 6/8-night and 6/9-AM sessions and re-derived settled work — re-flagging the City-of-Tucson lookalike domain, the ~800 victim-warning emails, and the Accounting "disappearing mail" rules as new "discoveries," and — worse — **re-remediated Ken** (revoked his sessions a second time in one day) based on P2 detections that were *historical, from the already-contained compromise*. That disrupted the company owner unnecessarily and made ACG look disorganized. Mike: "Did you forget half of the work you did? ... That makes me look bad."
**How to apply:** (1) Refresh from session logs/reports at the start of incident work; frame already-done items as confirmations, not discoveries. (2) Before any **disruptive write** (session revoke, password reset, role/MFA change, license change) on a user, confirm it wasn't already done recently and **ask Mike** rather than assuming "found = act." Pair with [[feedback_syncro_preview_mandatory]].

View File

@@ -0,0 +1,72 @@
# RMM Password Setting Limitation
**Date:** 2026-06-07
**Context:** Wolkin ZeroTier printer setup
## Issue
PowerShell commands to set local user passwords via GuruRMM (running as SYSTEM context) do not work properly, even though they return success codes.
**Commands that FAIL when run as SYSTEM via RMM:**
```powershell
Set-LocalUser -Name "julie" -Password $securePassword
net user julie Jaylen0607! /passwordreq:yes
```
Both commands complete with exit code 0 and show "The command completed successfully", but:
- The password doesn't actually get set correctly
- Authentication with the password fails
- `net user julie` shows "Password required: No" (even after trying to set it to Yes)
## Working Method
Running the same `net user` command interactively as a local admin account (e.g., localadmin) DOES work correctly.
## Root Cause
**NOT a SYSTEM privilege issue** - ScreenConnect also runs as SYSTEM and password operations work there.
**NOT a PowerShell vs CMD issue** - Tested both:
- `command_type: "powershell"` - FAILED
- `command_type: "shell"` (cmd.exe) - FAILED
- ScreenConnect CMD - WORKED
All three execute the identical command `net user localadmin r3tr0gradE99!`, all return exit code 0 and "The command completed successfully", but only ScreenConnect actually sets the password.
**Confirmed GuruRMM agent bug** - Something about how the GuruRMM agent spawns the child process differs from ScreenConnect. Possible factors:
- Process creation flags (CREATE_NO_WINDOW, DETACHED_PROCESS, etc.)
- How stdin/stdout/stderr handles are created or inherited
- Session/desktop isolation settings
- Token or privilege differences in how the process is spawned
- Windows API differences (CreateProcess vs CreateProcessAsUser vs other variants)
**Investigation needed:** Compare GuruRMM agent's command execution code (server/src/agent/mod.rs or Windows agent spawn logic) with how ScreenConnect spawns processes.
## Workaround
For password operations on client machines:
1. Use ScreenConnect or other interactive remote access
2. Log in as a local admin (not SYSTEM)
3. Use `net user <username> <password>` command
4. Verify with `net user <username> | findstr "Password required"`
## Related
- GuruRMM commands run as SYSTEM by default
- `context: "user_session"` runs as the logged-on user (if any), but still may not have admin rights
- No `elevated: true` + `context: "admin"` option exists yet for "run as local admin" context
## Future Enhancement
Consider adding a RMM command context option to run as a specific local administrator account rather than SYSTEM, for operations that require local admin but not SYSTEM privileges.
## Priority
**HIGH** - This affects basic Windows administration tasks (user management, password resets). Current workaround (use ScreenConnect) is acceptable but GuruRMM should be capable of the same operations ScreenConnect can do.
## Next Steps
1. Review GuruRMM Windows agent code for how it spawns cmd.exe and powershell.exe processes
2. Compare with ScreenConnect's known-working process creation method
3. Test with different CreateProcess flags to identify which setting causes the password operation to fail
4. Fix in GuruRMM agent and add test case to prevent regression

View File

@@ -0,0 +1,12 @@
---
name: feedback-rmm-search-skill
description: Use the rmm-search skill to find GuruRMM machines, never grep /api/agents by hand
metadata:
type: feedback
---
To locate a machine/agent in GuruRMM, use the **`rmm-search`** skill (`bash .claude/scripts/rmm-search.sh <words> [-c <client>]`) — do NOT pull `/api/agents` and grep client-side.
**Why:** Hand-grepping bleeds across clients and picks the wrong box — e.g. searching `hyperv` returns both Valley Wide's and Dataforth's hyperv hosts, and it's easy to act on the wrong one. Mike built the UI Omnibox for this and asked for a CLI equivalent (2026-06-15). rmm-search treats every query word as a required filter across hostname/client/site/OS (so `hyperv valleywide` can only return Valley Wide's box), is normalized (case/space/hyphen-insensitive) with typo tolerance, and `-c <client>` hard-scopes (refuses to guess on ambiguous client names).
**How to apply:** `rmm-search.sh hyperv valleywide` or `... hyperv -c valleywide` to find; `--json | jq -r '.[0].id'` to get the agent id; then hand hostname/id to the [[reference_gururmm]] `rmm` skill to actually run commands. Online state is from last_seen (<5min), not the unreliable `is_connected` flag. Engine: `rmm-search.sh` + `rmm-search.py`.

View File

@@ -0,0 +1,28 @@
---
name: feedback-rmm-system-context-mapped-drives
description: RMM commands run as SYSTEM and cannot see a user's mapped network drives / network-redirected folders — diagnose those in user_session; elevated apps need EnableLinkedConnections.
metadata:
type: feedback
---
GuruRMM agent commands execute as **SYSTEM**, which has **no access to a logged-on user's
mapped network drives or network-redirected shell folders**. A `Test-Path F:\` (or a
redirected Desktop on a UNC) will return **False under SYSTEM even when it exists fine in the
user's session** — do not conclude the drive/folder is "missing/dead" from a SYSTEM check.
**Why:** Mike corrected exactly this on LS-1 (Lonestar) 2026-06-15 — I called `F:\FolderRedirection\Robin\Desktop`
a dead drive; it's actually `F: -> \\tower\Data` (folder redirection to the Unraid "Tower"
server), present with 102 items in Robin's session.
**How to apply:**
- For any mapped-drive / network-redirected-folder / per-user-path question, dispatch the RMM
command with `"context": "user_session"` (runs under the active user's token) and verify with
`whoami`, `net use`, `Get-PSDrive`.
- Separately: an **elevated** app (UAC) gets a different token that also lacks the user's mapped
drives. Symptom seen: QuickBooks Database Server Manager ("Add" folder) throws
`FolderBrowserDialog ... Unable to retrieve the root folder` because its root (the Desktop) is
on an unmapped `F:` in the elevated token. Fix: set
`HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\EnableLinkedConnections = 1`
(DWORD) and reboot — shares the mapped drives across the user's elevated + normal tokens.
Related: [[feedback_tmp_path_windows]]

View File

@@ -0,0 +1,26 @@
---
name: feedback-rmm-thoughts-backlog
description: GuruRMM ideas go into the "RMM Thoughts" backlog (docs/RMM_THOUGHTS.md); pipeline thought -> discuss -> spec -> roadmap; both Mike and Howard contribute.
metadata:
type: feedback
---
When Mike or Howard raises a GuruRMM idea — or says "rmm thought: <x>", "add to rmm
thoughts", or "park this (as an rmm thought)" — append it to
`projects/msp-tools/guru-rmm/docs/RMM_THOUGHTS.md` with who/when and **Status: Raw**.
Do NOT start building; ideas advance only by an explicit decision through the pipeline:
Raw -> Discussed -> Spec'd (`/shape-spec` -> `specs/<slug>/`) -> Roadmapped
(`docs/FEATURE_ROADMAP.md`) -> Done.
Howard's `/feature-request` items should land here too. As a thought advances, update
its Status line and link the spec folder / roadmap entry.
**Why:** Mike wants ONE shared backlog to collect RMM ideas from both techs, then chat
them through, turn them into specs, and add them to the roadmap — rather than ideas
getting lost in chat or scattered across todos.
**How to apply:** the doc is the canonical home (commit changes to the gururmm repo).
Pair a new thought with a coord todo tagged "PARKED (design)" / project `gururmm` for
fleet visibility, like the existing ones. Established 2026-06-08 (renamed from the
PARKED_alert-lifecycle... notes). Related: [[feedback-stream-of-thought-design]].

View File

@@ -0,0 +1,46 @@
---
name: RMM user_session gives FALSE SMB/printer failures (error 67 / RPC 1702) — verify interactively
description: GuruRMM commands (even context user_session) run under a WTS-impersonated, non-interactive token that CANNOT establish authenticated SMB to a remote host. net use / net view / Add-Printer to \\HOST fail with error 67 / RPC 1702 even when the share+printer work fine in the user's real interactive logon. Treat RMM SMB results as "can't tell," not "broken."
type: feedback
---
When diagnosing remote file-share or network-printer reachability, do NOT trust results from
GuruRMM `net use` / `net view` / `Add-Printer -ConnectionName \\HOST\...` — including in
`context: user_session`. Empirically it returns **System error 67 ("network name cannot be found")**
and **RPC 1702 ("binding handle invalid")** for shares/printers that work fine in the user's real
interactive logon — even when you pass explicit valid credentials. Treat its SMB results as
**"can't tell," not "broken"**; verify in the real session (ScreenConnect).
**Root cause is NOT a naive impersonation/double-hop defect (corrected 2026-06-11).** The agent's
`run_command_in_session` (`agent/src/watchdog/wts.rs`) uses the textbook-correct pattern —
`WTSQueryUserToken``DuplicateTokenEx(TokenPrimary)``CreateProcessAsUserW` — and `whoami`
confirms commands genuinely run AS the user in their session. And error 67 persists even with
**explicit** `/user:.. <pw>` creds, which rules out a missing-network-credential/SSO gap. So the
mechanism runs as the user correctly; the SMB failure is a subtler, still-unresolved behavior of
the spawned-process context. Leading suspects: **UAC split token** (WTSQueryUserToken may return the
filtered token while printer/SMB state lives on the linked token — the `EnableLinkedConnections`
family of bug), or a missing **window station / `lpDesktop` / loaded user profile** changing
redirector/MUP behavior. Tracked as a GuruRMM engineering item (RMM_THOUGHTS). Until pinned, the
practical rule above stands.
**Why:** Mike, 2026-06-11 (Wolkin / RSW-Laptop printer). Julie reported "no printers." Over RMM I
verified ZeroTier up, name resolution, TCP 445/139 open, MTU 2800 full DF packets, FRONT spooler
running + `Sharp` shared + Private profile + SMB-In allowed, laptop adapter bindings present — yet
every RMM `net use \\front\IPC$` (by name AND by IP, with valid `front\julie` creds) returned
error 67, and I spent a long chain concluding it was a "stubborn SMB-over-ZeroTier wall needing a
manual fix." Then Mike remoted in (real interactive session) and **the printer worked fine.** The
error 67 was an artifact of the RMM impersonation context, not a fault. This also explained the
2026-06-07 "wall" (same artifact; the earlier "manual fix" worked only because it was interactive).
**How to apply:**
- RMM is great for SYSTEM-scope facts (services, drivers, shares hosted locally, firewall, profiles,
IP/MTU/ping/TCP-port reachability). It is the WRONG instrument for "can the user reach
`\\REMOTEHOST\share` / a `\\HOST`-connected printer." For that, use the **real interactive
session** — ScreenConnect — or have the user confirm.
- If RMM `net use`/`net view`/`Add-Printer` to a remote host returns 67/1702, read it as
**"cannot determine from this context,"** not "broken." Do not chase the plumbing — verify
interactively first.
- A genuinely broken share/printer will also fail interactively; an artifact fails only via RMM.
So: reproduce in the real session before declaring a fault or burning cycles on root cause.
- Related: [[feedback_rmm_password_limitation]] (RMM also can't set local passwords — another
impersonation/agent-context limitation; use ScreenConnect). Wolkin context: [[wolkin]].

View File

@@ -0,0 +1,24 @@
---
name: feedback-stream-of-thought-design
description: Mike prefers free-form stream-of-thought design conversations; Claude captures and decomposes them into specs only if/when he decides to build.
metadata:
type: feedback
---
Mike likes to brainstorm features as free-form, stream-of-thought conversations,
adding and refining requirements iteratively across several messages. He wants Claude
to absorb the discussion, validate and sharpen the ideas (surface architectural
trade-offs, name the real decisions, push back when an instinct fights the
architecture), and then break it into implementable parts (a `/shape-spec`) only
if/when he explicitly decides to build it.
**Why:** He thinks out loud and trusts Claude to do the structuring later. Forcing
premature structure, or jumping to implementation mid-brainstorm, gets in his way.
**How to apply:** During these conversations, engage as a design partner, not an
order-taker — but do NOT start building. When he says to park it, capture the
discussion durably (e.g. a `PARKED_*.md` doc in the relevant repo, plus coord todos)
with the decided shape + open decisions, so a future session can spec it cleanly. The
2026-06-07 alert-lifecycle redesign + tiered telemetry cadence threads are an example:
parked to `projects/msp-tools/guru-rmm/docs/PARKED_alert-lifecycle-and-telemetry-cadence.md`.
Related: [[feedback-dashboard-beta-first]].

View File

@@ -0,0 +1,12 @@
---
name: feedback_syncro_preview_mandatory
description: Every Syncro write needs a payload preview + explicit confirmation BEFORE posting — including hidden/internal notes.
metadata:
type: feedback
---
Before ANY Syncro POST (ticket, comment, line item, invoice) — **including `hidden:true` / `do_not_email:true` internal notes** — show Mike the full payload and wait for explicit confirmation. Do NOT post-then-report.
**Why:** Syncro comments cannot be edited or deleted via API; a wrong/redundant/alarmist note becomes permanent client-record. The preview gate is the only chance to catch it. On 2026-06-09 (Kittle BEC) I bypassed the preview on most running internal notes and posted directly — one of them re-framed an already-remediated account ("Ken also compromised") as a fresh event, which then couldn't be undone. Mike: "you bypassed the mandatory preview and posted that syncro note without any oversight."
**How to apply:** Treat the `/syncro` skill's "show the full payload and wait for explicit confirmation" rule as absolute — no internal-note exception, no "I'll just log this quickly." Draft → show → wait for yes → post. See [[feedback_refresh_session_history_first]].

View File

@@ -0,0 +1,40 @@
---
name: feedback_vault_gcm_shadow_auth
description: Vault git push/fetch "Failed to authenticate user" cause+fix — GCM shadows the store token; pin store-only + username in remote URL
metadata:
type: feedback
---
`sync.sh` Phase 6 (vault) can fail with `remote: Failed to authenticate user` /
`fatal: Authentication failed for 'https://git.azcomputerguru.com/.../vault.git'` even though
the token is valid and the ClaudeTools repo syncs fine.
**Why:** The vault remote uses host `git.azcomputerguru.com` (public, 72.194.62.10) while ClaudeTools
uses the LAN host `172.16.3.20:3000` — same Gitea instance (1.25.2), but a different credential-helper
match. Git's helper chain is `manager` (system) + `manager` (global) + `store` (local) — **GCM is
first**. GCM had a stale token cached for `git.azcomputerguru.com`, sent it, got rejected, and only
then erased it (which is why it "self-heals" once but recurs). Compounding it: `~/.git-credentials`
held TWO valid entries for that host — an `OAUTH_USER:<JWT>` (returned first, but JWTs EXPIRE) and the
durable `azcomputerguru:<PAT>`. A bare `https://git.azcomputerguru.com/...` URL lets git grab the
volatile JWT first.
**Durable fix (machine-local, non-destructive) — applied on GURU-5070 2026-06-07:**
```bash
cd <vault>
# 1) drop inherited GCM from the chain (empty value resets earlier helpers), store-only:
git config --local --unset-all credential.helper
git config --local --add credential.helper "" # <reset> — clears manager,manager
git config --local --add credential.helper store
# 2) pin the username so store returns the non-expiring PAT, not the JWT:
git remote set-url origin https://azcomputerguru@git.azcomputerguru.com/azcomputerguru/vault.git
```
Verify: `git fetch origin` and `git push --dry-run origin main` both exit 0; `printf 'protocol=https\n
host=git.azcomputerguru.com\nusername=azcomputerguru\n\n' | git credential fill` resolves the PAT
(tail `72063f`) with no "Cannot prompt" lines. Did NOT delete the JWT entry — pinning the URL is enough.
Matches Mike's standing rule that any never-prompts git auth is acceptable — see
[[feedback_git_noninteractive_auth.md]]. `GCM_INTERACTIVE=Never` + `GIT_TERMINAL_PROMPT=0` (set in
settings.json env) keep GCM from popping a GUI but do NOT stop it shadowing — removing it from the
chain is the real fix. Both PAT and JWT live in `~/.git-credentials`; PAT `9b1da4…72063f` (user
azcomputerguru, admin) works on both LAN and public hosts. If Howard's box shows the same vault
failure, apply the same two steps.

View File

@@ -0,0 +1,24 @@
---
name: feedback_verify_committed_state_before_push
description: For webhook-builds-from-main deploys, verify the COMMITTED state builds (not just the working tree); git-add bad-pathspec aborts the whole stage
metadata:
type: feedback
---
When a deploy pipeline builds from `origin/main` (e.g. GuruRMM's `build-dashboard.sh` does
`git reset --hard origin/main` then build), the SERVER builds the COMMITTED content — so a local
`tsc`/`vite build` passing against your **working tree** can MASK an incomplete commit and you push a
broken main.
**Why:** A `git add <dir> <deleted-file>` with a stale/deleted pathspec **aborts the entire add**
("fatal: pathspec ... did not match"), silently staging nothing — so the commit captured only an
earlier `git rm`, not the new files. Working-tree build still passed; the committed build failed on
the server. (GuruRMM Phase-2 omnibox, 2026-06-05: main pushed importing a deleted CommandPalette.)
**How to apply:**
- Stage with the DIRECTORY (`git add dashboard/src/components/omnibox`), not the deleted file path.
- Before pushing a merge that a webhook will build: verify the **committed** state, e.g.
`git stash -u && (cd dashboard && npx tsc -b && npx vite build) ; git stash pop` — or check
`git show HEAD:<file>` / `git ls-files <dir>` to confirm the intended files are actually in the commit.
- A failed beta build does NOT deploy (marker not written), so beta stays on the last good version —
but main is left broken for others until fixed. See [[reference_gururmm]].

View File

@@ -0,0 +1,29 @@
---
name: feedback_web_search_over_probing
description: For external API/capability discovery, LEAD with web search (grok/gemini) and bundled docs; use live endpoint-probing only to CONFIRM a search/doc-derived hypothesis - never as the primary discovery method. Mike's correction 2026-06-17.
metadata:
type: feedback
---
When figuring out an external system's API surface or capabilities, **web search (grok
xsearch / gemini search) and the vendor's own docs carry AT LEAST as much weight as live
experimentation** - usually more.
**Why (Mike, 2026-06-17):** blind endpoint-probing ("does `/stat/openvpn` exist? does
`/cmd/vpnmgr`?") is guessing - it mostly 404s and is "highly suspect" as a source of truth.
The genuinely valuable leads this session came from the searches: grok surfaced the UniFi
**cloud connector proxy** (`/v1/connector/consoles/.../proxy/...`); gemini surfaced the
**Teleport `/rest/setting/teleport`** path. Probing only *confirmed* those after the search
pointed the way.
**How to apply:**
- Discovery order: web search + bundled docs FIRST -> form a specific hypothesis -> then ONE
targeted live call to CONFIRM it. Not: spray candidate URLs and infer from status codes.
- Reading a system's OWN config (e.g. our gateway's `networkconf`) is fine - that's reading
real data, not guessing endpoints. The "suspect" part is guessing unknown PATHS.
- Do not present probe results as "authoritative" over web-search findings; weight them at
least equally and reconcile.
- Corollary: the web-search bots being flaky is a real liability (see CT_THOUGHTS "Thought 2 -
web-search reliability MUST FIX"); when they fail, say so plainly rather than silently
falling back to guessing and calling it authoritative.
- Complements [[feedback_interview_ai_read_docs]].

View File

@@ -0,0 +1,42 @@
---
name: feedback_windows_quote_stripping
description: On Windows, embedded double-quotes in command args get stripped/mangled twice over — by PowerShell-invoked curl.exe (CommandLineToArgvW) and by the GuruRMM cmd shell layer. Build quoted args without literal embedded double-quotes.
metadata:
type: feedback
---
On Windows, **embedded double-quotes inside a command argument get silently
stripped or mangled** at two separate layers we hit repeatedly. The body of the
arg survives; the `"` characters vanish, so the receiving program sees broken
syntax (an undefined constant, a usage dump, a parse error) — never a clean error
that points at quoting.
Two confirmed failure layers:
- **`curl.exe` invoked from PowerShell** — Windows re-parses the process command
line via `CommandLineToArgvW`, which eats the inner `"` in
`--data-urlencode 'x="y"'`. A pfSense `diag_command.php` PHP body became
`echo PHPRUNS-OK` -> `echo PHPRUNS` -> "Undefined constant" (cost ~4 wasted RMM
round-trips). (Howard, 2026-06-16.)
- **GuruRMM `command_type:shell` (cmd.exe) layer** — `shutdown /r /t 60 /c "comment"`
had its `"comment"` quotes mangled through the agent's cmd layer; shutdown
rejected the args and dumped usage. Fix was to drop `/c` entirely. (2026-06-16.)
**Why:** It's the same root cause both times — Windows command-line re-tokenization
(`CommandLineToArgvW`) strips a layer of double-quotes that a Unix shell would have
preserved. PowerShell -> native exe, and RMM -> cmd.exe, each add a re-parse.
**How to apply:**
- Don't put **literal embedded double-quotes** inside an arg you pass through
PowerShell->curl.exe or RMM->cmd. Prefer single-quotes for the outer payload and
construct any needed `"` from `[char]34` (PowerShell) — keep the command on one
line.
- For JSON request bodies, use a **single-quoted heredoc** (`<<'JSON'`) with
`--data-binary @-` (per the Syncro/RMM skill rules) — that bypasses
command-line re-parsing entirely. This is the reliable path.
- If an arg with quotes is unavoidable, **drop the quoted part** (as with
`shutdown /c`) or move the value into a file/variable the program reads itself.
- Distinct-but-adjacent gotchas: non-ASCII chars in payload text also break on
Windows/Git-bash (see [[feedback_ascii_only_api_payloads]]); `/tmp` resolves
differently between Write and Git-bash (see [[feedback_tmp_path_windows]]);
PowerShell variable names are case-insensitive (see the errorlog `$gUid`/`$guid`
incident).

View File

@@ -0,0 +1,67 @@
---
name: gururmm-beast-windows-build-host
description: GURU-BEAST-ROG (i9-14900K) is the PRIMARY GuruRMM Windows build host (Pluto 172.16.3.36 = fallback). Reached from .30 via Tailscale-on-.30 at Beast's tailnet IP 100.101.122.4 as user guru. build-windows.sh does `attempt_build beast || attempt_build pluto`.
metadata:
type: reference
---
Set up 2026-06-12. **GURU-BEAST-ROG = PRIMARY Windows build host; Pluto (Administrator@172.16.3.36)
= FALLBACK.** `deploy/build-pipeline/build-windows.sh` selects via
`attempt_build beast || attempt_build pluto` — falls back if Beast is **unreachable/down OR its
build fails**.
## Parallel build (lever A, 2026-06-12) — ~5.6 min, was ~10-21 min
`run_remote_build()` parallelises the 8 variants across concurrent SSH sessions instead of one
serial `cmd /c` chain (the release profile is opt-level=z + lto=true + codegen-units=1, so each
variant's codegen/LTO is single-threaded — concurrency overlaps those tails). Beast: 24c/32t, 128 GB.
- **WAVE 1** (5 concurrent, stable toolchain): agent amd64 (`target/release`) + debug
(`target/debug-agent`) + x86 (`target/x86`), tray, cleanup.
- **WAVE 2** (2 concurrent, Rust 1.77): legacy amd64 (`target/legacy-x64`) + legacy x86
(`target/legacy-x86`). MSI (WiX) runs after wave 1, overlaps wave 2.
- **Two hard rules learned (both broke the build on BOTH hosts first try):**
1. **Every concurrent cargo needs its OWN `--target-dir`** — sharing one (e.g. amd64+x86 both on
`target/`) makes them block on cargo's per-build-dir lock and run serially ("Blocking waiting
for file lock on build directory"). `copy_artifacts()` paths must match the per-variant dirs.
2. **Do NOT pre-resolve the legacy lock with `cargo +1.77 fetch`/`generate-lockfile`** — a
full-graph resolve on 1.77 dies parsing a transitive `edition2024` dep (wit-bindgen),
`rc=101`. Just `move Cargo.lock aside` and let the two `cargo +1.77 build --features legacy`
invocations resolve scoped (no wit-bindgen); cargo's package-cache lock serialises their brief
resolve safely, then they compile in parallel. Restore the lock after.
Result: v0.6.66 built on Beast in **336s** (cargo phase 319s), all 8 artifacts signed + published
beta. vs Beast's first serial+cold build 622s and Pluto's 1269s.
## How .30 reaches Beast
- Beast is on Wi-Fi `10.2.51.228` (a DIFFERENT LAN than the .30 office 172.16.3.x) + tailnet
`100.101.122.4`. .30 (office) could NOT reach it via the pfSense subnet route — the pfSense
Tailscale **SNAT-subnet-routes is deliberately OFF** (so remotes see real LAN IPs), and the raw
172.16.x source didn't complete to Beast. **Fix: installed Tailscale ON .30** (node
`gururmm-server`/`100.86.12.15`, `tailscale up --accept-routes=false`) → reaches Beast
`100.101.122.4` peer-to-peer (DERP-relayed, ~50ms — fine for SSH-driven builds). No pfSense/ACL
changes. (Don't chase the subnet route again — Tailscale-on-.30 is the working path.)
- Build SSH user = **guru** (an admin; built-in Administrator is disabled). Pipeline path verified:
`root@.30 (/root/.ssh/id_ed25519) -> guru@100.101.122.4`. Host key pinned in
`/opt/gururmm/beast_known_hosts`. Both root's build key AND GURU-5070's key are in Beast's
`C:\ProgramData\ssh\administrators_authorized_keys` (ACL: Administrators+SYSTEM only).
## Beast build toolchain (under C:\Users\guru)
- Rust: stable + **1.77** toolchains, **i686-pc-windows-msvc** target for both; cargo/rustup in
`C:\Users\guru\.cargo\bin`. sccache 0.8.2 (`RUSTC_WRAPPER`, `SCCACHE_DIR=C:\sccache`).
- **MSVC 2022 Build Tools** (was already installed). dotnet, git present.
- **WiX 4.0.6** (`dotnet tool`, `C:\Users\guru\.dotnet\tools\wix.exe`) + extensions
`WixToolset.Util.wixext` + `WixToolset.UI.wixext` @ 4.0.6 (matches Pluto). Repo clone at
`C:\gururmm` (origin URL has the Gitea api-token embedded; credential.helper scrubbed local).
## Gotchas (these bit during setup)
- **WiX must be 4.x.** v6/v7 require accepting a paid OSMF EULA (`WIX7015`). Install pinned:
`dotnet tool install --global wix --version 4.0.6 --add-source https://api.nuget.org/v3/index.json`.
- **Beast NuGet had only the VS offline feed** — `dotnet tool install wix` AND `wix extension add`
failed until `dotnet nuget add source https://api.nuget.org/v3/index.json --name nuget.org`.
- **Wi-Fi is "Public" profile** so the stock sshd firewall rule (Private-only) blocked LAN SSH;
added rule `ACG-Build-SSH-22` (inbound 22, scoped LocalSubnet+172.16.0.0/12+100.64.0.0/10).
- **rustup hangs in a detached/no-console context** (Start-Process). The pipeline runs builds via
an SSH command (has a console) so it's fine; only background-launch validation stalled.
## Build user / RMM
- Beast agent id `5233d75b-f589-43c4-b96e-cfa75365a78d` (RMM). I bootstrapped SSH/firewall/toolchain
via `/rmm` (agent runs as SYSTEM = elevated) then over SSH (`guru@10.2.51.228` same-LAN from
GURU-5070, or `guru@100.101.122.4` over tailnet). Pluto build wiring unchanged. [[reference_pluto_build_server]]

View File

@@ -0,0 +1,36 @@
---
name: gururmm-install-report-failed-agent-v1
description: GuruRMM legacy-installer v1 must reuse /api/install-report AND create a visible "failed-install agent" server-side (Mike, 2026-06-12)
metadata:
type: project
---
For the SPEC-029 legacy-fleet build, Mike decided (2026-06-12) the observable-installer
requirement is satisfied by the EXISTING `install-report` channel, extended:
- **Reuse `/api/install-report`** (do NOT invent a new beacon). The MSI already POSTs rich
machine info + event/agent logs + service status there, success AND fail (`InstallReportCA` +
`installer/install-report.ps1``server/src/api/install_report.rs`, recorded to `install_reports`).
The **new NSIS 32-bit/legacy installer must POST the same payload** — this finally covers the
legacy tier (today it has no installer → zero install-reports = the biggest blind spot).
- **Failed-install agent IN v1 (Mike's call):** on a report indicating failure (service not Running
after poll / no enrollment / connect-verification failed), the server **upserts a visible
"failed-install" device record** — keyed by hostname + machine fingerprint (so retries update one
record, no spam), carrying machine info + failed-step/reason + log refs + attempt count. Shows in
the dashboard as FAILED-INSTALL (distinct from healthy agents), triage-able + alertable. **Reconcile**
if the box later enrolls for real (don't leave a ghost). Success reports don't create a failed agent
but still feed trend/near-fail analytics (failure-rate by OS/arch/version — build-shaping signal,
mirrors SPEC-022 §5e patch telemetry).
- Installer must **verify enroll/connect before declaring success** ("don't terminate until success")
and emit a meaningful exit + a local diagnostic bundle on fail.
Scope split: the running legacy-agent Coding Agent does the agent + NSIS installer (+ the install-report
POST). The **server-side failed-install-agent + trend analytics is a separate, sequential** work item
(can't run a 2nd agent in the same submodule checkout concurrently) → its own SPEC after the first
branch lands. See [[gururmm-log-analysis-claude-cutover]] for the server deploy shape.
**Note (Mike, 2026-06-12):** the legacy build must eventually be folded into the MAIN
production builds for **agent parity** (not a separate side-build). build-windows.sh already
emits legacy-x86/amd64 in WAVE 2, but the legacy INSTALLER + the SPEC-029 §12 fixes need to
become first-class in the promoted pipeline. For now, scoped TEST artifacts off the
`fix/legacy-32bit-agent` branch are fine (Mike OK'd) — productionize after the Win7 VM proof.

View File

@@ -0,0 +1,46 @@
---
name: gururmm-log-analysis-claude-cutover
description: GuruRMM log analysis cut over from Ollama-on-Beast to Claude Haiku 4.5; why, and the deploy shape
metadata:
type: project
---
GuruRMM server log analysis (`server/src/api/logs.rs`, `analyze_logs_with_*`) was
cut over from **Ollama (qwen3:14b on Beast, `100.101.122.4:11434`)** to the
**Anthropic API (Claude Haiku 4.5)** on 2026-06-12 (decision: Mike).
**Why — the "Ollama unreachable" error was a mislabeled timeout, not reachability.**
The GuruRMM server `.30` (gururmm, `172.16.3.30` — a **physical box**, Ubuntu 26.04;
the VM-on-Jupiter was retired and the physical server took over the `.30` IP) reaches
Beast fine for `/api/tags` and
short warm `/api/chat` (warm "say OK" = 1.1s), but a fleet-sized `/api/chat`
(~1500 log lines / ~17KB) never completes — it hit the curl 300s ceiling even warm.
Cause is qwen3:14b's minutes-long inference on a big prompt over a flaky cross-LAN
tailnet (`.30` is behind symmetric NAT — `MappingVariesByDestIP:true`; Beast is on
Wi-Fi `10.2.51.228`). reqwest's 120s timeout surfaced as
`error sending request ... Check Tailscale`, which read as "unreachable." Beast
also had a **duplicate-Ollama bind conflict** (the desktop tray app's `ollama serve`
couldn't bind 11434; the older standalone PID 14144 held `0.0.0.0:11434` and served)
— noisy but not the cause. See [[gururmm-beast-windows-build-host]] for Beast.
**The fix.** `analyze_logs_with_claude()` POSTs `https://api.anthropic.com/v1/messages`
with `x-api-key` from env, reading `ANTHROPIC_API_KEY` (required) and `ANTHROPIC_MODEL`
(default `claude-haiku-4-5`). Uses **structured outputs** (`output_config.format` +
json_schema) so the reply is guaranteed-parseable findings JSON (no fence stripping).
Cloud over plain HTTPS — no tailnet, no Beast. Validated end-to-end against Haiku
(200, ~1-6s, correct findings). `cargo check` clean.
**Secrets / privacy.** Key vaulted at `projects/gururmm/anthropic-api` (vault convention:
per-project key, mint its own). **ZDR requested from Anthropic, pending** — org-level,
not a console toggle (email sales@anthropic.com). Test fleet OK to run before ZDR
confirms; don't point a production fleet at it until ZDR is live.
**Deploy shape (DONE 2026-06-12).** Production server is a **native binary**
`/opt/gururmm/gururmm-server` via systemd, `EnvironmentFile=/opt/gururmm/.env`
(root-owned). A Gitea webhook → CI builds+ships the binary on push to gururmm `main`
(no cargo on `.30`). `guru` CAN do root ops via `sudo` with the password in vault
`infrastructure/gururmm-server` `credentials.password` (SSH via `~/.ssh/gururmm-physical`).
Shipped: gururmm `c869e4d` → CI redeployed the binary; `ANTHROPIC_API_KEY` appended to
`/opt/gururmm/.env`; `gururmm-server` restarted; `/api/logs/analyze` verified end-to-end
(1500 logs → 10 findings in 24s). **Migration note:** the key lives in `.30`'s local
`.env`, not the repo — already on the physical `.30`, so nothing to re-add.

View File

@@ -0,0 +1,84 @@
---
name: gururmm-physical-server-storage
description: Physical GuruRMM server (now IS 172.16.3.30) storage layout + hot/cold tiering; host migration COMPLETE 2026-06-11
metadata:
type: project
---
**MIGRATION COMPLETE (2026-06-11 ~07:20 MST).** The physical box now IS 172.16.3.30 and runs the
full stack: gururmm-server :3001, guruconnect :3002, coord/claudetools-api :8001, webhook :9000,
nginx :80, PostgreSQL 18, MariaDB 11.8, Grafana :3000, Prometheus :9090. Cred-decrypt verified
(MSP360 sync 62/0). Agents reconnected (162/212 within 15 min). SSH: `~/.ssh/gururmm-physical`
(alias `gururmm-new` -> .231 was the temp DHCP; box is now .30). sudo password = the vault `guru`
password, piped via `echo "$P" | sudo -S -p ""` (a bare `sudo -u postgres` with no prior sudo in
the SSH session fails with "a terminal is required").
**Cutover gotchas that bit us (see runbook):** (1) the box's nginx loaded a STALE config missing
`location /ws` -> agents got 404 on /ws -> `systemctl reload nginx` fixed it (always reload after
config placement). (2) Public ingress/TLS is **Nginx Proxy Manager on Jupiter 172.16.3.20**, NOT
local nginx (which is :80-only) -> NPM forwards to .30:80, no reconfig needed since .30 preserved.
(3) Prometheus TSDB WAL was copied mid-write -> `segments are not sequential` -> moved
`/var/lib/prometheus/metrics2/wal` aside (lost ~2h, blocks intact). (4) the `.30` IP swap used a
self-confirming detached netplan apply + a fresh `.47` mgmt IP (no stale-ARP baggage like `.30`);
the VM kept `.46` as an independent channel and released `.30`.
**Post-cutover DONE:** 7-day metrics/agent_logs backfill (2026-06-11) -- streamed VM->new box
direct (id-range filtered, .pgpass), 3.46M rows / ~3.4 GB in ~2.5 min, lossless (id-range counts
match VM<->new box: metrics 1,189,924; agent_logs 2,262,938). **Perf proof:** SSD sustained
186-214 MB/s writes, w_await 0.7-3.2 ms, fsync ~3 ms, peak %util ~65% (headroom), and ZERO
pool-timeouts under the bulk load + 212 live agents -- the rotational-VM WAL-fsync root cause is fixed.
**Workstream B DONE (2026-06-11):** jupiter-runner (act_runner v0.6.1, labels ubuntu-latest/22.04)
online on Jupiter .20 Docker; VM's gitea-runner DISABLED (kept registered for rollback). Build env
provisioned on the new box: source repo /home/guru/gururmm @ main 7c2f20e (rsync'd from VM, target/
+node_modules excluded), last-built-commit baselines copied, Rust 1.96.0 + Node v20.20.2/npm 10.8.2,
Pluto (Administrator@172.16.3.36) SSH auth OK for Windows builds. NOTE: gururmm has NO .gitea/workflows
-- builds run via the **webhook-handler path** (Gitea webhook http://172.16.3.30/webhook/build ->
nginx :80 /webhook/ -> :9000 -> build-*.sh on the server), NOT Gitea Actions. Pipeline wired end-to-end;
not yet exercised by a real build. **Post-cutover cleanup DONE (2026-06-12):** old VM `GuruRMM`
decommissioned after the soak — `virsh destroy`+`undefine`, `vdisk1.img` deleted, `.46` released;
`.47` mgmt IP dropped from the physical box's netplan (eno1 now carries only `172.16.3.30`). The
rollback anchor was intentionally retired; there is no longer a parked VM.
**History (pre-cutover — now DONE, retained for context).** The GuruRMM server/build-pipeline
ran on a **VM** at 172.16.3.30 (slow rotational-backed disk — the WAL-fsync pool-timeout cause)
and was migrated to a **physical box**, which took over the 172.16.3.30 IP at cutover
(2026-06-11). During provisioning (2026-06-10) the physical box was briefly at temp DHCP IP
**172.16.1.231**; that IP is no longer used. hostname `gururmm`, **Ubuntu 26.04 LTS**. SSH:
dedicated ed25519 key `~/.ssh/gururmm-physical` to `guru@172.16.3.30`, vault
`infrastructure/gururmm-server-physical` (SSH key + initial `guru` password). sudo needs that
password (`sudo -S`), not passwordless.
**Drives (storage optimized 2026-06-10):**
- **SSD `sda`** (Samsung 860, 929 GB) = HOT tier. Installer had left root at only 100 GB;
extended the LV into the full VG → **root is now ~915 GB**. Holds: OS, Postgres DEFAULT
tablespace (live/recent data) + WAL, cargo build targets, `/opt/gururmm`. Fast fsync here is
the real fix for the pool-timeout root cause (could even revert `synchronous_commit=on`).
- **HDD `sdb`** (WD 1 TB, spinning) = COLD tier. Old NTFS "Data2" (504 GB, user confirmed
already backed up) wiped → **ext4, mounted at `/data`** (fstab by UUID, `noatime`). Dirs:
`/data/gururmm/{pgcold, downloads, backups, archive}`.
**Cold-storage isolation (built at migration — needs PG running):**
- `CREATE TABLESPACE gururmm_cold LOCATION '/data/gururmm/pgcold'` (chown the dir
postgres:postgres first).
- Time-partition `agent_logs` (by month). Recent partitions on SSD default tablespace (hot
write path: the batched multi-row INSERT + heartbeats). Nightly job `ALTER TABLE
agent_logs_YYYYMM SET TABLESPACE gururmm_cold` ages old partitions onto the HDD (still
queryable for signatures/build-correlation). Past retention horizon: pg_dump partition to
`/data/gururmm/archive` (compressed) then DROP.
- `downloads` (build artifacts, served by nginx + written by pipeline) and `backups`
(nightly pg_dump) also live on `/data`.
This is the concrete answer to the deferred "#3 log retention/archival" discussion. See
[[rmm-agent-update-model]] (the downloads dir is the update artifact source) and the WAL-fix
context (synchronous_commit=off + pool→30 applied to the OLD VM).
**Migration architecture (ratified 2026-06-10, via a 2-round Gemini+Grok panel).** The VM
`172.16.3.30` is a kitchen-sink host (GuruRMM + GuruConnect + coord API :8001 + Gitea runner +
Grafana/Prometheus + MariaDB; PG 14, 5.4 GB gururmm DB). Decision: physical box **becomes
`172.16.3.30`** and runs **everything EXCEPT the Gitea runner** (which becomes a Docker container
on Jupiter `.20`); VM retired. (MariaDB MIGRATES — Gate-A found it backs the coord API's `claudetools`
DB at localhost:3306, NOT droppable.) Keeping `.30` + coord on physical means NO fleet-wide
re-point (the `http://172.16.3.30:8001` refs + Cloudflare→pfSense→.30 path are unchanged). PG via
`pg_dumpall --globals-only` + `pg_dump -Fc`/`pg_restore -j` (14→16, schema as-is — storage tiering
is a SEPARATE later task). Full runbook (Gate-A pre-flight, cutover from CONSOLE, ARP flush,
credential-decrypt gate, PONR=first-agent-reconnect, rollback): `projects/msp-tools/guru-rmm/docs/
HOST_MIGRATION_RUNBOOK.md`. EXECUTED and COMPLETE 2026-06-11 (see the top of this note).

View File

@@ -0,0 +1,37 @@
---
name: gururmm-session-logs-submodule-save
description: gururmm session-logs/docs live in the guru-rmm git submodule (not parent ClaudeTools); sync.sh won't commit submodule contents. GURU-5070 CAN push them directly over HTTP (Git Credential Manager) — `git push origin HEAD:main`; only the SSH path (git@…:2222) is blocked
metadata:
type: reference
---
`projects/msp-tools/guru-rmm` is a **git submodule** (gururmm repo, branch main). So gururmm
session logs (`projects/msp-tools/guru-rmm/session-logs/...`) and docs are tracked in the
**submodule**, not the parent ClaudeTools repo.
`/save` -> `sync.sh` commits/pushes the **parent** ClaudeTools repo only; it leaves submodule
**gitlinks unstaged** and NEVER commits submodule *contents*. So a session log written under the
submodule is left **uncommitted** by a normal `/save` — commit it inside the submodule yourself.
**GURU-5070 CAN push to gururmm directly (verified 2026-06-11).** The submodule's `origin` is the
**HTTP** remote `http://172.16.3.20:3000/azcomputerguru/gururmm.git`, and **Git Credential Manager**
has stored creds for that host (`git config credential.http://172.16.3.20:3000.provider generic`).
So from GURU-5070, in the submodule: `git add ...`, `git commit`, then
`GIT_TERMINAL_PROMPT=0 git push origin HEAD:refs/heads/main` (HEAD is usually **detached** on a
submodule — push `HEAD:main`, not bare `main`). `git fetch origin` first to confirm a clean
fast-forward (`git log --oneline origin/main..HEAD`). The old scp-to-new-box workaround is NO
LONGER NEEDED.
Only the **SSH** push path is blocked: `ssh -p 2222 git@172.16.3.20` -> Permission denied
(GURU-5070's key isn't authorized; the new box .30/.47 uses `gururmm-build-server`). Use HTTP.
**WARNING — pushing to gururmm main triggers the build webhook** (Pluto), which builds AGENTS and
publishes a new `-latest`. The fleet auto-updates. Server changes are NOT auto-deployed (deliberate
deploy). Order accordingly (agent-safe changes can ride the webhook; server/reaper/migration
changes deploy separately).
**After pushing:** advance the parent gitlink — `git -C <ClaudeTools> add projects/msp-tools/guru-rmm`,
commit, push ClaudeTools. (Do this AFTER the submodule push so the gitlink references a commit that
exists on the remote.) Also: a `sync.sh` run can `git checkout` the submodule back to the
gitlink-pinned commit, detaching from fresh local commits — advance the gitlink promptly so they're
pinned. See [[gururmm-physical-server-storage]].

View File

@@ -0,0 +1,23 @@
---
name: howard-home-lan-shadow
description: Howard-Home LAN is now 10.137.42.0/24 (renumbered 2026-06-16 off 192.168.0.0/24) — Cascades .0.x VPN shadow RESOLVED
metadata:
type: project
---
Howard-Home LAN = **10.137.42.0/24**, gateway+DNS **10.137.42.1** (a **UniFi OS gateway**, cert
CN=unifi.local — NOT pfSense). Howard renumbered it on **2026-06-16** (was 192.168.0.0/24).
**Why it was changed:** the old 192.168.0.0/24 **shadowed Cascades' 192.168.0.0/24** (Cascades pfSense
.0.1, NAS .0.120). With both on the same subnet, the OS preferred the directly-connected local /24, so
Cascades-VPN traffic to 192.168.0.x went to Howard's home UniFi instead of across the tunnel (Cascades
APs on 192.168.2.x/3.x worked; .0.x did not). A /32 route couldn't fix it because .0.1 was Howard's own
home gateway. Renumbering home to 10.137.42.0/24 frees 192.168.0.0/24 to route over the VPN.
**Status: RESOLVED.** From Howard-Home, 192.168.0.x (Cascades pfSense/NAS) should now route via the
Cascades VPN (confirm the .ovpn still pushes route 192.168.0.0/22). This removes the home-side blocker on
the pfSense compat-layer live validation — though that work is ALSO separately ON HOLD on the Cascades
pfSense being too old to install the RESTAPI package (see ROADMAP §E / [[MEMORY]]).
**How to apply:** Howard-Home is 10.137.42.x now — don't assume 192.168.0.x for this machine. If the
Cascades VPN still can't reach .0.x, check the ovpn route + that no other local interface re-collides.

View File

@@ -13,7 +13,7 @@ ACG office LAN is 172.16.0.0/22, routed via Tailscale through pfSense node `pfse
| pfSense | 172.16.0.1 | port 2248, user admin | Router, DNS (Unbound), Tailscale subnet router |
| Jupiter | 172.16.3.20 | port 22, user root | Unraid NAS — all VMs + Docker containers |
| Uranus | 172.16.3.21 | (no key) | OwnCloud additional storage only — NOT a proxy |
| GuruRMM VM | 172.16.3.30 | port 22, user guru | Linux VM on Jupiter — GuruRMM, Coord API, MariaDB, Gitea |
| GuruRMM | 172.16.3.30 | port 22, user guru | PHYSICAL box (Ubuntu 26.04) — took the .30 IP when the Jupiter VM was retired 2026-06-11; runs GuruRMM, Coord API, MariaDB/PostgreSQL. Old VM parked at .46 (rollback) |
| Pluto | 172.16.3.36 | (Windows) | Windows Server 2019 VM on Jupiter — MSI build server |
**Why:** How to apply: check these IPs before assuming what's where. .21 is NOT the Seafile proxy — NPM on .20 is.

View File

@@ -0,0 +1,33 @@
---
name: ix-whm-dns-api-access
description: IX cPanel/WHM API access uses the FULL-ACCESS-root 'ClaudeTools' API token (header auth), NOT the root password
metadata:
type: reference
---
All WHM API work on **IX** (`ix.azcomputerguru.com:2087`, the primary cPanel/WHM box,
public NS `ns1/ns2.acghosting.com` = `52.52.94.202`) — DNS zone edits and everything else —
authenticates with the **WHM API token** named **`ClaudeTools`**, used as a header, NOT the
root password. The token is **FULL-ACCESS ROOT** (capable of ALL WHM API actions, not
DNS-scoped) — treat it as a root credential.
**Working method:**
```
curl -4 -sk "https://ix.azcomputerguru.com:2087/json-api/<func>?api.version=1&..." \
-H "Authorization: whm root:$(bash "$CLAUDETOOLS_ROOT/.claude/scripts/vault.sh" get-field infrastructure/ix-server.sops.yaml credentials.whm-api-token)"
```
**Why (the trap that burned ~an hour on 2026-06-12):** the legacy `/json-api/` path with
**basic-auth password** (`-u root:<password>`) now returns `HTTP 403 Forbidden Access
denied` (a `cpanelresult` JSON, denied **pre-auth** — bad creds give the same 403). It is
NOT cPHulk (disabled) and NOT an Imunify IP block (the WHM login page `/:2087/` returns 200
from the same IP; whitelisting the IP does nothing). cpsrvd/Imunify simply rejects
password-based scripted `json-api` access; the API token is the supported client.
**Token location:** vault `infrastructure/ix-server.sops.yaml``credentials.whm-api-token`
(also documented in that entry's plaintext `notes`). `credentials.password` is still the
real root password but DOES NOT work for the API — leave it for SSH/console only.
Common funcs: `dumpzone` (read), `addzonerecord` / `editzonerecord` / `removezonerecord`
(write; cPanel auto-bumps SOA serial + cluster-syncs to the public NS), `synczone`
(force cluster push). Force IPv4 (`curl -4`) for a stable egress IP. Related: [[neptune-exchange-mail-hosting]].

View File

@@ -0,0 +1,28 @@
---
name: project_ai_auth_product_boundary
description: Which ACG products may use Claude subscription OAuth vs must use customer/API-key auth, and why (Anthropic ToS)
metadata:
type: project
---
Firm product/AI-auth boundary Mike set 2026-06-14, decided by Anthropic's Agent SDK ToS
(third-party products may NOT offer claude.ai login / subscription rate limits without
written approval):
- **ClaudeTools harness (incl. a future web "ClaudeTools 3.0" co-work app)** —
ALWAYS internal-only, internal users only. May use per-person Claude subscription OAuth
(`CLAUDE_CODE_OAUTH_TOKEN` via `claude setup-token`). Compliant pattern = each node/workstation
authenticates with THAT person's own token (Mike's on Mike's boxes, Howard's on his); the hub
never centralizes one subscription to serve many. Note: as of 2026-06-15 subscription-backed
Agent SDK usage draws on a separate monthly credit pool (~$100-200 Max), not unlimited — spill
to API key when exhausted.
- **GuruRMM** — SELLABLE product. AI features must use the CUSTOMER's own dropped-in API key
(per-tenant `ANTHROPIC_API_KEY`), never ACG's subscription. Build RMM AI auth around BYO-key
from day one.
- **Backend dev work (e.g. GuruRMM log-analysis dev side)** — always internal.
**Why:** subscription OAuth is fine for internal/own use but ToS-banned for offering to external
users; getting this wrong on a sellable product is a hard compliance line, not grey.
**How to apply:** ClaudeTools 3.0 web design assumes per-person subscription tokens on each node
(internal). Anything customer-facing (GuruRMM) = customer-provided API keys. See
[[feedback_stream_of_thought_design]] for the design-partner posture on this early-stage vision.

View File

@@ -0,0 +1,16 @@
---
name: project_cascades_isolated_vlan_pattern
description: Cascades pfSense — the only isolated-VLAN template is the GUEST VLAN (VLAN50/igc1.50); VLAN20 is NOT isolated; verify with pfctl -sr not config.xml
metadata:
type: project
---
On the Cascades pfSense (`192.168.0.1`, Plus 25.07), the **template for an isolated VLAN is the GUEST VLAN (VLAN 50 / `igc1.50`)** — four `quick`, **Protocol=Any** interface rules: block -> `192.168.0.0/22`, block -> `10.0.0.0/8`, block -> `172.16.0.0/12`, then pass -> `any`; DHCP hands out **public DNS `8.8.8.8, 1.1.1.1`** (DNS resolves over the internet egress, NOT to the firewall — the 10.0.0.0/8 block would kill firewall DNS). No `RFC1918` alias exists; isolation uses literal CIDRs.
**VLAN 20 (Internal / `igc1.20`) is NOT isolated** — its only user rule is `opt238net -> lan`; all other traffic (incl. to internal) rides a floating `pass inet all` catch-all. Do not use VLAN 20 as an isolation template.
**Two traps, both burned time 2026-06-17:**
1. **config.xml lies** — it showed RFC1918 block rules on a friendly "opt239" that are NOT in the enforced ruleset (friendly-name/macro offset + inactive rules). **Always verify against the live enforced ruleset: `pfctl -sr | grep igc1.<vlan>`**, never trust the config-file rule dump alone.
2. **Protocol=Any is mandatory** on the block rules. A GUI build that sets Protocol=TCP leaves UDP (SIP/RTP/DNS) un-blocked to internal — it leaks via the floating `pass inet all`. pf prints port 53 as `domain`, not `53`.
**VOICE VLAN 30 (`igc1.30`/opt241, `10.0.30.0/24`)** was built 2026-06-17 to this exact pattern (cloud-PBX phones + Vertical LogMeIn desktop, HIPAA isolation). Scripted changes go via the pfSense PHP config API (`require config.inc; write_config(); filter_configure(); services_dhcpd_configure()`) — supported path, not config.xml surgery. Full runbook: `clients/cascades-tucson/docs/network/voice-vlan-cutover.md`. See [[howard-home-lan-shadow]] for VPN reach.

View File

@@ -0,0 +1,18 @@
---
name: project_cascades_kpi_dashboard
description: Cascades KPI-dashboard request (Ashley Jensen) — parked scoping notes; Power BI Gateway is wrong frame; Tier1→Tier2 path
metadata:
type: project
---
Ashley Jensen (Cascades accountant) wants a single dashboard pulling KPIs from all their **reporting** SaaS: ALIS (clinical EHR), QuickBooks, Bill.com, Relias, You've Got Leads, TELS, Focus HR, Helpany (app.safe-living.com), a POS. She asked about using the **Power BI Gateway** for it.
**Key correction:** the Power BI on-prem data gateway is the WRONG frame — it only bridges Power BI to on-prem sources (SQL/file/DB inside the building), NOT cloud SaaS. The real work is per-system data extraction + a landing store, then Power BI on top.
**Recommended path (Tier 1 → Tier 2):** scheduled CSV/Excel exports land in **SharePoint****Power BI Pro** dashboard (proves value, ~zero new cost since they're on M365 Business Premium). Phase 2: automate the API-capable systems (Bill.com, QuickBooks Online) with **Power Automate**. Niche senior-living apps (ALIS/TELS/You've Got Leads/Helpany) won't have ready connectors in Tier3/4 tools (Fivetran/Databox/etc.), so exports are the backbone regardless. SharePoint = storage layer, not an integration engine.
**Open items before proposing:** which KPIs day one, data freshness needed, per-system API/export availability, which POS + which Focus HR plan. Also check whether ALIS (Medtelligent) offers a built-in analytics/BI add-on or data feed — could replace plumbing for their top source. **HIPAA:** BAA required before any ALIS PHI leaves it.
**Status:** parked. Next action = draft client-facing one-pager (realistic path + cheap Phase 1: census + financial KPIs) for Ashley to approve.
Full notes: `clients/cascades-tucson/docs/proposals/kpi-dashboard.md`. Client context: [[project_cascades]].

View File

@@ -0,0 +1,25 @@
---
name: python3-shim-use-python
description: On GURU-5070, `python3` in Git bash resolves to the flaky MS Store shim (errors with "run without arguments to install from the Microsoft Store"). Use `python` (real 3.12.10) or `py` (3.14.5) instead — affects coord.py, wiki-compile, any python tooling.
metadata:
type: reference
---
On **GURU-5070** (verified 2026-06-11), invoking `python3` from the **Bash/Git-bash** tool
hits the **Microsoft Store app-execution-alias shim**
(`~/AppData/Local/Microsoft/WindowsApps/python3.exe`), which can error with
`Python was not found; run without arguments to install from the Microsoft Store`. So
`PY=$(command -v python3 || command -v python)` picks the SHIM first (it exists as a file,
so `command -v` succeeds) and breaks.
**Real interpreters that work** (both from Bash and PowerShell):
- `python` -> 3.12.10 (`~/AppData/Local/Programs/Python/Python312/python.exe`)
- `py` -> 3.14.5 (the Windows launcher; `py -0p` lists all)
**Fix / how to apply:** when a skill or script needs Python on this box, run **`python`**
(or `py`), NEVER `python3`. This affects the `coord` skill
(`.claude/skills/coord/scripts/coord.py` — verified working via `python`, reaches the live
coord API at 172.16.3.30:8001) and `/wiki-compile` (which hardcodes `python3 -c "import
urllib..."` for URL-encoding and `command -v python3`). When a skill hardcodes `python3`,
substitute `python`. The coord per-article lock IS claimable here — do not skip it as
"no local Python". Related: [[gururmm-session-logs-submodule-save]].

View File

@@ -0,0 +1,49 @@
---
name: reference_aadconnect_keycredlink_writeback
description: Diagnose/fix AAD Connect "completed-export-errors" — msDS-KeyCredentialLink writeback (8344 INSUFF_ACCESS_RIGHTS) blocked by AdminSDHolder on protected accounts
metadata:
type: reference
---
AAD/Entra Connect AD-connector Export shows **completed-export-errors** every cycle while
AAD export + imports + sync all succeed, and one privileged account (Domain/Enterprise/Schema
Admins, `adminCount=1`) won't update in the cloud → it's almost always the **msDS-KeyCredentialLink
writeback** (Windows Hello for Business / passwordless key) being **denied by AdminSDHolder**.
The connector account (`MSOL_xxxx`) has key-writeback rights inherited on the OU, but SDProp
strips inheritance on protected objects → LDAP error **8344 / problem 4003 INSUFF_ACCESS_RIGHTS
{ msDS-KeyCredentialLink }**.
**Diagnose (all read-only, run on the Connect server; it's often the DC):**
- `Get-ADSyncScheduler` (rule out StagingModeEnabled), `Get-Service ADSync`
- `Get-ADSyncRunProfileResult -ConnectorId <id> -NumberRequested 8 | select RunProfileName,Result,StartDate`
— note WHICH connector errors (AD = writeback, not the AAD/cloud direction). NOTE: `-RunProfileName`
param and `.RunStepResults` detail are NOT available on older builds; event log id 6100 only gives counts.
- Real error detail comes from **csexport errors-only**:
`& "$env:ProgramFiles\Microsoft Azure AD Sync\Bin\csexport.exe" "<AD-connector-name>" out.xml /f:x`
then read `//cs-object``export-errordetail/@error-type` + `cd-error/error-literal` + the failing attr.
**Fix (grant the one attribute on AdminSDHolder so SDProp propagates to all protected accounts):**
```
dsacls "CN=AdminSDHolder,CN=System,DC=<dom>,DC=com" /G "<NETBIOS>\MSOL_xxxx:WP;msDS-KeyCredentialLink"
# force SDProp now: rootDSE RunProtectAdminGroupsTask=1 ; then Start-ADSyncSyncCycle -PolicyType Delta
```
`dsacls /G` is additive (one attribute-scoped ACE, removes nothing). Verify ACE landed:
`dsacls "<protected-user-DN>"` shows `Allow <dom>\MSOL_xxxx SPECIAL ACCESS for msDS-KeyCredentialLink`,
then csexport `/f:x` returns **0 errored cs-objects** and the next AD Export = success.
Same fault hits OTHER writeback attributes the same way — any attribute AAD Connect writes back to
a protected on-prem object. Confirmed attributes: **msDS-KeyCredentialLink** (WHfB/passwordless key)
and **msExchSafeSendersHash** (Exchange hybrid safe-senders writeback). Fix is identical; just swap
the attribute name in the dsacls grant. A persistent single-object `completed-export-errors` on the
AD connector with `permission-issue`/8344 on a Domain/Enterprise/Schema-Admin account = this pattern.
Instances fixed (all via /rmm as SYSTEM on the client DC — RMM agent on the Entra Connect box):
- 2026-06-16 **RUSSO-SRV** (Russo Law, rrs-law.com) — `guru@rrs-law.com`, msDS-KeyCredentialLink,
since 2025-05-07 (17.7k retries). The Microsoft "sync error" email = Entra Connect Health flagging it.
- 2026-06-16 **GTI-INV-DC** (Glaztech, glaztech.com — Connect is on GTI-INV-DC not DC1) — `seastman`
(Steve Eastman, their IT lead), msExchSafeSendersHash, since 2025-08-28 (13.9k retries).
NOT this pattern (different fix): a cloud-side `onPremisesProvisioningErrors` PropertyConflict
(duplicate UPN/proxyAddress) — that's a directory data collision needing a who-owns-the-address
decision, not a permission grant. (Glaztech also had one: CAS@glaztech.com on both the `CAS` user's
UPN and an alias on `alex` → CAS1944 parked on onmicrosoft UPN.) Related: [[reference_gururmm]].

View File

@@ -0,0 +1,12 @@
---
name: reference_antigravity_agy_not_headless
description: Antigravity CLI agy.exe is the IDE embedded agent (no stdout, SQLite store) — NOT a headless CLI. The agy skill uses @google/gemini-cli, not agy.exe. Don't reinstall agy.exe expecting a headless tool.
metadata:
type: reference
---
The `agy.exe` installed by Google's Antigravity CLI (`%LOCALAPPDATA%\agy\bin\agy.exe`, installer `https://antigravity.google/cli/install.ps1`) is the IDE's embedded agent, **NOT a usable headless CLI** on this fleet. Even v1.0.6's advertised `-p/--print` produces ZERO stdout and hangs when invoked non-interactively from the Bash/PowerShell tool harness — it writes only to a SQLite conversation store. First found 2026-06-05 (`session-logs/2026-06-05-mike-gururmm-platform-day.md` line 35); **re-confirmed 2026-06-06** after the GURU-5070 reinstall (reinstalled agy.exe and walked straight back into the same no-output/hang symptom).
The `agy` SKILL (despite the name) routes to the official **`@google/gemini-cli`** (`gemini`, npm global) — that IS the real headless second-opinion tool (Google OAuth, no API key), resolved via `identity.json .gemini.binary`. Grok (`ask-grok.sh`) is the other working second model. Both were verified returning `OK` on 2026-06-06.
**June 18 sunset — likely a non-issue for ACG.** Google is sunsetting gemini-cli's free/unpaid OAuth quota on **2026-06-18**, but Mike has a **paid Gemini account**, so the plan is to **stay on gemini-cli** (do NOT migrate to Antigravity). The bulletproof form is to auth gemini-cli with a paid **Gemini API key** (`GEMINI_API_KEY`) rather than the free OAuth quota — that path is unaffected by the OAuth-CLI sunset regardless of how the consumer tiers shake out, and is more stable for headless use. (Sources disagree on whether paid Pro/Ultra OAuth is also cut, so the API-key path is the safe bet.) **Do NOT reinstall agy.exe expecting it to work headless.** Related: [[feedback_agy_review_not_readonly]].

View File

@@ -0,0 +1,12 @@
---
name: reference_backblaze_storage_rate
description: ACG's Backblaze B2 storage cost rate ($0.00695/GB) for the GuruRMM mspbackups storage-cost calculation
metadata:
type: reference
---
ACG's Backblaze B2 storage rate is **$0.00695 per GB**. Use this as the cost input when calculating client storage cost in the GuruRMM **mspbackups** (MSP360) ability.
- Cost = stored_GB x 0.00695 (USD).
- This is ACG's cost basis; client-facing markup/billing is a separate decision, not this figure.
- The B2 storage-management credential is the vault entry `projects/claudetools/backblaze-b2.sops.yaml` (key name "ClaudeTools", manages buckets/keys for the mspbackups feature).

View File

@@ -0,0 +1,18 @@
---
name: Cascades Folder Redirection GPO — DOA root cause + fix (misnamed fdeploy)
description: Why native Folder Redirection failed on EVERY Cascades machine (LE + staff) and forced the per-user registry workaround — the GPO's redirect targets were saved in a misnamed fdeploy1.ini; Windows only reads fdeploy.ini. Fixed 2026-06-08. Read when touching Cascades folder redirection or onboarding a new Cascades user.
metadata:
type: reference
---
**Root cause (found 2026-06-08):** Native Folder Redirection never worked at Cascades — every machine needed `fix-shell-redirect.ps1`. The FR GPO `CSC - Folder Redirection` (`{512B43A4-F049-4CE5-BFAC-860AD13E92BE}`) had its redirect targets in a file named **`fdeploy1.ini`**, but the Windows FR client-side extension reads **`fdeploy.ini`** only. No `fdeploy.ini` existed → the client knew which 5 folders to redirect but got an **empty target path** (FR Operational log event 1006 shows `Path = ""`, and there is NO event 1008 "successfully redirected"). It silently no-op'd. The GPO had been hand-built by editing the wrong filename.
**Fix:** wrote a correct `fdeploy.ini` (5 folders, `Flags=187`, `FullPath=\\CS-SERVER\Homes\%USERNAME%\<Folder>`) into `{512B43A4-...}\User\Documents & Settings\`, then bumped the GPO version 917506→983042 keeping **GPT.INI Version AND the AD `versionNumber` attribute in sync** (FR is a foreground/logon CSE; it only re-applies when the version changes). Canonical artifact: `clients/cascades-tucson/gpo/fdeploy.ini`. Backup of original `\User` tree + GPT.INI: `C:\Windows\Temp\frfix-20260608-161144` on CS-SERVER.
**How to apply / diagnose elsewhere:**
- Diagnose: on the client, `Get-WinEvent -LogName 'Microsoft-Windows-Folder Redirection/Operational'``Path = ""` in event 1006 + no 1008 = the GPO is delivering no target path (missing/empty/misnamed `fdeploy.ini`).
- The dead `fdeploy1.ini` was LEFT in place (Windows ignores it) — do NOT edit it. Edit redirection via GPMC, or replace `fdeploy.ini` from the repo artifact.
- The **LE GPO** `CSC - Folder Redirection (LE)` (`{889BE7BE-...}`) is also broken — `\User` tree completely empty. Retire it / move LE users into SG-FolderRedirect, or apply the same fix.
- After the fix, the per-user registry workaround should no longer be needed; native FR redirects all 5 folders on first logon. Still pre-create the home folder (`New-HomeFolder`) before first logon. See [[feedback_cascades]].
**Also (2026-06-08):** CS-SERVER live GuruRMM agent re-enrolled to `c39f1de7-d5b6-45ae-b132-e06977ab1713` (old `6766e973` is stale) — always resolve the agent live by hostname, never hardcode. Related: [[project_cascades]].

View File

@@ -0,0 +1,38 @@
---
name: reference_cdp_chrome_driver
description: Drive Chrome via CDP (debugger) with on-disk screenshots; how Gemini/Grok "see" the live site
metadata:
type: reference
---
`.claude/scripts/cdp.py` drives Chrome over the **Chrome DevTools Protocol** (same approach
Antigravity uses) — fixing two problems the claude-in-chrome MCP extension had: invisible windows,
and screenshots that never landed on disk.
**Why it matters:** CDP `Page.captureScreenshot` returns the PNG bytes, so cdp.py writes a **real
PNG file** → which can be fed to `agy image-analyze` (Gemini) or Grok. That is how Gemini/Grok
"look at the live site" (verified 2026-06-05: Gemini correctly read a CDP screenshot of the GuruRMM
login). The MCP extension's `save_to_disk` never produced a findable file.
**Setup (one-time per session):**
- `py -m pip install websocket-client` (uses stdlib `urllib` + `websocket-client`; no Playwright/Node).
- `py .claude/scripts/cdp.py launch [url]` — opens a **visible** Chrome on a **dedicated profile**
(`~/.claude/cdp-chrome-profile`) with `--remote-debugging-port=9222`. Dedicated profile = NOT logged
in; the user signs into authenticated apps once (Claude still must NOT type passwords — that rule
holds regardless of CDP).
**Gotchas:**
- Chrome's DNS-rebinding guard rejects `Host: 127.0.0.1` on the debug endpoint → **use `localhost`**
(cdp.py BASE is `http://localhost:9222`). Launch also passes `--remote-allow-origins=*`.
- Launching `chrome.exe` while Chrome runs on the SAME profile just opens a tab in the existing
instance (flags ignored). The dedicated `--user-data-dir` forces a real new instance with the port.
**Commands:** `launch [url]` · `status` · `nav <url> [tabid]` · `shot <out.png> [tabid]` ·
`click <x> <y>` · `type <text>` · `key <Key>` · `eval <js>`. Stateless (new WS per command).
**Letting Gemini/Grok DRIVE (not just see):** cdp.py is a plain CLI, so Grok's `run_terminal_command`
(or any agent with shell access) could call it to navigate/click. **Security caveat:** a debug Chrome
on :9222 is controllable by any local process, and if it holds authenticated sessions (M365, Syncro,
RMM) those are driveable by whatever drives it — including external-vendor CLIs. Safer model: **Claude
drives cdp.py; Gemini/Grok receive the on-disk screenshots.** Only expose direct driving to an
external CLI deliberately. See [[reference_gururmm]].

View File

@@ -0,0 +1,14 @@
---
name: reference-cloudflare-access
description: Where the Cloudflare API credentials live (SOPS vault) — azcomputerguru.com DNS is on Cloudflare, not the IX nameservers
metadata:
type: reference
---
Cloudflare API access is in the SOPS vault at **`services/cloudflare.sops.yaml`** (account "Mike@azcomputerguru.com Account", account_id `44594c346617d918bd3302a00b07e122`). Fields under `credentials`:
- `api_token_full_account` — full-account token (`solitary-rain-773d`, added 2026-05-10, expires 2027-05-10)
- `api_token_full_dns` — full DNS-edit token (use this for DNS record changes)
- `api_token_legacy` — legacy token
- `zone_id_azcomputerguru` = `1beb9917c22b54be32e5215df2c227ce`
**azcomputerguru.com DNS is hosted on Cloudflare** (ns mckinley/amir.ns.cloudflare.com), NOT the IX/cPanel nameservers (ns1/ns2.acghosting.com) that most CLIENT domains use. So azcomputerguru.com zone edits go through the Cloudflare API, not `whmapi1`. Pattern: `curl -H "Authorization: Bearer <api_token_full_dns>" https://api.cloudflare.com/client/v4/zones/<zone_id>/dns_records`. (Used 2026-06-15 to add the cross-domain DMARC report-authorization record `cryoweave.com._report._dmarc.azcomputerguru.com TXT "v=DMARC1;"` so client DMARC reports can be sent to rua@azcomputerguru.com.) See [[reference_ix_server_access]] for client-domain DNS (cPanel).

View File

@@ -0,0 +1,37 @@
---
name: reference_ff_firefox_driver
description: Drive Firefox via Playwright (.claude/scripts/ff.py) — Mike's preferred browser; replaces the disliked claude-in-chrome extension
metadata:
type: reference
---
`.claude/scripts/ff.py` drives **Firefox** over Playwright — the Firefox sibling of
[[reference_cdp_chrome_driver]]. Mike dislikes Chrome and the `claude-in-chrome` MCP
extension, so when he asks to "look at a website / interact / collect the logs", use this,
not Chrome. (The Chrome connector was disabled 2026-06-06: keys `claudeInChromeDefaultEnabled`,
`cachedChromeExtensionInstalled` set false and `chromeExtension` pairing removed in
`~/.claude.json`; backup at `~/.claude.json.bak-prechrome`. Re-toggle in the connectors UI if it
reappears.)
**Why a daemon, not stateless like cdp.py:** Firefox dropped most CDP support, so cdp.py's
"new WS per command" trick doesn't port. `ff.py launch` spawns a background daemon holding ONE
Playwright Firefox page on a **persistent profile** (`~/.claude/ff-profile`, logins survive);
every other subcommand is a thin HTTP client to it on `localhost:9333` (env `FF_PORT`). The page
persists between calls (nav now, shot later) and the daemon accumulates console + network logs.
**Commands:** `launch [url] [--headless]` · `status` · `nav <url>` · `shot <out.png>` (real PNG to
disk → feed to `agy image-analyze`/Grok) · `click <x> <y>` · `type <text>` · `key <Key>` ·
`eval <js>` · `console [--clear]` · `network [--clear]` · `stop`. Default headed (visible) so Mike
can log into authenticated apps once; Claude still must NOT type passwords.
**Gotchas (both bit during build, 2026-06-06):**
- **`py` honors a script's shebang.** ff.py's `#!/usr/bin/env python` makes `py ff.py` resolve
`python` via PATH → **Python 3.12**, while bare `py -c` uses the default **3.14**. Playwright is
installed in BOTH now (`<py312>\python.exe -m pip install playwright` + `... -m playwright install
firefox`), so it's interpreter-agnostic. If `ModuleNotFoundError: playwright` recurs after a
Python upgrade, install playwright into whatever `py .claude/scripts/ff.py status` actually runs.
- The detached daemon's stdio is redirected to `~/.claude/ff-daemon.log` (NOT inherited) — otherwise
`launch` never returns control and startup crashes are invisible. Check that log if `launch` hangs.
Verified end-to-end 2026-06-06: launch→status→eval→shot (26KB real render of example.com)→network
(200 captured)→console (caught an injected log). See [[reference_cdp_chrome_driver]].

View File

@@ -0,0 +1,25 @@
---
name: reference_gururmm_command_type
description: GuruRMM agent only accepts specific command_type values; an unknown type is silently dropped (looks like a black-hole)
metadata:
type: reference
---
GuruRMM agent `CommandType` (agent/src/transport/mod.rs) accepts ONLY: `shell`,
`powershell`, `python`, `script`, `claude_task` — plus alias `cmd` → shell
(added 2026-06-12). On Windows: `powershell` runs powershell.exe (UTF-8 output
fixed in-agent, so the old "-OutputEncoding not recognized" quirk is gone);
`shell`/`cmd` runs cmd.exe.
A command with an UNKNOWN `command_type` fails the agent's whole-message serde
parse; pre-2026-06-12 the error was logged-and-ignored and the command was
**silently dropped — no ack, no result** — indistinguishable from a NAT/proxy
black-hole. On 2026-06-12 a `command_type:"cmd"` (no variant then) caused a long
mis-diagnosis (7 multi-AI rounds, packet captures, a pfSense SNAT change) of
"PST agents can't receive commands" — the agents ran `powershell` commands fine
the whole time. The agent now also NAKs an unparseable command (CommandAck +
error CommandResult) so it fails fast instead of black-holing.
**How to apply:** When a dispatched command sits un-acked/never-completes,
FIRST verify `command_type` is one of the valid values before chasing the
network/proxy. Never send a made-up type. See [[reference_gururmm]].

View File

@@ -1,25 +1,27 @@
---
name: IX server access — network + SSH
description: How to reach ix.azcomputerguru.com (172.16.3.10) — Tailscale-on means it's directly reachable, no separate VPN. SSH currently uses sshpass with the root password (key auth was never set up after GURU-5070 was reinstalled to Windows 11). Setting up key auth would simplify this.
description: How to reach ix.azcomputerguru.com (172.16.3.10) — Tailscale-on means it's directly reachable, no separate VPN. SSH KEY AUTH from GURU-5070 now works (verified 2026-06-05); sshpass+password is only the fallback. Also enrolled in GuruRMM (gururmm-agent.service). Full inventory: wiki/systems/ix-server.md.
type: reference
---
## Network reachability
- **Host:** `ix.azcomputerguru.com` / `172.16.3.10`
- **Access:** directly reachable when Tailscale is on. No separate VPN connection required.
- **Host:** `ix.azcomputerguru.com` / `172.16.3.10` (also `172.16.1.39`)
- **Access:** directly reachable when Tailscale is on. No separate VPN connection required. External `72.194.62.5:22` is firewalled — internal only.
- **Also enrolled in GuruRMM** (`gururmm-agent.service`, binary `/usr/local/bin/gururmm-agent`, config `/etc/gururmm/agent.toml`) — drivable via `/rmm` when SSH isn't handy.
## SSH
> **VERIFY 2026-05-26** — the no-key-auth note was written under the old CachyOS install on GURU-5070; the machine is now Windows 11. Re-confirm whether key auth got set up before relying on the sshpass fallback below.
- **User:** `root`
- **Password:** vault — see `credentials.md` or SOPS.
- **SSH key auth:** NOT configured from GURU-5070 (the old `guru@wsl` key was authorized but the workstation was reinstalled; new pubkey hasn't been added to IX's `authorized_keys` yet).
- **Current workflow (sshpass):**
- **SSH key auth: WORKS from GURU-5070** (verified 2026-06-05 via system OpenSSH, internal IP, Tailscale up):
```bash
/c/Windows/System32/OpenSSH/ssh.exe -o BatchMode=yes root@172.16.3.10 'whmapi1 listaccts'
```
- **Password fallback:** vault `infrastructure/ix-server.sops.yaml` (root password). Use sshpass only if key auth ever breaks:
```bash
sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no -o PubkeyAuthentication=no root@172.16.3.10
```
- **Suppress sshpass warnings:** pipe through `grep -v WARNING | grep -v 'not using'` or `tail`.
- **Account-level (`gurushow`) paths from scripts:** paramiko with `look_for_keys=False, allow_agent=False` (that account's key auth is disabled).
**Recommended:** add GURU-5070's pubkey to IX's `~/.ssh/authorized_keys` to drop the sshpass dance.
## What's on it
Full systems inventory (host specs, web/mail/DB stack versions, 72 cPanel accounts → domains → disk, ACG subdomain docroots, backup gap) is documented in **`wiki/systems/ix-server.md`** (live SSH inventory 2026-06-05). cPanel 134, CloudLinux 9.7, 64-core Xeon, 4.4 T /home. [[reference_radio_website]] is hosted here.

View File

@@ -43,7 +43,7 @@ Native Windows MSVC builds — produces `.exe` with no MinGW runtime dependency.
- GuruRMM Windows agent variants (amd64, x86, legacy, debug) and MSI packaging
- Anything using Windows-only APIs or needing `signtool` signing
**Note:** Routine GuruRMM agent builds are automated on the Linux server (172.16.3.30) via MinGW + jsign. Use Pluto for MSVC-specific builds or one-off tooling.
**Note:** Routine GuruRMM Windows agent builds run via `build-windows.sh` (MSVC + WiX + jsign) with **Beast (GURU-BEAST-ROG, tailnet 100.101.122.4) PRIMARY and Pluto the FALLBACK**`attempt_build beast || attempt_build pluto`. Pluto is **no longer the primary GuruRMM build host**; it's the fallback path, plus GuruConnect's Gitea-runner builds, MSVC-specific builds, and one-off tooling. See [[gururmm-beast-windows-build-host]].
## Directory Layout

View File

@@ -7,12 +7,14 @@ type: reference
## Radio Show Website
- **URL:** https://radio.azcomputerguru.com
- **Platform:** Astro 6.0.4 (static site generator)
- **Platform:** Astro 6.0.4 (`output: 'static'`) with **React 19 islands** (`@astrojs/react`), MDX, sitemap, RSS; `wavesurfer.js` (episode audio) + `fuse.js` (client search). Node >= 22.12.0.
- **Server:** IX server (172.16.3.10), cPanel account `azcomputerguru`
- **Document Root:** `/home/azcomputerguru/public_html/radio`
- **Source Code:** `projects/radio-show/website/` in ClaudeTools repo
- **Source Code:** `projects/radio-show/website/` in ClaudeTools repo (server holds only built `dist/`)
- **Content:** Markdown/MDX collections at `src/content/episodes/` and `src/content/blog/`
- **Build:** `cd projects/radio-show/website && npm run build` produces `dist/` folder
- **Deploy:** rsync/SCP `dist/` contents to document root on IX server
- **Full infra record:** `wiki/systems/ix-server.md`. human-flow can AST-scan the `.tsx` islands under `src/components`, not the `.astro` pages.
### Community Link
- The community page (`/community`) links to:

View File

@@ -36,7 +36,7 @@ type: reference
- Detail: [[infra_office_network]].
### gururmm-server (172.16.3.30, hostname `gururmm`)
- **What:** Linux VM on Jupiter. THE workhorse — runs MariaDB, PostgreSQL, ClaudeTools API (`:8001`), GuruRMM API (`:3001`), GuruConnect server (`:3002`), coord API, Gitea Actions runner, build pipeline, webhook.
- **What:** PHYSICAL box (Ubuntu 26.04), NOT a VM — took the .30 IP when the Jupiter VM was retired 2026-06-11 (old VM parked at 172.16.3.46 as rollback). THE workhorse — runs MariaDB, PostgreSQL, ClaudeTools API (`:8001`), GuruRMM API (`:3001`), GuruConnect server (`:3002`), coord API, Gitea Actions runner, build pipeline, webhook.
- **Default:** `ssh guru@172.16.3.30`. Password `infrastructure/gururmm-server.sops.yaml` `credentials.password`. User is **`guru`** NOT `mike`. Home `/home/guru/`.
- **Gotcha:** for cargo/protoc/PATH, use a **login shell**: `ssh guru@172.16.3.30 'bash -lc "..."'`. Non-interactive shell doesn't source `~/.profile` and these look "missing".
- **Layout:** repo at `/home/guru/gururmm`, build pipeline at `/opt/gururmm/` (auto-synced from repo `deploy/build-pipeline/` by `build-shared.sh`).

View File

@@ -0,0 +1,33 @@
---
name: reference_sqlx_migrations_immutable
description: NEVER edit an already-applied sqlx migration file — even a comment. sqlx::migrate! checksums each file at compile time and validates against _sqlx_migrations at startup; a changed checksum crash-loops the server with "migration N was previously applied but has been modified". Code review MUST flag any edit to an applied migration.
metadata:
type: reference
---
GuruRMM and GuruConnect both apply DB migrations at server startup via `sqlx::migrate!()`
(embedded at COMPILE time from `server/migrations/`). sqlx stores a **checksum** of each migration
in the `_sqlx_migrations` table when it first applies it, and on every startup re-validates the
embedded migration files' checksums against that table.
**Editing an already-applied migration file — even just a COMMENT — changes its checksum** and the
server fails to boot:
```
ERROR Failed to run migrations: migration 8 was previously applied but has been modified
```
systemd then crash-loops it and eventually trips the start-limit ("Start request repeated too quickly").
**Incident 2026-06-01 (GuruConnect):** a one-line `ON CONFLICT` fix in `server/src/db/machines.rs`
was bundled with a *comment-only* edit to `server/migrations/008_machine_uid.sql`. The code fix was
correct, but the migration comment edit took the relay down for ~6 min on deploy. Both the Coding
Agent and the Code Review Agent explicitly judged the comment edit "zero runtime effect" — WRONG.
**Rules:**
- Applied migrations are **immutable**. Never touch them. To change schema, write a NEW migration.
- If documentation about a migration needs fixing, put it in code comments / docs, NOT the migration file.
- **Code review must reject ANY diff that touches a file under `server/migrations/` that has already
been applied in prod** (or require a brand-new migration instead).
- **Recovery:** restore the migration's exact original bytes (`git checkout <prev> -- path/to/NNN.sql`),
rebuild (sqlx embeds at compile time, so a rebuild is required), restart. If systemd shows
"Start request repeated too quickly", clear the limiter first: `sudo systemctl reset-failed <svc>`
then `sudo systemctl start <svc>`.

View File

@@ -0,0 +1,23 @@
---
name: reference_starrpass_mail_routing
description: Starr Pass mail routing — starrpass.com is DIRECT to Microsoft (EOP/Defender), NOT Mailprotector; only devconllc.com is on Mailprotector. Check quarantine/rejects accordingly.
metadata:
type: reference
---
**Starr Pass** email routing (don't reach for Mailprotector first):
- **starrpass.com** delivers **direct to Microsoft** — M365 tenant `starrpass.com`
(tenant id `222450dd-141f-435f-87b8-cec719aac99e`). Quarantined / rejected / held / message-trace
questions for an @starrpass.com address = **EOP / Defender**, via the `remediation-tool`
(`investigator-exo` → EXO REST InvokeCommand: `Get-QuarantineMessage`, `Get-MessageTraceV2`,
`Get-MessageTraceDetailV2`). NOT on the Mailprotector/CloudFilter platform.
- The Mailprotector **"Starr Pass" account (id 16170)** covers ONLY the domain **devconllc.com**
(Devcon LLC, their management company) — domain id `27629`. So a Mailprotector `find-user
cansley@starrpass.com` 404s and `starrpass.com` is absent from the MP domain list — that's expected,
not a fault.
Practical: for any @starrpass.com mail-flow ask, go straight to the remediation-tool/EOP. Use
Mailprotector only for devconllc.com. (Confirmed by Mike 2026-06-16 while checking quarantined/rejected
mail for cansley@starrpass.com — the "rejected" mail was `550 5.1.10 RecipientNotFound` from before the
mailbox was provisioned, now delivering.) Related: [[reference_resource_map]].

View File

@@ -0,0 +1,33 @@
---
name: reference_unifi_site_manager_api
description: UniFi Site Manager cloud API (api.ui.com) + its CONNECTOR proxy give remote access to the WHOLE ACG UniFi fleet (~36 consoles) outside UOS - AND full UOS-parity RF/client data via the connector. Key vaulted at services/unifi-site-manager; backend = unifi-wifi skill gw-sitemanager.sh.
metadata:
type: reference
---
ACG has a **UniFi Site Manager / Cloud API** key (account owner mike@azcomputerguru.com)
that reaches every ACG UniFi console remotely - no UOS server, no on-site/LAN access. This is
the "access a UDM outside the UOS environment" path, and via the connector it reaches
**UOS-parity depth**. Backend: `.claude/skills/unifi-wifi/scripts/gw-sitemanager.sh`.
Full catalog: `.claude/skills/unifi-wifi/references/site-manager-api.md`.
- **Base:** `https://api.ui.com` - **Auth:** header `X-API-KEY: <key>` + `Accept: application/json`.
- **Key:** vault `services/unifi-site-manager` (`credentials.api_key`).
- **Tier 1 (Site Manager, fleet overview):** `GET /v1/hosts` (~36 consoles: id, WAN ipAddress,
controllers+integrationApis), `/v1/sites` (health counts, IPS, ISP/ASN), `/v1/devices`
(inventory: name/model/ip/state/fw), `/v1/isp-metrics/{5m,1h}` (WAN latency/throughput/downtime
time-series). Inventory + health + WAN, NOT per-radio/per-client.
- **Tier 2 (CONNECTOR -> console LOCAL Network API = UOS PARITY):**
`https://api.ui.com/v1/connector/consoles/{hostId}/proxy/network/<path>` with the SAME account key.
- `/proxy/network/api/s/{site}/stat/device` -> `radio_table_stats` (cu_total airtime, channel, bw,
tx_power, num_sta, satisfaction) - the SAME depth as UOS Mongo `ace_stat`.
- `/proxy/network/api/s/{site}/stat/sta` -> per-client rssi/signal/noise/satisfaction/rates.
- `/proxy/network/integration/v1/...` -> official Integration API (sites/devices/clients + POST
actions: device restart, client block/unblock).
- site short name is usually `default`. Confirmed live on Brooklyn/Skybar 2026-06-17.
- == parity for ANY console remotely (broader than UOS, which only sees UOS-adopted sites).
- **Standalone consoles:** direct WAN SSH/HTTPS to a UDM is usually FIREWALLED (e.g. Brooklyn/Skybar
67.1.139.219 - 22/443/8443 filtered). Use the connector; per-console device SSH pw under
`clients/<slug>/udm-ssh` (e.g. clients/brooklyn-skybar/udm-ssh).
Relevant to extending `unifi-wifi` to non-UOS sites. See [[reference_resource_map]].

View File

@@ -0,0 +1,42 @@
---
name: rmm-agent-update-model
description: How GuruRMM agents actually update (server-push on heartbeat, channel-gated, beta-first) and two gotchas that strand agents
metadata:
type: project
---
GuruRMM agent updates are **100% server-push** — the agent never self-polls. On every
heartbeat the server (`server/src/ws/mod.rs` ~line 1124) resolves the agent's channel,
calls `UpdateManager::needs_update`, and pushes `ServerMessage::Update` if a newer build
exists. A pending update is re-dispatched on the next heartbeat (the `[RE-DISPATCH]` path).
The only other Update senders are the manual `POST /api/agents/:id/update` and rollback.
**Available versions = a filesystem scan**, not a DB table. `updates/scanner.rs` scans
`/var/www/gururmm/downloads/` for `gururmm-agent-{os}-{arch}-{ver}.exe` (per-site
`...-site-<uuid>-...` names deliberately fail to parse), requires a `.sha256` companion
(no checksum → silently skipped), and reads channel from a `<binary>.channel` sidecar
(absent or non-"beta" ⇒ **stable**). `get_latest_version` for a stable agent returns the
newest binary whose sidecar isn't "beta". Channel resolves agent→site→client→"stable".
**Promotion** (`POST /api/updates/rollouts/:ver/promote`) just flips every matching
`.channel` sidecar beta→stable (globally — os/arch only scopes the health-gate + rollout
DB row) and rescans. The fleet then pulls it on the next heartbeat. Rollback removes the
sidecars + blocks the version + downgrades. Dashboard admin login: vault
`projects/gururmm/dashboard`. DB: `psql "$DATABASE_URL"` after `source ~/.cargo/env` on
guru@172.16.3.30.
Two gotchas that strand agents (both hit 2026-06-10):
1. **Beta-first freezes stable.** New builds are tagged beta; stable only advances on an
explicit promote. Stable had been frozen at 0.6.47 (since 2026-05-28) while builds ran
to 0.6.58 beta — so every stable agent silently stopped updating. Promoting 0.6.58
rolled ~200 agents in minutes.
2. **Old agents re-enroll with a NEW identity.** The device_id format changed (`win-<uuid>`
→ bare `<uuid>`) somewhere between 0.6.27 and ~0.6.50. An agent old enough to cross that
boundary (e.g. megan, 0.6.27→0.6.58) re-registers as a **new agent row** instead of
updating in place, orphaning its old row (clean up the stale duplicate). Agents already
past the boundary update in place.
Related: [[reference_gururmm]] (downloads dir + sidecar detail + privileged server access).
Audit/log-feedback work: build/version correlation lives in `log_signatures` +
`log_signature_versions`; server self-errors are captured via `self_log.rs` into the
"GuruRMM Server" pseudo-agent.

View File

@@ -0,0 +1,39 @@
---
name: unraid-windows-vm-virtio-no-ip
description: Unraid VMs fail to get a DHCP IP - PRIMARY cause is Docker setting bridge-nf-call-iptables=1 (drops new-VM DHCP OFFERs on br0); secondary is virtio-net having no in-box Windows driver
metadata:
type: reference
---
Two distinct causes make Unraid/KVM VMs come up with **no DHCP IP**. Confirmed 2026-06-12/13 on
Jupiter (`172.16.3.20`, Unraid 6.12.85; host creds vault `infrastructure/jupiter-unraid-primary`).
## PRIMARY (the "VMs generally stopped getting IPs lately" cause): bridge-nf-call-iptables
Docker sets `net.bridge.bridge-nf-call-iptables=1`, which routes **bridged** VM traffic on `br0`
through the iptables FORWARD chain. Docker's `DOCKER-FORWARD` chain only ACCEPTs the docker
bridges (`br-*`, `docker0`) and has **no ACCEPT for `br0`** (the VM bridge), so it drops new
unmatched inbound flows. Effect:
- The VM's DHCP DISCOVER (broadcast) egresses fine and pfSense/Kea sends an OFFER...
- ...but the inbound **OFFER (new unicast flow to an unassigned IP) is dropped** before reaching
the VM tap. The VM never completes DORA -> APIPA 169.254.x. Symptom in tcpdump on the DHCP
server: VM re-DISCOVERs with 3s/8s/15s backoff, server keeps OFFERing fresh IPs, never an ACK.
- **Existing** VMs survive because lease RENEWALS are ESTABLISHED flows (pass); only NEW/rebooted
VMs (fresh DISCOVER) break. = "lately" (a Docker/Unraid update) + "all new VMs".
- **Fix (runtime, reversible):** `echo 0 > /proc/sys/net/bridge/bridge-nf-call-iptables` (and
`bridge-nf-call-ip6tables`). Bridged frames then bypass iptables entirely. **Caveat: Docker
re-sets it to 1 on daemon restart** -> needs a PERSISTENT post-Docker hook (User Scripts "At
Array Start", or a delayed setter in `/boot/config/go`) to truly fix it fleet-wide. NOT yet
made persistent on Jupiter as of 2026-06-13 (pending Mike's OK for the prod boot config).
## SECONDARY (per-VM, Windows-specific): virtio-net has no in-box Windows driver
A Windows VM whose NIC model is the Unraid default `virtio-net` has a **dead NIC** (Windows has
no in-box virtio driver; the guest sends 0 packets). Linux VMs are fine (in-kernel virtio).
The "Windows 11" VM worked because it was set to **e1000**. Fix: NIC model `e1000` (in-box Win7/
Server2003 driver, `virsh edit`/Unraid template dropdown) OR install virtio-win NetKVM (ISOs on
Jupiter `/mnt/user/isos/virtio-win-0.1.271-1.iso`). Diagnose without tcpdump: sample
`/sys/class/net/<vnetN>/statistics/rx_packets` twice -> flat = dead NIC (driver), climbing = NIC
works (then look at the bridge-nf cause above).
Diagnosis order: confirm NIC model first (e1000 vs virtio), then if the NIC transmits but no IP,
suspect bridge-nf-call-iptables. Related: [[gururmm-install-report-failed-agent-v1]]
(WIN7TEST is the SPEC-029 legacy-32bit-agent test VM, static IP 172.16.2.55, NIC now e1000).

Some files were not shown because too many files have changed in this diff Show More