sync: auto-sync from GURU-KALI at 2026-05-26 18:47:58

Author: Mike Swanson
Machine: GURU-KALI
Timestamp: 2026-05-26 18:47:58
This commit is contained in:
2026-05-26 18:48:01 -07:00
parent f94849fc00
commit 4e97e20a2f
8 changed files with 235 additions and 11 deletions

View File

@@ -40,7 +40,11 @@ Claude writes all sections directly. Be concise, factual, technical. No filler p
### Required sections (in order)
1. **User block**name, machine, role, session span. Pull from `.claude/identity.json` + git config.
1. **User block**generate it deterministically; do NOT hand-write or infer it. Run:
```bash
bash .claude/scripts/whoami-block.sh
```
Paste its output verbatim as this section. The script reads `.claude/identity.json` (+ `users.json` for role) — the only authoritative attribution sources. Never derive the user from the hostname, the `# userEmail` context hint, or memory. If the script emits a `[WARNING]` about a stale machine/hostname mismatch, stop and fix `identity.json` before saving.
2. **Session Summary** (Ollama)
3. **Key Decisions** (Ollama)
4. **Problems Encountered** (Ollama)
@@ -61,7 +65,7 @@ When in doubt, include MORE detail — future sessions search these logs to reco
bash .claude/scripts/sync.sh
```
`sync.sh` handles: stage tracked changes by name (never `git add -A`), auto-commit, fetch + rebase, push, then the same flow for the vault repo, then surface cross-user `## Note for <user>` blocks.
`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.
After sync, emit a **Post-commit Summary**:

View File

@@ -10,7 +10,7 @@
- [IX Server SSH Access](reference_ix_server_ssh.md) - SSH access notes, no key auth from CachyOS workstation yet
- [IX Access via Tailscale](reference_ix_access_tailscale.md) - IX server accessible with Tailscale on, no VPN needed
- [Neptune Access via D2TESTNAS](reference_neptune_access_d2testnas.md) - Neptune must be routed through D2TESTNAS
- [ACG-5070 Workstation](reference_workstation_setup.md) - Windows 11, replaced CachyOS. SOPS vault, Ollama, all dev tools.
- [GURU-5070 Workstation (Mike's primary)](reference_workstation_setup.md) - Mike's box, Windows 11. Same machine as OC-5070/ACG-5070/acg-guru-5070 (renamed). SOPS vault, Ollama, all dev tools.
- [Matomo Analytics](reference_matomo_analytics.md) - Self-hosted analytics at analytics.azcomputerguru.com, site IDs, tracking for all 3 sites
- [Dataforth Contact - AJ](reference_dataforth_contact.md) - AJ at Dataforth, dataforthgit@ email forwarding to him
- [TickTick Integration](reference_ticktick_integration.md) - OAuth API integration, MCP server, SOPS vault creds, project/task CRUD
@@ -21,9 +21,10 @@
- [Pluto Build Server](reference_pluto_build_server.md) - General-purpose Windows build VM, 172.16.3.36, SSH as Administrator, MSVC toolchain — use for any EXE (utilities, Howard's tools, GuruRMM agent)
## Users
- [Howard Enos](user_howard.md) — Mike's brother, technician, full trust/access. Known machine: ACG-TECH03L.
- [Howard Enos](user_howard.md) — Mike's brother, technician, full trust/access. Machines: ACG-TECH03L (laptop), Howard-Home (desktop) — authoritative list in users.json.
## Feedback
- [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.
- [GuruRMM agent parity rule](feedback_gururmm_agent_parity.md) — "Add feature X to the agent" = Windows + Linux + macOS in the same change, no exceptions. Stub + TODO if real impl not feasible.
- [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
@@ -51,7 +52,7 @@
- [Cascades folder redirect — fdeploy failure/recovery](feedback_cascades_folder_redirect.md) — Must pre-create subfolders before first logon. fdeploy caches failures silently. Recovery: fix-shell-redirect.ps1. Both GUID and legacy name keys required.
## Machine
- [ACG-5070 Workstation Setup](reference_workstation_setup.md) - Windows 11 Pro clean install 2026-03-30, replaced CachyOS. All tools installed.
- [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.
## Pending Setup
- [Mac gururmm setup pending](project_mac_gururmm_setup_pending.md) — ACTION REQUIRED: run `bash scripts/install-hooks.sh` in gururmm repo on Mikes-MacBook-Air before any RMM work

View File

@@ -0,0 +1,20 @@
---
name: Attribution is read, never inferred
description: Who-did-what (user + machine) comes only from identity.json + users.json + git authorship. Never infer it from hostname patterns, the userEmail hint, or memory notes.
metadata:
type: feedback
---
When attributing work to a person or machine — in session logs, wiki articles, coord messages, commit authorship, or conversation — the attribution MUST come from authoritative sources, never from inference:
- **Who sits at this keyboard:** `.claude/identity.json` (per-machine, gitignored). Greet and stamp logs from its `full_name` / `user` / `machine` / `role`.
- **Which machines belong to whom:** `.claude/users.json``users.<user>.known_machines`. This is the registry.
- **Commit authorship:** `git log` author (`%an <%ae>`). `sync.sh` reconciles `git config` to identity.json on every run, so the author is always correct going forward.
- **Session-log User block:** generated by `bash .claude/scripts/whoami-block.sh` — paste verbatim, do not hand-write.
**Why:** On 2026-05-26 Mike reported recurring misattributions (Howard tied to Mike's machines and vice versa). Investigation proved git history and session logs were actually CLEAN (0 author-vs-machine-owner mismatches across 892 commits / 180 logs). The misattributions were a *reasoning-time inference* problem fed by stale, owner-less memory items — e.g. an "ACG-5070 workstation" note with no owner, and Howard's note saying "desktop hostname TBD". A session would then guess ownership and guess wrong. The fix was deterministic attribution + binding every machine in memory to its owner. Compare [[feedback_identity_precedence]] (identity.json beats the userEmail hint).
**How to apply:**
- Never write "this was probably done by X" or assign a machine to a person from a hostname pattern (e.g. the "5070" box is Mike's per users.json — it had names OC-5070 / ACG-5070 / acg-guru-5070 / GURU-5070; see [[reference_workstation_setup]]).
- If identity/ownership is unknown, read users.json/identity.json or ASK — do not infer.
- When you add a machine to a memory note, state its owner and point at users.json as the source of truth; don't restate the machine list as if it were authoritative.

View File

@@ -1,13 +1,15 @@
---
name: ACG-5070 Workstation Setup
description: Primary workstation ACG-5070 (Windows 11 Pro), clean install 2026-03-30. Replaced CachyOS.
name: GURU-5070 Workstation Setup (Mike's primary)
description: Mike Swanson's primary workstation, current hostname GURU-5070 (Windows 11 Pro). Renamed over its life — same physical box as OC-5070 / ACG-5070 / acg-guru-5070.
type: reference
---
## Workstation: ACG-5070
## Workstation: GURU-5070 — owner: **Mike Swanson** (confirmed 2026-05-26)
**Same physical machine across renames:** `OC-5070``ACG-5070` / `acg-guru-5070`**`GURU-5070`** (current). All of these hostnames (and the bare `OC-5070` git author from March 2026) are this one box, and it is Mike's. Authoritative owner record: `.claude/users.json` (`users.mike.known_machines` includes `GURU-5070`). Do not attribute "5070" work to anyone but Mike, and never to Howard. See [[feedback_attribution_from_identity]].
- **OS:** Windows 11 Pro (clean install 2026-03-30)
- **Previous OS:** CachyOS Linux (gone, replaced by Windows)
- **Previous OS:** CachyOS Linux (gone, replaced by Windows — historical only)
- **Hardware:** ASUS laptop, Intel Arrow Lake-S + NVIDIA RTX 5070 Ti Mobile, dual NVMe
### Installed Tools

View File

@@ -6,8 +6,8 @@ type: user
Howard Enos is a technician at Arizona Computer Guru LLC and Mike Swanson's brother. He has full access to all systems, credentials, and client data — same level as Mike. No permission gating.
Known machine: ACG-TECH03L (laptop). Desktop hostname TBD (will be registered on first sync).
Howard's machines: **ACG-TECH03L** (laptop) and **Howard-Home** (desktop). This list is NOT authoritative here — the source of truth is `.claude/users.json` (`users.howard.known_machines`). Do not infer machine ownership from this note or from hostname patterns; read users.json + the local `.claude/identity.json`. See [[feedback_attribution_from_identity]].
When working with Howard, treat him exactly as you would Mike — same context loading, same credential access, same capabilities. He uses claudetools for MSP work tracking, client management, and daily IT operations.
His git commits should show `Howard Enos <howard@azcomputerguru.com>`.
His git commits show `Howard Enos <howard@azcomputerguru.com>``sync.sh` reconciles git config to identity.json on every sync.

View File

@@ -43,6 +43,31 @@ purge_garbled_paths() {
return 0
}
# --- Attribution: force commit authorship to match identity.json -------------
# Commit author is the per-person source of truth and must NEVER ride on stale
# local `git config`. If config disagrees with identity.json, correct the LOCAL
# repo config (not --global) so this machine's commits are always attributed to
# the human who actually owns this identity. Call once per repo (claudetools,
# then vault) before any commit happens.
reconcile_git_identity() {
local want_name="$1" want_email="$2" cur
if [ -n "$want_name" ]; then
cur=$(git config user.name 2>/dev/null || true)
if [ "$cur" != "$want_name" ]; then
echo -e "${YELLOW}[WARNING]${NC} git user.name '${cur}' -> '${want_name}' (corrected to match identity.json)"
git config user.name "$want_name"
fi
fi
if [ -n "$want_email" ]; then
cur=$(git config user.email 2>/dev/null || true)
if [ "$cur" != "$want_email" ]; then
echo -e "${YELLOW}[WARNING]${NC} git user.email '${cur}' -> '${want_email}' (corrected to match identity.json)"
git config user.email "$want_email"
fi
fi
return 0
}
# Machine + timestamp
if [ -n "$COMPUTERNAME" ]; then
MACHINE="$COMPUTERNAME"
@@ -109,12 +134,36 @@ fi
# Load user identity
USER_DISPLAY="unknown"
USER_GITEA=""
USER_EMAIL=""
if [ -f ".claude/identity.json" ]; then
USER_DISPLAY=$($PYTHON -c "import json,sys; d=json.load(open('.claude/identity.json')); print(d.get('full_name', d.get('user','unknown')))" 2>/dev/null || echo "unknown")
USER_GITEA=$($PYTHON -c "import json,sys; d=json.load(open('.claude/identity.json')); print(d.get('user',''))" 2>/dev/null || echo "")
USER_EMAIL=$($PYTHON -c "import json,sys; d=json.load(open('.claude/identity.json')); print(d.get('email',''))" 2>/dev/null || echo "")
fi
echo -e "${GREEN}[OK]${NC} Syncing as: $USER_DISPLAY (machine: $MACHINE)"
# Force this repo's commit authorship to match identity.json before any commit.
# Skip if identity.json was unreadable (USER_DISPLAY left at the "unknown"
# sentinel) — overwriting correct config with "unknown" would be worse than
# leaving it alone, and is the exact mis-attribution this guard prevents.
if [ "$USER_DISPLAY" != "unknown" ]; then
reconcile_git_identity "$USER_DISPLAY" "$USER_EMAIL"
else
echo -e "${YELLOW}[WARNING]${NC} identity.json present but unreadable — leaving existing git config untouched (not reconciling)."
fi
# Warn (don't block) if identity.json's machine field disagrees with the real
# hostname — the classic "stale identity.json copied to a new box" failure that
# stamps logs/commits with the wrong machine. Compare case-insensitively and
# ignore a trailing ".local" (macOS).
if [ -f ".claude/identity.json" ]; then
ID_MACHINE=$($PYTHON -c "import json; print(json.load(open('.claude/identity.json')).get('machine',''))" 2>/dev/null || echo "")
norm() { printf '%s' "$1" | tr 'A-Z' 'a-z' | sed 's/\.local$//'; }
if [ -n "$ID_MACHINE" ] && [ "$(norm "$ID_MACHINE")" != "$(norm "$MACHINE")" ]; then
echo -e "${YELLOW}[WARNING]${NC} identity.json machine '${ID_MACHINE}' != hostname '${MACHINE}' — identity.json may be stale on this box. Fix it before attributing work."
fi
fi
# Phase 1a: Update submodules to latest remote
# `git submodule foreach` only visits *initialized* submodules, so a fresh clone
# would silently skip population and the old "[OK] updated" message lied. We now
@@ -385,6 +434,12 @@ else
cd "$VAULT_PATH"
echo -e "${GREEN}[OK]${NC} Vault: $VAULT_PATH"
# Same attribution guard for the vault repo (it has its own git config).
# Same "unknown" skip as the main repo — the main-repo warning already fired.
if [ "$USER_DISPLAY" != "unknown" ]; then
reconcile_git_identity "$USER_DISPLAY" "$USER_EMAIL"
fi
# Commit any local vault changes (porcelain catches untracked-only too)
if [ -n "$(git status --porcelain)" ]; then
echo -e "${YELLOW}[INFO]${NC} Local vault changes detected — committing..."

66
.claude/scripts/whoami-block.sh Executable file
View File

@@ -0,0 +1,66 @@
#!/bin/bash
# Emit the canonical session-log "## User" attribution block.
#
# ATTRIBUTION IS NEVER INFERRED. The only sources of truth are:
# .claude/identity.json (per-machine, gitignored — who sits at this keyboard)
# .claude/users.json (role lookup by user key)
# Do NOT derive the user from the hostname, the `# userEmail` context hint, or
# from memory. /save calls this script and pastes its output verbatim as the
# session log's first section, so every log is stamped identically and correctly.
set -e
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
ID="$REPO_ROOT/.claude/identity.json"
USERS="$REPO_ROOT/.claude/users.json"
if [ ! -f "$ID" ]; then
echo "## User"
echo "- **User:** UNKNOWN — no .claude/identity.json on this machine."
echo "- **Action:** run the first-machine onboarding flow in CLAUDE.md before saving."
exit 0
fi
PYTHON=""
for c in py python3 python; do
if command -v "$c" >/dev/null 2>&1 && "$c" -c "import sys" >/dev/null 2>&1; then PYTHON="$c"; break; fi
done
if [ -z "$PYTHON" ]; then
echo "## User"
echo "- **User:** (could not render — no Python interpreter found)"
exit 0
fi
"$PYTHON" - "$ID" "$USERS" <<'PYEOF'
import json, sys, socket, re
idp, usersp = sys.argv[1], sys.argv[2]
try:
d = json.load(open(idp))
except Exception as e:
print("## User")
print(f"- **User:** UNREADABLE — .claude/identity.json failed to parse ({e}).")
print("- **Action:** repair identity.json before saving; do not infer the user.")
sys.exit(0)
full = d.get("full_name") or d.get("user", "unknown")
user = d.get("user", "unknown")
machine = d.get("machine", "unknown")
role = d.get("role", "")
if not role:
try:
role = json.load(open(usersp))["users"].get(user, {}).get("role", "")
except Exception:
pass
def norm(x): return re.sub(r'\.local$', '', (x or '').lower())
host = socket.gethostname()
stale = machine and host and norm(machine) != norm(host)
print("## User")
print(f"- **User:** {full} ({user})")
print(f"- **Machine:** {machine}")
if role:
print(f"- **Role:** {role}")
if stale:
print(f"- **[WARNING]** identity.json machine '{machine}' != hostname '{host}'. "
f"identity.json may be stale on this box — verify before trusting this attribution.")
PYEOF