# Dataforth Session Log — 2026-06-02

## User
- **User:** Howard Enos (howard)
- **Machine:** Howard-Home
- **Role:** tech

## Session Summary

Winter (Dataforth contact) asked Howard to clean up the Dataforth asset list in Syncro (customer_id 578095), removing machines no longer in service, so the account can be moved to metered per-asset billing. ACG pays per asset even after a machine is retired client-side, so stale records inflate the bill. The work was a read-only reconciliation across three systems we control — Syncro, Bitdefender GravityZone, and ScreenConnect — to determine which assets are genuinely dead versus merely missing one agent.

Pulled the full Syncro asset list (78 assets, 2 pages). Found 57 of 78 frozen at or before `updated_at = 2025-10-06`, with the rest showing recent check-ins — a hard cutoff indicating a fleet-wide Syncro agent break around that date rather than 57 machines retiring simultaneously. Confirmed no in-Syncro duplicate records (no shared computer_name/serial; the only MAC collisions were VirtualBox default adapters), so the frozen records are unique machines, not re-enrollment orphans. Pulled Bitdefender GravityZone for the Dataforth company (`64c94ef310db128bfa0d908f`, suffix `_578095` confirms the client): only 4 endpoints actively managed in "Custom Groups", 53 in the "Deleted" folder — Dataforth is being phased off Bitdefender, so BD-absence is NOT a decommission signal.

Howard then set the governing rule: a machine is **saved** if it has been online within 150 days in **any** of Syncro, ScreenConnect, or Bitdefender, and flagged for repair if alive in one system but broken in another. ScreenConnect was the missing third source. Reverse-engineered the ScreenConnect RESTful API extension auth (the vault note only said "CTRLAuthHeader + Origin"); the working scheme is `CTRLAuthHeader: <raw api_secret>` (no "Basic " prefix) + `Origin` header, method `GetSessionsByName` with `{"sessionName":"..."}`. The api user's session visibility is limited (agent session `Name` fields are blank), so the API alone could not enumerate Dataforth. Howard supplied a 15s screen recording of the ScreenConnect session list; extracted 10 frames via a pip-installed `imageio-ffmpeg` and read the online/offline + idle status for each machine.

The ScreenConnect data was decisive: ~21 machines that look dead in Syncro (frozen 2025-10-06) are online right now in ScreenConnect (AD1, AD2, SAGE-SQL, FILES-D1, MY9-PC, DANC0619, DFORTH-SHIP, etc.) — their boxes are fine, only the Syncro agent died. Final reconciliation of 78 assets: 20 keep (active in Syncro <150d), 21 save+flag (alive elsewhere, broken Syncro agent), 28 remove (dead in all three), 9 verify (servers with no agent anywhere — could be live console-only). Attempted the deletions via API but `DELETE /customer_assets/{id}` returned an HTML 404 (route not exposed for this token); stopped per the Syncro skill hard-rule, verified the test asset was untouched, and handed Howard a GUI delete list. Closed by saving a 5-step todo tree under `clients/dataforth` for Howard to execute tomorrow.

## Key Decisions

- **Drove removal decisions off Syncro last-checkin age, not Bitdefender presence.** BD is being decommissioned at Dataforth (only 4 of 57 endpoints still managed), so "gone from BD" only means the BD agent was uninstalled. Proven by GOLDSTAR19/DESKTOP-3PFA5I7 checking into Syncro today while showing BD-deleted.
- **Adopted Howard's 3-system OR rule** (online <150d in Syncro OR ScreenConnect OR Bitdefender → save + flag) instead of removing on Syncro-staleness alone. This saved 21 live machines from wrongful removal.
- **Split stale candidates into workstations vs servers.** Removing a live-but-agent-broken server also kills its monitoring, so 8 server-OS boxes absent from all three systems went to a VERIFY bucket rather than auto-remove.
- **Stopped after one failed DELETE rather than probing alternative endpoints,** per the Syncro skill hard-rule (unexpected response → stop). The API returned an HTML 404 page, indicating the route is not exposed for this integration token; deletions were routed to the Syncro GUI.
- **Treated ScreenConnect "Idle Xd" as a last-activity recency signal** even for currently-offline machines (e.g. DFASLB0519 idle 5h = active 5h ago → alive), so short-idle offline machines were saved.

## Problems Encountered

- **identity.json not at the assumed path.** The Syncro skill template referenced `D:/claudetools`; this machine is `C:/claudetools`. Corrected all script paths to `C:/claudetools/.claude/identity.json`.
- **Python could not open git-bash-style paths** (`/c/...`). Switched file paths in Python scripts to Windows form (`C:/claudetools/...`).
- **Syncro API responses contain unescaped control chars** — stripped with `tr -d '\000-\037'` before jq/json parsing, per the skill's documented quirk.
- **ScreenConnect API auth undocumented.** Probed method names (only `GetSessionsByName` exists) and auth header formats; the working combination is `CTRLAuthHeader: <raw secret>` + `Origin`. The api user sees only a handful of sessions by name (agent Names are blank), so the API could not enumerate Dataforth — resolved by reading frames from Howard's screen recording.
- **No ffmpeg / video libs installed.** Installed `imageio-ffmpeg` via pip (bundles a static ffmpeg) to extract frames from the .mp4.
- **`DELETE /customer_assets/{id}` returned HTML 404.** Asset deletion not available via this API token; verified the test asset (id 23845) was untouched and switched to GUI deletion.
- **Coord todos POST schema mismatch.** Required fields are `text`, `created_by_user`, `created_by_machine` (not `title`/`description`); a heredoc-in-function quoting bug silently dropped 5 subtasks — recreated them with direct POSTs.

## Configuration Changes

- No repo source files modified. All work products are machine-local under `C:/claudetools/.claude/tmp/` (gitignored, not synced):
  - `dataforth_assets.json` — 78 Syncro assets (raw)
  - `bd_endpoints.json`, `bd_lastseen.json` — Bitdefender GravityZone inventory + last-seen
  - `remove_ids.json` — 28 confirmed-dead assets (id + name)
  - `flag_ids.json` — 21 alive-but-broken machines
  - `frames/f_001.jpg`..`f_010.jpg` — extracted ScreenConnect frames
  - analysis scripts: `analyze_assets.py`, `dupe_check.py`, `reconcile.py`, `sc_status.py`, `bd_walk.py`, `bd_lastseen.py`, `sc_all.json`
- Installed pip package `imageio-ffmpeg` (user site) for frame extraction.

## Credentials & Secrets

- **ScreenConnect RESTful API** — vault: `msp-tools/screenconnect.sops.yaml` (fields `credentials.username`, `credentials.api_secret`). Host `https://computerguru.screenconnect.com`, extension-guid `2d558935-686a-4bd0-9991-07539f5fe749`.
  - **Working auth (newly determined):** header `CTRLAuthHeader: <raw api_secret>` (NO "Basic " prefix) + header `Origin: https://computerguru.screenconnect.com`. Basic-auth in `Authorization` or a "Basic <b64>" CTRLAuthHeader both 401.
  - **Only method that exists:** `POST /App_Extensions/<guid>/Service.ashx/GetSessionsByName` with body `{"sessionName":"<name>"}`. All other Get* method names return 500 "Web method does not exist". Match is on the session `Name` field (blank for unattended agents), so this api user enumerates only a few sessions — not full inventory. Custom props: CP1=Company, CP2=Site, CP3=Tag.
- **Syncro API** — per-user keys baked into the `/syncro` skill; Howard's integration token lacks (or Syncro does not expose) asset-delete scope (`DELETE /customer_assets/{id}` → HTML 404).
- No new secrets created.

## Infrastructure & Servers

- **Syncro PSA:** `https://computerguru.syncromsp.com/api/v1`; Dataforth customer_id `578095`; assets page `https://computerguru.syncromsp.com/customer_assets?customer_id=578095`.
- **Bitdefender GravityZone (ACG partner tenant):** Dataforth company id `64c94ef310db128bfa0d908f`; group tree = "Custom Groups" (`64c94ef410db128bfa0d9094`, 4 managed) + "Deleted" (`64c94ef410db128bfa0d9095`, 53 mostly unmanaged).
- **ScreenConnect:** `https://computerguru.screenconnect.com`.
- **Coord API:** `http://172.16.3.30:8001/api/coord/todos`.
- **Dataforth primary contact (Syncro):** Dan Center, dcenter@dataforth.com; invoice CC jantar@dataforth.com. GravityZone company owner field: Lee Payne.

## Commands & Outputs

```bash
# Syncro asset pull (paginate, strip control chars)
curl -s "$BASE/customer_assets?customer_id=578095&per_page=100&page=N&api_key=$KEY" | tr -d '\000-\037'

# Bitdefender: find company, walk group tree, pull lastSeen
py gz.py companies --json                      # -> 64c94ef310db128bfa0d908f Dataforth
py gz.py raw --module network --method getNetworkInventoryItems --params '{"parentId":"<co>","page":1,"perPage":100}'
py gz.py raw --module network --method getManagedEndpointDetails --params '{"endpointId":"<id>"}'   # -> lastSeen

# ScreenConnect (WORKING auth):
curl -s -X POST "https://computerguru.screenconnect.com/App_Extensions/2d558935-686a-4bd0-9991-07539f5fe749/Service.ashx/GetSessionsByName" \
  -H "CTRLAuthHeader: <api_secret>" -H "Origin: https://computerguru.screenconnect.com" \
  -H "Content-Type: application/json" --data '{"sessionName":""}'

# Frame extraction
python -m pip install imageio-ffmpeg
"<imageio_ffmpeg ffmpeg>" -i "Recording 2026-06-02 185624.mp4" -vf "fps=1/1.5" -q:v 3 frames/f_%03d.jpg

# Syncro asset delete attempt (FAILED — route not exposed)
curl -s -X DELETE "$BASE/customer_assets/23845?api_key=$KEY"   # -> HTML 404; asset still present (GET 200)
```

Reconciliation result: 78 assets → 20 keep / 21 save+flag / 28 remove / 9 verify.

## Pending / Incomplete Tasks

Saved as a coord todo tree under `clients/dataforth` (parent `103c48ad-7b31-4967-9388-065a91888e7c`), assigned to howard:
1. Delete the 28 confirmed-dead assets in the Syncro GUI (API won't delete).
2. Decide the 9 VERIFY servers (likely dead: OLD-AD2, EXCHANGE16, SAGETS-1; confirm: APPS, AD-3, AD-4, EXCHANGE, EPICOR, D2-ASSY-001).
3. Fix the 21 alive-but-broken machines — reinstall Syncro agent (do NOT delete).
4. Switch Dataforth to metered Syncro asset billing once clean.
5. Reply to Winter; flag the ~2025-10-06 fleet-wide Syncro agent break for investigation.

Open offer: persist remove/flag lists into the Dataforth wiki (tmp files are machine-local/unsynced); save ScreenConnect API auth to vault note + memory.

## Reference Information

- **28 REMOVE ids:** 23845, 149614, 9708445, 9357407, 9276901, 9212922, 9078651, 8824875, 8824867, 8726494, 8726485, 8657233, 8606209, 8572160, 8523941, 8411908, 8410614, 8632009, 8726495, 8421223, 9081717, 8726493, 8423782, 8726481, 8525650, 8622969, 8361459, 8670944
- **21 SAVE+FLAG:** AD1, AD2, SAGE-SQL, FILES-D1, ENG-DEV-SERVER, D2-MFG-001, D1-ENGI-012, MY9-PC, D1-CUST-003, DANC0619, DFORTH-SHIP, DF-LEE11-I9, DFASLB0519, D2-AS-26, HGHAUBNER, D1-PWRM, D1-ENGI-EMCLAB1, D1-CONF-002, D2-HIPOT-SURFAC, D2-AS-34, TS-41 (shows as STATION_41 in ScreenConnect)
- **9 VERIFY:** APPS, EXCHANGE, EXCHANGE16, AD-3, AD-4, OLD-AD2, SAGETS-1, EPICOR, D2-ASSY-001
- Coord parent todo: `103c48ad-7b31-4967-9388-065a91888e7c`
- Root cause flagged: fleet-wide Syncro agent stopped reporting ~2025-10-06 across ~half of Dataforth.

---

## Update: 20:28 MST — Persisted ScreenConnect API auth (vault + memory)

### Session Summary

Following the Dataforth cleanup, persisted the reverse-engineered ScreenConnect RESTful API auth so it does not have to be re-derived. Updated the vault note `msp-tools/screenconnect.sops.yaml` (`notes` field) via `sops set` from the vault root, documenting the exact working scheme, the only valid method, and the enumeration limitation. Created a new shared memory reference `reference_screenconnect_api.md`, indexed it in `MEMORY.md`, and linked it to `[[reference_acg_msp_stack]]`. Both repos synced and pushed.

### Key Decisions

- Stored the auth in BOTH the vault note (where the `bitdefender`/ScreenConnect-style skills look) and repo memory (where recall surfaces it) — the vault note is the operational source, memory is the discovery hook.
- Recorded the blank-`Name`/limited-visibility caveat prominently so a future session does not assume the API can enumerate a full client inventory (it cannot).

### Configuration Changes

- `D:/vault/msp-tools/screenconnect.sops.yaml` — `notes` field rewritten via `sops set '["notes"]'` (re-encrypted in place).
- `C:/claudetools/.claude/memory/reference_screenconnect_api.md` — created.
- `C:/claudetools/.claude/memory/MEMORY.md` — appended one index line.

### Credentials & Secrets

- No new secret values. Vault note `msp-tools/screenconnect.sops.yaml` now carries the verified auth recipe (header `CTRLAuthHeader: <raw api_secret>`, no Basic/b64, + `Origin` header). Secret values unchanged.

### Commands & Outputs

```bash
# update encrypted notes field (run from vault root)
cd D:/vault
NEW=$(python -c 'import json;print(json.dumps("...note text..."))')
sops set msp-tools/screenconnect.sops.yaml '["notes"]' "$NEW"   # exit=0
sops decrypt msp-tools/screenconnect.sops.yaml | grep '^notes:'  # verified
```

### Pending / Incomplete Tasks

- Unchanged from the main log: Howard's 5-step coord todo tree under `clients/dataforth` (GUI-delete the 28, decide the 9 verify, fix the 21 agents, move to metered billing, reply to Winter).

### Reference Information

- Vault commit: `297dc9c..bbd8b30` (vault repo).
- ClaudeTools HEAD after first save: `bba57a1`; after this update: see post-commit summary below.
- Memory file: `.claude/memory/reference_screenconnect_api.md`.
