Compare commits

..

19 Commits

Author SHA1 Message Date
20dc394ac9 sync: auto-sync from AD2 at 2026-06-17 16:21:47
Author: Mike Swanson
Machine: AD2
Timestamp: 2026-06-17 16:21:47
2026-06-17 16:23:11 -07:00
a545a06591 dataforth(datasheet): save email draft to John summarizing all findings
Plain-language summary for John (EE, non-developer) covering all 4 problems
(RTD label, DSCA table, same-day run, missing units), framed as ACG-owned fixes.
Cross-links the four supporting technical docs. Draft - not yet sent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 16:23:11 -07:00
9daba0f9f0 dataforth(datasheet): correct Cause 2 of missing units - cryptolocker incident, not import timing
Per Mike: import runs every 15 min, so routine timing isn't the cause. The 379
absent units are confined to 2025-10..2026-01 (stop after Jan 2026) on TS-4L/4R/1R
- fingerprint of a one-time overwrite during the incident/recovery (fresh DOS logs
overwrote accumulated appended server-side logs for ~2 weeks). One-time, not
recurring; backfill from the surviving staged .TXT.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 16:23:11 -07:00
6bfc0fba6a dataforth(datasheet): root-cause the 608 missing units (report for John)
608 staged datasheets absent from DB. Two causes: (1) 229 units with encoded/
non-standard serials the importer's leading-digit regex silently skips - data is
in the .DAT, recoverable; full blind spot is 840 serials / 9,510 records / 141
models dropped fleet-wide. (2) 379 units whose per-model .DAT was overwritten by a
later work order - recoverable only from the staged .TXT or a log backup. Adds
John-facing report, raw data, and the chase-missing-units.js tool.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 16:23:11 -07:00
8ecd8d22e5 dataforth(datasheet): same-day retest faithfulness — exposure sweep + fix proposal
Whole-source sweep (981,716 records / 406,549 serials): 6,515 same-day multi-run
events; DB holds a NON-latest run for 311 (the strictly-greater-date conflict rule
freezes on an arbitrary same-day run). Corrects the verdict doc to flag same-day
retests as a latest-wins faithfulness violation (not benign). Adds the proposed
>= -with-data-differs conflict-rule fix (diagnose-only) and the sweep tool.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 16:23:11 -07:00
0989ca01ab dataforth(datasheet): parsing-fidelity validation — all staged originals vs DB
Validated all 11,922 staged original .TXT datasheets against test_records.
0 genuine parse faults across 11,239 comparable records; mismatches all explained
(retests, reused serials, VAS format, legacy out-of-scope units). Adds the
validate-parsing.js tool, raw report, and verdict. Two follow-ups (NOT parse bugs):
608 staged units absent from DB (ingestion completeness), and same-day retests keep
the first run (ON CONFLICT strictly-greater-date).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 16:23:10 -07:00
e348a468cb dataforth(datasheet): PROPOSED Defect-A fix — render RTD input as Temp (C), not resistance
Repo copy only (review before deploying to C:\Shares\testdatadb). Folds RTD
(sensorNum 7) into the temperature path so the ACCURACY input column shows
'Temp. (C)' with signed temperature values, matching the original DOS-generated
datasheets and thermocouples (3-6). raw_data stimulus is already in deg C; no
conversion. getSensorNum and the i==13 ohm/ohm unit override are untouched.

Verified read-only against deployed env: 8B35 SN 179553-13 now shows Temp (C);
regression over 184 5B/8B renders -> 15 RTD changed (intended), 0 non-RTD changed.
Does NOT address Defect B (DSCA template). See DATASHEET-RTD-BUG-DIAGNOSIS-2026-06-17.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 16:23:10 -07:00
5c660af453 memory: AD2 sync.sh pushes main not ad2 (fork push gotcha)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 16:23:10 -07:00
7ea4d0d5be sync: auto-sync from AD2 at 2026-06-17 13:56:18
Author: Mike Swanson
Machine: AD2
Timestamp: 2026-06-17 13:56:18
2026-06-17 16:23:10 -07:00
69e3e56a91 memory: record AD2 Dataforth-fork structure + sync gotchas
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 16:23:10 -07:00
a2e98e68f1 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 16:23:10 -07:00
451cef7a40 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 16:23:06 -07:00
sysadmin
f2fcd9cc59 Add AD scripts and stage import instructions
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-17 16:23:02 -07:00
sysadmin
2dc3a4d9f0 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 16:23:02 -07:00
10988d98df sync: auto-sync from GURU-5070 at 2026-06-17 16:18:26
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-17 16:18:26
2026-06-17 16:18:44 -07:00
e72036e48d dataforth: hardened fix spec for test-datasheet defects (multi-AI reviewed)
Consolidates AD2's diagnosis + independent Grok/Gemini review into an
implementation spec for the 5 fixes (RTD label, DSCA Final-Test rebuild, retest
supersede rule, encoded-serial importer decode, 379 backfill) with per-fix
validation gates and a cross-cutting re-publication discipline. Drives the
AD2-side implementation. Ref ticket #32441.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 16:18:44 -07:00
b01c63f6a8 sync: auto-sync from HOWARD-HOME at 2026-06-17 16:01:19
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-17 16:01:19
2026-06-17 16:01:28 -07:00
56f27ec6b1 sync: auto-sync from HOWARD-HOME at 2026-06-17 15:47:50
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-17 15:47:50
2026-06-17 15:48:18 -07:00
b7e0ec3cc9 sync: auto-sync from GURU-5070 at 2026-06-17 14:19:50
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-17 14:19:50
2026-06-17 14:24:35 -07:00
8 changed files with 289 additions and 26 deletions

View File

@@ -2,7 +2,7 @@
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`.
> **Mail path (working — repointed 2026-06-17).** `/mailbox` uses the dedicated single-tenant **ComputerGuru Mailbox** app (`1873b1b0-3377-485c-a848-bae9b2f8f1f5`; vault `msp-tools/computerguru-mailbox.sops.yaml`; Mail.ReadWrite + Mail.Send + Contacts.ReadWrite; azcomputerguru.com only). Tokens come from the suite tool: `bash .claude/skills/remediation-tool/scripts/get-token.sh azcomputerguru.com mailbox` (cert-preferred, secret fallback, 55-min cache). This **replaces the deleted `fabb3421`** (Claude-MSP-Access), removed from the tenant 2026-06-14 — it returns **AADSTS700016**; do NOT reintroduce it. The mailbox app's service principal is **disabled when idle**: on a token 401 "account is disabled", enable the SP, then retry.
## Usage
@@ -21,15 +21,15 @@ Read and send mail for an Arizona Computer Guru mailbox via Microsoft Graph, usi
Microsoft Graph access to ACG's own mailboxes (azcomputerguru.com tenant). Reading is unrestricted; **sending and replying are always gated** behind a full draft preview + explicit confirmation. Sends go out *as* the mailbox owner (your `From:`), saved to your Sent Items.
**Scope boundary:** this is for ACG's OWN mailboxes. For reading a CLIENT tenant's mailboxes (breach checks, rule audits), use `/remediation-tool`same Graph app (`fabb3421`), different purpose.
**Scope boundary:** this is for ACG's OWN mailboxes (the dedicated mailbox app, azcomputerguru.com only). For reading a CLIENT tenant's mailboxes (breach checks, rule audits), use `/remediation-tool`the tiered Security Investigator / Exchange Operator apps, different apps and purpose.
## API Configuration
- **App:** Claude-MSP-Access (Graph API), `client_id = fabb3421-8b34-484b-bc17-e46de9703418` (application permissions incl. Mail.ReadWrite + Mail.Send, tenant-wide).
- **App:** ComputerGuru Mailbox (dedicated single-tenant Graph app), `client_id = 1873b1b0-3377-485c-a848-bae9b2f8f1f5` (Mail.ReadWrite + Mail.Send + Contacts.ReadWrite, azcomputerguru.com only). Replaces the deleted `fabb3421`.
- **Tenant:** `azcomputerguru.com`
- **Secret:** SOPS vault `msp-tools/claude-msp-access-graph-api.sops.yaml` -> `credentials.credential`. Read with command substitution; never hardcode, never print it.
- **Token:** acquire via the suite tool — `bash .claude/skills/remediation-tool/scripts/get-token.sh azcomputerguru.com mailbox` (cert-preferred, secret fallback, 55-min cache in `/tmp/remediation-tool/<tenant>/mailbox.jwt`). Do NOT roll your own `client_credentials` here. Credential vault entry: `msp-tools/computerguru-mailbox.sops.yaml`.
- **SP idle-toggle:** the mailbox app's service principal is disabled when idle. On a token 401 "account is disabled", enable the SP, then retry.
- **Mailbox (default):** resolved from `identity.json` `.user` -> `mike` = `mike@azcomputerguru.com`, `howard` = `howard@azcomputerguru.com`. Override with `--as <email>`.
- **Token:** `client_credentials`, scope `https://graph.microsoft.com/.default`. Cache to `$REPO_ROOT/.claude/tmp/mailbox-token.json` (gitignored), ~55 min TTL.
- **Graph base:** `https://graph.microsoft.com/v1.0`
## Hard Rules (sending email is irreversible — no exceptions)
@@ -72,29 +72,18 @@ sys.stdout.reconfigure(encoding='utf-8', errors='replace')
MAILBOX = sys.argv[1]
REPO = subprocess.run(["bash","-lc","jq -r .claudetools_root \"$HOME/.claude/identity.json\" 2>/dev/null || git rev-parse --show-toplevel"],
capture_output=True, text=True).stdout.strip() or r"D:\claudetools"
CLIENT_ID = "fabb3421-8b34-484b-bc17-e46de9703418"
TENANT = "azcomputerguru.com"
GRAPH = "https://graph.microsoft.com/v1.0"
CACHE = os.path.join(REPO, ".claude", "tmp", "mailbox-token.json")
def secret():
r = subprocess.run(["bash", os.path.join(REPO,".claude","scripts","vault.sh"),
"get-field","msp-tools/claude-msp-access-graph-api.sops.yaml","credentials.credential"],
capture_output=True, text=True)
return r.stdout.strip()
def token():
try:
c = json.load(open(CACHE))
if c.get("exp",0) - time.time() > 120: return c["tok"]
except Exception: pass
data = urllib.parse.urlencode({"client_id":CLIENT_ID,"client_secret":secret(),
"scope":"https://graph.microsoft.com/.default","grant_type":"client_credentials"}).encode()
t = json.loads(urllib.request.urlopen(urllib.request.Request(
f"https://login.microsoftonline.com/{TENANT}/oauth2/v2.0/token", data=data), timeout=20).read())
os.makedirs(os.path.dirname(CACHE), exist_ok=True)
json.dump({"tok":t["access_token"],"exp":time.time()+int(t.get("expires_in",3600))}, open(CACHE,"w"))
return t["access_token"]
# Delegate to the suite token tool: dedicated mailbox app (1873b1b0) via vault
# msp-tools/computerguru-mailbox.sops.yaml. Cert-preferred, secret fallback, 55-min cache.
# Do NOT reintroduce the deleted fabb3421 / inline client_credentials here.
gt = os.path.join(REPO, ".claude", "skills", "remediation-tool", "scripts", "get-token.sh")
r = subprocess.run(["bash", gt, "azcomputerguru.com", "mailbox"], capture_output=True, text=True)
tok = (r.stdout.strip().splitlines() or [""])[-1].strip()
if not tok:
raise SystemExit("[ERROR] mailbox token failed (SP disabled? enable it, then retry). stderr: " + r.stderr[-400:])
return tok
def graph(method, path, body=None):
h = {"Authorization": f"Bearer {token()}", "Content-Type":"application/json"}

View File

@@ -8,7 +8,9 @@ When the user says "365 remediation tool" or "remediation tool", they mean ACG's
**App suite (current — tiered):** Security Investigator `bfbc12a4` (Graph read + EXO read), Exchange Operator `b43e7342` (EXO write), User Manager `64fac46b` (user/license/MFA/pw write), Tenant Admin `709e6eed` (high-priv directory), Defender Add-on `dbf8ad1a` (MDE-licensed tenants ONLY). Secrets in `msp-tools/computerguru-*.sops.yaml`. Client-credentials auth; tenant ID via OpenID discovery (or the `*.onmicrosoft.com` domain when the primary domain isn't verified). Use the lowest tier needed. Each app is consented per-tenant (URLs in `references/gotchas.md`); privileged ops also need directory roles assigned to the SP in that tenant (`onboard-tenant.sh`).
**DEPRECATED — do NOT consent to customer tenants:** `fabb3421` ("AI Remediation" / "Claude-MSP-Access", secret `msp-tools/claude-msp-access-graph-api.sops.yaml`). ~159 perms incl. Defender ATP, so admin consent **breaks with AADSTS650052 on any tenant lacking an MDE license**. It still works where already consented (e.g. ACG's own tenant — the `/mailbox` skill reads our own mailboxes with it), but new onboarding MUST use the tiered suite. (Corrected 2026-05-27 during Quantum onboarding — nearly consented the deprecated app to a no-MDE tenant.)
**DELETED — gone, do not reference:** `fabb3421` ("AI Remediation" / "Claude-MSP-Access", secret `msp-tools/claude-msp-access-graph-api.sops.yaml`). Removed from the azcomputerguru.com tenant **2026-06-14**; every token request now returns **AADSTS700016**. It previously had ~159 perms incl. Defender ATP (admin consent broke with AADSTS650052 on no-MDE tenants). Any skill still pointing at it is broken — repoint to the suite. (Original deprecation: 2026-05-27 Quantum onboarding.)
**ACG OWN-mailbox reads/sends (`/mailbox`) — dedicated app `1873b1b0-3377-485c-a848-bae9b2f8f1f5`** ("ComputerGuru Mailbox", vault `msp-tools/computerguru-mailbox.sops.yaml`, Mail.ReadWrite + Mail.Send + Contacts.ReadWrite, azcomputerguru.com single-tenant). Token via `get-token.sh azcomputerguru.com mailbox` (a tier in get-token.sh; cert-preferred). This is what REPLACED fabb3421 for `/mailbox`. Its SP is **disabled when idle** → a token 401 "account is disabled" means enable the SP first. (`/mailbox` command doc repointed to it 2026-06-17 — it had been left on the dead fabb3421.)
**Why (original):** user clarified "remediation tool" != CIPP after a wrong CIPP navigation. **How to apply:** prefer the `/remediation-tool` skill — it wraps tenant resolution, token caching, breach check, sweep, gated remediation, and consent/onboarding URLs (`references/gotchas.md`, `graph-endpoints.md`, `checklist.md`).

View File

@@ -785,3 +785,36 @@ experience delta needs BUSINESS-HOURS load -> daytime re-check scheduled.
Daytime re-check: CronCreate one-shot 9:33 AM 6/17 (reported session-only — NOT durable) + coord todo
(reliable backstop) filed for Howard. Remaining: Phase 5 (5GHz 80->40 + channel) + Phase 6 (6GHz: add 6g
to CSCNet + bss-tm). apply-wlan 6e->6g fix already committed (5ad25d1).
## Update: 16:00 PT (2026-06-17) — daytime re-checks: interference DOWN but client experience WORSE (over-thinning) -> Low->Medium recommended. (Howard continuing in another session.)
Two loaded-network re-checks (09:33 + 11:27, time-of-day controlled vs same hours yesterday at full power)
both say the SAME thing: the 2.4 remediation cleaned too aggressive.
metric (2.4 site-wide, hourly) yesterday FULL now POST-change
interference cu_interf 65-66% 32% <- WIN (down ~half)
total cu_total 79% 44% <- WIN
retry% 17-18.5% 23-32% <- WORSE (roughly doubled)
satisfaction 39 30-31 <- WORSE
Active-radio-only snapshot: cu_interf ~48% (vs 64 pre), retry ~22%. Fleet: 77 adopted / 2 disconnected,
NO AP offline.
DIAGNOSIS: OVER-THINNING. Power to Low (~6 dBm) is a 17 dB cut from auto(~23); combined with 24 disabled
radios, some edge 2.4 clients now reach a farther/weaker AP -> retries up, satisfaction down EVEN THOUGH
the air is much cleaner. The interference goal is met; the lever overshot on coverage.
>>> CURRENT DECISION POINT (next action, NOT yet done):
1. Bump the 42 KEPT 2.4 radios Low -> MEDIUM (~12-15 dBm): restores edge signal, cells still tighter than
full, and the 24 disables STAY OFF so most of the interference win is kept. Command per floor (skip 5/6,
skip mesh/offline like the power-down was):
bash .claude/skills/unifi-wifi/scripts/apply-radio.sh cascades ng power medium --zone "Floor 3" --apply
(Floor 1/2/misc per-AP, spaced ~4s, to skip mesh parents + 128/108 + login throttle)
2. Re-measure retry/satisfaction (same time-of-day) after the bump to confirm recovery.
3. If still high, re-enable a few of the 24 disabled radios in the weakest spots.
STATE RECAP for the other session: Phase1 power-down DONE (was Low on 65; recommend ->Medium on the kept
42). Phase3 disables DONE (24 off: 102 115 116 122 127 128 139 145 203 204 209 229 236 240 241 248 304 317
336 342 407 442 445 450). Floors 5/6 + mesh (2nd Floor Atrium/206/CC Bridge/salon) + offline 108/108U7Pro
UNTOUCHED. Phase 5 (5GHz 80->40 + channel) and Phase 6 (6GHz: add 6g to CSCNet + bss-tm; apply-wlan 6e->6g
already fixed) still PENDING. Runbook: reports/2026-06-16-2.4ghz-remediation-runbook.md. Rollback: ng power
auto / ng enable --ap. Friction logged: per-AP apply re-logins -> space calls.

View File

@@ -0,0 +1,94 @@
# Cascades — CS-SERVER failing-drive review + noon network-spike question
- **Date:** 2026-06-17
- **Machine:** Howard-Home
- **Client:** Cascades of Tucson
## User
- **User:** Howard Enos (howard)
- **Machine:** Howard-Home
- **Role:** tech
## Session Summary
Review/Q&A session on the CS-SERVER degraded RAID-1 (failing C: drive). Pulled up the
2026-06-15 CS-SERVER RAID/VPN session log and the client wiki hardware warning, and recapped
the state: C: (Virtual Disk2, RAID-1, ~297 GB usable) is DEGRADED on a single surviving 320 GB
5400 RPM laptop spindle after the mirror's other member failed. The healthy D: array and the
single-DC / newly-installed-but-unverified cloud backup posture were summarized. No changes
made to the server — this was an advisory/planning conversation.
Answered three hardware questions for Howard ahead of a possible drive swap. (1) Hot-swap: yes,
the R610 hot-plug backplane + SAS 6/iR support hot-swapping the already-removed failed member
with the server running; gating items are a verified backup first and correct-drive
identification, not power state. (2) SSD limits: the SAS 6/iR (LSI 1068E, SATA II / 3 Gbps, no
TRIM, no Dell certified-drive lockout) sets the constraints — min size >= 320 GB raw (so 480/500 GB
class; 240/256 GB too small to rebuild), 2.5" SATA negotiating to 3 Gbps, enterprise SSD with
PLP (no TRIM => avoid consumer QLC/DRAM-less), <= 2 TB, 512e; buy two identical for the
rebuild-then-swap. (3) The two installed OS drives: surviving 0:0:2 Hitachi HTS545032B9A300 and
failed 0:0:3 WDC WD3200BEVT, both 320 GB SATA.
Howard then asked what caused a network spike at Cascades today around noon. No spike is
recorded in the logs (that is live monitoring data), but the only network change today was the
VLAN 30 voice build (port-16 bounce + desktop re-DHCP onto 10.0.30.201, pfSense filter reload),
which is the leading correlation candidate; the larger phone cutover was deferred to tonight.
Per Howard's follow-up command, the next step is to pull the UniFi skill and investigate the
Cascades site (va6iba3v on UOS 172.16.3.29) live for the noon window — that investigation
begins after this save.
## Key Decisions
- Treated the failing-drive discussion as advisory only; no server-side commands run this
session (the standing rule is no drive work until the cloud backup completes and verifies).
- Recommended spec: two 480 GB enterprise 2.5" SATA SSDs with power-loss protection (e.g.
Solidigm D3-S4520 480 GB or Samsung PM893 480 GB) for the rebuild-then-swap.
- Offered to re-pull OMSA live before any physical swap to confirm the surviving Hitachi has not
also degraded since 2026-06-15.
## Problems Encountered
- None. Advisory session.
## Configuration Changes
- None on CS-SERVER or any infrastructure. Session log written only.
## Credentials & Secrets
- None created or discovered this session.
## Infrastructure & Servers
- **CS-SERVER** — Cascades DC/file/Hyper-V host, Dell PowerEdge R610 (~2009), Win Server 2019
Std, 48 GB RAM. GuruRMM agent `c39f1de7-d5b6-45ae-b132-e06977ab1713`. LAN 192.168.2.254.
- C: = Virtual Disk2, RAID-1, ~297 GB, DEGRADED. Surviving member `0:0:2` Hitachi
HTS545032B9A300 (320 GB SATA, 5400 RPM). Failed member `0:0:3` WDC WD3200BEVT (320 GB SATA,
Critical/Removed).
- D: = Virtual Disk0, RAID-1, two 1.2 TB SAS, OK. Spare `1:0:4` 1.2 TB SAS "Ready" (wrong size
to rebuild the 320 GB mirror).
- Controller: SAS 6/iR Integrated (LSI 1068E), SATA II 3 Gbps, no TRIM, no certified-drive
lockout.
- Cloud backup (MSP360/CloudBerry -> ACG-backup) installed/started 2026-06-15, not yet verified.
- **Cascades UniFi** — site `va6iba3v` on UOS controller `172.16.3.29` (for the spike
investigation).
- **Cascades pfSense** — `192.168.0.1`.
## Commands & Outputs
- No commands run against infrastructure this session.
## Pending / Incomplete Tasks
- **Investigate the noon network spike** via the UniFi skill (Cascades site va6iba3v on UOS
172.16.3.29) + optionally pfSense WAN throughput — begins after this save.
- **CS-SERVER drive remediation** still gated on: backup first full completes + verifies +
confirmed image/bare-metal + retention set. Then rebuild-then-swap to 2x 480 GB enterprise
SATA SSDs. Re-pull OMSA live before any physical action.
- DC migration off the EOL R610 remains the strategic fix.
## Reference Information
- Prior drive session: `clients/cascades-tucson/session-logs/2026-06/2026-06-15-howard-cs-server-raid-vpn-reset.md`
- Today's VLAN build: `clients/cascades-tucson/session-logs/2026-06/2026-06-17-howard-voice-vlan30-build.md`
- Client wiki: `wiki/clients/cascades-tucson.md`
- GuruRMM agent (CS-SERVER): `c39f1de7-d5b6-45ae-b132-e06977ab1713`. RMM API http://172.16.3.30:3001.

View File

@@ -1,3 +1,6 @@
# plaintext credential note - never commit
Oauth.txt
# Large local-only binary artifacts (WizTree disk scans, dumps) - keep on the machine, never commit
local-artifacts/

View File

@@ -17,6 +17,8 @@ Categories (the `[type]` tag): _(none)_ = skill/command execution failure ·
<!-- Append entries below this line -->
2026-06-17 | GURU-5070 | mailbox/365-mail | [correction] claimed in a prior session that /mailbox skill + memories were repointed off the deleted fabb3421 to the 365-mail suite, but mailbox.md still hardwired fabb3421 (token 401 AADSTS700016). Correct app is the dedicated ComputerGuru Mailbox app 1873b1b0 via get-token.sh 'mailbox' tier (cert auth); repointed mailbox.md + feedback_365_remediation_tool.md 2026-06-17. Lesson: verify the edit actually landed before reporting it done.
2026-06-17 | Howard-Home | wiki-compile/coord | [friction] skill doc Phase 6 shows 'lock release claudetools wiki/<type>/<slug>' but coord.py takes 'lock release <id>'; wasted a round-trip. Capture the lock id from claim output and release by id. [ctx: ref=wiki-compile-skill]
2026-06-17 | Howard-Home | unifi/controller-write | [friction] UniFi OS controller PUT (rest/device port_overrides) returned 403 without CSRF. Fix: login with -D headers, read 'x-updated-csrf-token' (or decode csrfToken from TOKEN cookie JWT), send as X-CSRF-Token on PUT/POST/DELETE
@@ -25,6 +27,12 @@ Categories (the `[type]` tag): _(none)_ = skill/command execution failure ·
2026-06-17 | Howard-Home | pfsense/cascades-voice-vlan | [correction] assumed new RFC1918 alias + DNS-to-firewall:53/123 rules + clone VLAN20 for VOICE isolation; correct is clone the GUEST VLAN (VLAN50/igc1.50, the only actually-isolated net: 3x literal-CIDR quick blocks + pass any) and hand out PUBLIC DNS 8.8.8.8/1.1.1.1 via DHCP. VLAN20 is NOT isolated; config.xml rules were mismapped/not matching live pfctl -sr [ctx: ref=voice-vlan-cutover.md; lesson=read pfctl -sr not just config.xml]
2026-06-17 | GURU-5070 | ssh/plink-windows | [friction] plink -pw with a special-char password through Git-bash -> native plink.exe got MANGLED (CommandLineToArgvW) -> 'Access denied', led to a wrong 'stale cred' conclusion + wasted DMs. The pw was correct. Use system OpenSSH KEY auth (or pass pw via stdin/file), never plink -pw, for special-char passwords on Windows [ctx: ref=feedback_windows_quote_stripping;host=192.168.0.9]
2026-06-17 | GURU-5070 | vault/d2testnas-cred | [correction] CORRECTION: the d2testnas password is NOT stale - it worked for Mike. My plink '-pw' failure was special-char mangling through Git-bash -> native plink.exe (Windows CommandLineToArgvW). Use KEY auth (system OpenSSH) for password-special-char hosts [ctx: ref=feedback_windows_quote_stripping;host=192.168.0.9]
2026-06-17 | GURU-5070 | vault/d2testnas-cred | vaulted clients/dataforth/d2testnas credentials.password REJECTED by 192.168.0.9 (Access denied) - stale/wrong; needs update. Key auth is the reliable path [ctx: host=192.168.0.9]
2026-06-17 | GURU-5070 | agy | gemini returned no response (empty after 3 attempts) [ctx: mode=search err=Attempt 1 failed: You have exhausted your capacity on this model. Your quota wil]
2026-06-17 | GURU-5070 | agy | gemini auth/login failure [ctx: mode=search]

View File

@@ -0,0 +1,134 @@
# Dataforth Test-Datasheet Pipeline — Fix Spec (hardened)
**Date:** 2026-06-17 · **Host:** AD2 (`C:\Shares\testdatadb`, Node + PostgreSQL 18) · **Status:** SPEC for review — implementation driven on AD2
**Inputs:** AD2 diagnosis (`DATASHEET-RTD-BUG-DIAGNOSIS`, `PARSING-FIDELITY-VERDICT`, `MISSING-UNITS-REPORT`, `CONFLICT-RULE-FIX-PROPOSAL`) + independent multi-AI review (Grok adversarial + Gemini). Owner direction on retest handling (Mike, 2026-06-17).
All defects are in the **regeneration/ingestion** pipeline that replaced the cryptolocker-destroyed original parser/publisher. Source test data is intact (DB matches staged originals across 11,239 records, 0 parse faults).
---
## 0. Ground truth (verified live, 2026-06-17)
- `test_records`: 473,780 rows = 473,780 distinct `serial_number`**exactly one row per serial**.
- Unique constraints: `uq_test_records_sn` UNIQUE(serial_number) [operative]; redundant UNIQUE(log_type,model_number,serial_number,test_date,test_station).
- Columns incl. `raw_data` (verbatim .DAT), `overall_result`, `api_uploaded_at`, `forweb_exported_at`, `datasheet_exported_at`, `work_order`.
- **Deployed `database/import.js` uses `ON CONFLICT (serial_number)`** with `WHERE overall_result='FAIL' OR (EXCLUDED PASS AND EXCLUDED.test_date > test_records.test_date)`.
- **WARNING — repo drift:** the repo copy `…/implementation/database/import.js` is STALE (shows a 5-tuple `ON CONFLICT`). **Edit the DEPLOYED file; reconcile the repo copy after.** Verify every file's deployed content before changing it.
## 0a. CROSS-CUTTING — re-publication discipline (MANDATORY for any fix that changes cert text)
Every fix below that alters rendered output must be published deliberately, not by blanket cache-clear:
1. **Diff before re-push.** For each candidate serial, render OLD vs NEW and only act where output actually changes. Do not clear `api_uploaded_at`/`forweb_exported_at` for unchanged renders.
2. **Re-POST semantics.** Hoffman bulk API is idempotent — returns `Unchanged` when content matches, overwrites when it differs (per diagnosis §6). Confirm this holds before bulk re-push; watch for dedup/version behavior.
3. **Targeted, staged rollout.** Re-publish in bounded batches (start with the Phytec 102), confirm counts, then widen. Log every batch.
4. **Rollback.** Keep the prior rendered text (or the prior template commit) for every re-published serial so a bad batch can be reverted.
5. **Audit framing.** A cert's text changing after initial publication is a bug correction — record it (ticket #32441 + an internal change log of affected serial ranges) so it's defensible in an audit.
---
## Fix 1 — Defect A: RTD input labeled resistance, not temperature (the audit finding)
**File:** `templates/datasheet-exact.js` · **Scope:** ~24,000 certs (8B35, DSCA34, SCM5B34/35) · **Status:** fix written, needs scope/ground-truth proof.
**Root cause:** `getSensorNum()` returns 7 for RTD sentypes (`s.includes('RTD')`); two branches on `sensorNum===7` emit `' Rin (ohms)'` header + unsigned value. Dataforth RTD certs report the input as Temperature (deg C); raw_data stimulus is already deg C.
**Approach (AD2 diff):** fold `sensorNum===7` into the temperature branch (36) for header (`' Temp. (C)'`) and value (`formatSigned`). Leave the `i===13` ohm/ohm Lead-R override intact.
**Hardening (multi-AI):**
- "15 renders changed / 0 non-RTD" proves a branch moved, **not** correctness. Before deploy: (a) **byte-compare** the fixed render against the staged original `.TXT` for a real RTD sample (8B35 incl. SN 179553-13, DSCA34, SCM5B34/35) — require exact match; (b) **confirm RTD-detection coverage**: count RTD-family rows in the DB (`model_number LIKE '%34%'/'%35%'` etc.) and confirm the regenerator's `s.includes('RTD')` actually classifies all of them as 7 (only 15 changing across 184 renders may mean the sample was RTD-thin, or some RTD sentypes aren't matched).
**Risk:** LOW-MODERATE (localized; main risk is under-detecting which modules are RTD). **Deploy:** after byte-match + coverage check; then targeted re-push (Phytec 102 first).
---
## Fix 2 — Defect B: DSCA Final-Test table wrong / dropped lines
**File:** `templates/datasheet-exact.js` (`DATA_LINES['DSCA']`, `buildTSpecs()` DSCA branch, accuracy-block titles) · **Scope:** up to ~78,000 DSCA certs · **Status:** NEEDS DESIGN — highest structural risk.
**Root cause:** a single hardcoded `DATA_LINES['DSCA']` + single DSCA `buildTSpecs` branch; real DSCA modules have **per-subtype** Final-Test layouts → wrong names, garbage specs (`< 0 mA`, `+/- 0 %`), rows misaligned, lines dropped (e.g. Output Noise on DSCA38-05). Accuracy block also uses 5B/8B titles (`Vout (V)` / `====`) instead of DSCA's (`Output (V|mA)` / `----`).
**Approach (multi-AI consensus — REVISED from AD2's DSCFIN.DAT idea):**
- **Do NOT reverse-engineer `DSCFIN.DAT`** (legacy DOS config; QB writer has hardcoded overrides outside the config → guaranteed edge-case drift).
- **Derive per-subtype templates from the staged original `.TXT`** (the actual correct customer certs are ground truth): group staged DSCA `.TXT` by subtype, extract each subtype's Final-Test parameter name/unit/spec list and accuracy titles directly.
- **Key subtype selection** on `model_number` (prefix) + `SENTYPE` / output-signal type. Build an explicit subtype→layout map.
- Fix the DSCA accuracy block titles/separators (`Output (V|mA)`, `----`).
**Validation (hard gate):** generate ALL DSCA certs and **byte-for-byte diff vs the staged originals** across every subtype; zero-delta required. Any subtype with no staged original → flag, do not guess.
**Risk:** HIGH (largest population + wrong numeric labels/limits). The longer pole; do after 1/3/4. **Deploy:** only after zero-delta validation per subtype; re-publish in batches with diff-gating.
---
## Fix 3 — Retest handling: latest test supersedes (OWNER DIRECTION + hardening)
**File:** deployed `database/import.js` (`ON CONFLICT (serial_number)` WHERE clause) · **Scope:** ~311 stuck units now + all future retests · **Status:** design owner-set, implementation hardened.
**Owner rule (Mike):** one row per serial; a new test on the SAME unit (same model / "everything else checks out") **supersedes anything prior — latest test wins**. A reused serial on a DIFFERENT product is NOT the same unit — recognize the collision, don't blindly overwrite.
**Why the current rule fails:** strictly-greater date + date-only granularity → same-day reruns can't replace (~311 stuck on a non-final run).
**Approach (hardened — both AIs refute pure scan-order as the recency signal):**
- Conflict on `serial_number`. Update when the incoming row is the SAME unit and is genuinely newer:
- `EXCLUDED.model_number = test_records.model_number` (same unit) **AND** `EXCLUDED.raw_data IS DISTINCT FROM test_records.raw_data` (real change; avoids re-push churn) **AND** incoming is at least as new.
- **Recency must not rely on import scan order alone.** Use `EXCLUDED.test_date >= test_records.test_date`, and break same-date ties with a **monotonic signal captured at parse time** — source `.DAT` mtime or an ingest sequence number (add a column, e.g. `ingest_seq` / `source_mtime`). Last-by-(date, tiebreaker) wins.
- **Collision handling (different `model_number`, same serial):** do NOT overwrite. Route to a `test_records_quarantine` table (or a flagged status) + alert. These are the reused generic serials (`1-1`, `1-2`) — genuinely different units.
- Keep the `FAIL → PASS` override.
**Validation:** ingest the owner's **4-retest sample IN REVERSE chronological order**; final DB state MUST be the mathematically newest run. Re-run `tools/validate-parsing.js`; same-day violations → ~0. Confirm the 311 settle on the latest run.
**Risk:** HIGH if scan-order is trusted (a future bulk re-import could overwrite newer with older across the whole DB). MITIGATED by the date+tiebreaker rule. **Deploy:** after the reverse-order sample passes; then re-import + diff-gated re-push of the 311.
---
## Fix 4 — Importer drops letter-prefixed encoded serials
**File:** `parsers/multiline.js` (serial/date regex) · **Scope:** ~9,510 records / 840 serials / 141 models · **Status:** needs design (PK-boundary mutation).
**Root cause:** `line.match(/^"(\d+-\d+[A-Za-z]?)","(\d{2}-\d{2}-\d{4})"$/)``\d+-\d+` requires leading digits, so DOS 8.3-encoded serials (`10243-1``A243-1`; first two digits → letter, `prefix = charCodeAt(0)-55`) never match → whole record silently dropped.
**Approach (hardened — keep the literal, both AIs):**
- Widen regex to allow an optional leading letter: `/^"([A-Za-z]?\d+-\d+[A-Za-z]?)","(\d{2}-\d{2}-\d{4})"$/`.
- **Store BOTH:** add `raw_serial_number` (the literal file bytes, e.g. `A243-1`) and keep `serial_number` = decoded numeric (`10243-1`). UNIQUE stays on `serial_number`. Preserves a perfect audit trail.
- Decode only when the captured serial matches `^[A-Za-z]\d`.
**Pre-flight (MANDATORY before any import):** run the parser over the ~9,510 dropped records read-only → emit CSV `raw_serial, decoded_serial, model_number`. **Search decoded serials for collisions against the existing 473,780 rows.** For each collision decide policy (a decoded `A243-1` colliding with a genuine `10243-1` of a different model = a real conflict — quarantine, don't merge two physical units). Only proceed once collisions are enumerated and a policy set.
**Risk:** MODERATE (transforms the uniqueness key + customer lookup id). **Deploy:** after the collision CSV is clean/resolved; the re-import re-exercises the upsert path → run under the Fix-3 rule, diff-gated re-push.
---
## Fix 5 — Backfill 379 cryptolocker-era units from staged originals
**Scope:** 379 units (Oct 2025Jan 2026, 3 stations), no surviving `.DAT`; staged `.TXT` exist · **Status:** operational.
**Key fact:** the staged `.TXT` were produced by the ORIGINAL (pre-crypto) renderer → already correct (no Defect A/B).
**Approach (both AIs — publish directly, don't round-trip):**
- Add `legacy_cert_text` column. Insert the 379 with `raw_data = NULL`, `legacy_cert_text` = the staged `.TXT` content.
- Publisher serves `legacy_cert_text` when `raw_data IS NULL` (bypasses regeneration).
- Do NOT reverse `.TXT`→raw_data→re-render (two translation-loss points; guarantees drift).
**Validation:** cross-reference the 379 serials against the ERP / work-order system to confirm they are valid shipped units before exposing via the API. Spot-check rendered vs staged text.
**Consistency note:** these rows are permanently "original-renderer" output, divergent from what the fixed template would emit. Acceptable (and safer) given no raw source; document the class.
**Risk:** LOW (bounded 379, immutable text) — but zero tolerance for wrong text (no raw fallback). **Deploy:** after ERP cross-check.
---
## Risk ranking (combined) & recommended order
| Rank | Fix | Why |
|---|---|---|
| 1 (tie) | **Fix 3** retest | scan-order recency could corrupt DB-wide on any future re-import (Gemini #1) |
| 1 (tie) | **Fix 2** DSCA | largest scope + wrong numeric labels/limits; design-heavy (Grok #1) |
| 3 | **Fix 4** serials | mutates the uniqueness/lookup key; merge risk |
| 4 | **Fix 1** RTD | localized; risk is under-scoping RTD detection |
| 5 | **Fix 5** backfill | small, immutable, but no raw fallback |
**Suggested execution order (lowest-risk customer win first, hardest last):**
1. **Fix 1 (RTD label)** — after byte-match + scope check → clears the audit + corrects the Phytec 102 (deploy tonight candidate).
2. Re-publish Phytec 102 (diff-gated).
3. **Fix 4 (serial decode)** — after clean collision CSV.
4. **Fix 3 (retest rule)** — after reverse-order sample passes.
5. **Fix 2 (DSCA rebuild)** — after per-subtype zero-delta validation.
6. **Fix 5 (backfill 379)** — after ERP cross-check.
## Open items needing an owner decision
- **Fix 3:** add a real tie-breaker column (`ingest_seq`/`source_mtime`) vs accept `test_date >=` + scan-order? (recommend the column.)
- **Fix 3/4 collisions:** quarantine table + alert vs reject-and-log? (recommend quarantine table.)
- **Fix 4:** add `raw_serial_number` column (recommended) — schema change.
- **Fix 5:** add `legacy_cert_text` column + publisher branch (recommended) — schema + publisher change.
- **Tonight:** scope = Fix 1 + Phytec re-push only (smallest safe win); the rest staged after.