sync: auto-sync from GURU-5070 at 2026-07-06 13:43:08

Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-07-06 13:43:08
This commit is contained in:
2026-07-06 13:43:38 -07:00
parent 3cc9b22594
commit b20985d241
5 changed files with 368 additions and 221 deletions

View File

@@ -218,3 +218,4 @@
- [Claude tui fullscreen crash](feedback_claude_tui_fullscreen_crash.md) — "flicker-free" prompt writes tui=fullscreen; instant-exit crash loop on some Windows terminals; set tui=default (reinstall does NOT fix) - [Claude tui fullscreen crash](feedback_claude_tui_fullscreen_crash.md) — "flicker-free" prompt writes tui=fullscreen; instant-exit crash loop on some Windows terminals; set tui=default (reinstall does NOT fix)
- [Backup targets never guessed](feedback_backup_targets_never_guessed.md) — billed-vs-running mismatches are billing questions for Mike/Winter; never infer/deploy backup targets - [Backup targets never guessed](feedback_backup_targets_never_guessed.md) — billed-vs-running mismatches are billing questions for Mike/Winter; never infer/deploy backup targets
- [Fire #dev-alerts for client/RMM work](feedback_fire_dev_alerts_for_client_work.md) — Howard+Mike watch #dev-alerts Discord for live visibility into what techs/sessions touch. Fire post-bot-alert.sh ([DEV]/[RMM]/[DEPLOY]/[GURURMM] prefix -> #dev-alerts) at the START of any RMM/Dev or active-client-machine action. No CLAUDE.md rule yet — this memory is the rule. - [Fire #dev-alerts for client/RMM work](feedback_fire_dev_alerts_for_client_work.md) — Howard+Mike watch #dev-alerts Discord for live visibility into what techs/sessions touch. Fire post-bot-alert.sh ([DEV]/[RMM]/[DEPLOY]/[GURURMM] prefix -> #dev-alerts) at the START of any RMM/Dev or active-client-machine action. No CLAUDE.md rule yet — this memory is the rule.
- [M365 app: SharePoint needs CERT](reference_m365_app_sharepoint_rest_vs_graph.md) — SP app-only works via the CERT (get-token.sh <tenant> sharepoint); the secret gives "Unsupported app only token". Graph app-only uses the secret.

View File

@@ -0,0 +1,38 @@
---
name: reference_m365_app_sharepoint_rest_vs_graph
description: ComputerGuru M365 app suite — SharePoint app-only WORKS but requires the CERT (client_assertion), not the secret. Secret => "Unsupported app only token". Use get-token.sh <tenant> sharepoint.
metadata:
type: reference
---
The ACG **Tenant Admin app** (`709e6eed-0711-4875-9c44-2d3518c47063`) has **full SharePoint app-only
access** — `Sites.FullControl.All` on the SharePoint resource — but it is gated by an auth-method rule
that keeps biting:
- **SharePoint app-only REQUIRES a CERTIFICATE (client_assertion). A `client_secret` token is rejected**
with **`Unsupported app only token`** on EVERY SharePoint endpoint (REST `/_api/...` and CSOM
`/_vti_bin/client.svc/ProcessQuery`). This is a SharePoint platform rule, not a missing grant.
- **Graph** app-only accepts the secret fine — so Graph SharePoint calls (driveItem `createdBy`/
`lastModifiedBy`/`retentionLabel`, item `/permissions` + inheritance, `/groups` + members) work with
the secret and are the right tool for *investigation*.
**Do NOT hand-roll a SharePoint token from the secret. Use the remediation-tool cert tiers:**
```bash
cd .claude/skills/remediation-tool
bash scripts/get-token.sh <tenant> sharepoint # content resource <name>.sharepoint.com (cert)
bash scripts/get-token.sh <tenant> sharepoint-admin # admin resource <name>-admin.sharepoint.com (cert)
```
`get-token.sh` forces cert for the `sharepoint*` tiers automatically; the minted token's `roles` =
`["Sites.FullControl.All"]`. The cert is vaulted in `msp-tools/computerguru-tenant-admin.sops.yaml`
(`cert_thumbprint_b64url` + `cert_private_key_pem_b64`). Tenant arg = the domain (`birthbiologic.com`).
**Use the CERT (SP REST/CSOM) for:** list settings (`ForceCheckout`, `EnableModeration`/content approval,
versioning), per-item `CheckoutUserId`/checkout state, re-stamping `Author`/`Editor` (Created By /
Modified By), site lock, tenant settings. Graph can't do these.
Reference (authoritative): `.claude/skills/remediation-tool/references/app-permissions-and-sharepoint.md`
and `app-suite.md`. **Before ever telling the user "the tool can't do X" on SharePoint, use the cert
tier and verify** — the "Unsupported app only token" wall means *wrong auth method (secret)*, NOT no access.
See [[birth-biologic]] (migrated content owned by "SharePoint App" => greyed-out Move; fix = re-stamp
Author/Editor via cert CSOM `SystemUpdate`). Open question (Mike, 2026-07-06): standardize ALL M365
app-only auth on cert (cert is resource-agnostic + more secure) to kill this secret-vs-cert friction.

View File

@@ -0,0 +1,105 @@
## User
- **User:** Mike Swanson (mike)
- **Machine:** GURU-5070
- **Role:** admin
## Session Summary
Birth Biologic reported (warranty) that users "can no longer move files within SharePoint" — Move/Copy
options greyed out and drag-drop silently failing. Investigated the migrated Quality library and
confirmed Mike's hypothesis: **every migrated folder/file is authored by `SharePoint App`** (the app-only
service principal that ran the Datto->SharePoint Graph/SPMT uploads) — there is no human owner on any of
it. By contrast, a folder a real user created (`Quality General`, author **Julie Beck**) is normal and
movable. This is the whole problem: SharePoint disables Move/Copy/Rename on items whose author is a
system/app account, so users can manage their own content but not the app-migrated content.
Ruled out the other candidate causes via Graph and (once authenticated correctly) SharePoint REST:
**no retention/records labels** on the items; **library `ForceCheckout=false`** and **`EnableModeration=false`**
(no require-checkout, no content approval); **no files checked out** (`CheckoutUserId` null on all sampled
items); and **inheritance is intact** with the **Members group granted `write`** — the Quality staff (Julie
Beck, Brandy Burgess, Mary Ster, Vicki Fountain, Alicia Meneely, Kristin Steen + sysadmin/operations) are
all Members, so they *have* Edit. That leaves the app authorship as the sole cause.
The investigation stalled twice on SharePoint access before landing on the correct method. Using the
Tenant Admin app's **client_secret** against SharePoint REST returned `Unsupported app only token`; I
initially (wrongly) concluded app-only SP REST was blocked and wrote a memory saying to fall back to
delegated/PnP. Mike corrected this: the app suite has a dedicated SharePoint tier — SharePoint app-only
**requires the certificate, not the secret**. Minting via `get-token.sh birthbiologic.com sharepoint`
produced a cert token with `roles=["Sites.FullControl.All"]` and SP REST worked immediately. Rewrote the
memory to the correct cert-based guidance.
The fix was scoped but not yet executed (awaiting Mike's go + target-owner decision): re-stamp
`Author`/`Editor` on the app-owned items to a real user via cert-based CSOM `SystemUpdate` (SystemUpdate
so it does not bump Modified dates — matters for a QMS/compliance library), test on one folder, have a
Quality user confirm Move works, then run across all migrated libraries (Quality, Admin, Donor Services,
Supply — all migrated the same way, so all affected). Mike also raised standardizing all M365 app-only
auth on cert (recommended — cert is resource-agnostic and kills the secret-vs-cert friction).
## Key Decisions
- **Diagnosis = app/System-Account authorship**, not permissions/checkout/retention — all three ruled out
with live data; the `SharePoint App` vs `Julie Beck` author contrast is the tell.
- **Fix via CSOM `SystemUpdate`** (not a plain REST MERGE) to preserve Modified dates on QMS records.
- **Do not bulk-run yet** — test-on-one + user confirmation first; this is a live client library.
- **Recommend cert-only for all M365 app-only tasks** — a cert works for Graph/EXO too, is MS best
practice, and removes the secret-vs-cert branching that wasted time this session.
## Problems Encountered
- **Used the client_secret for SharePoint** -> `Unsupported app only token`; wrongly concluded "no app-only
SP access" and wrote a misleading memory. Correct method: the **cert** tier via
`get-token.sh <tenant> sharepoint` (`Sites.FullControl.All`). Memory rewritten; logged as a correction.
- **Group lookup by `Quality Systems Department` returned empty first** because I filtered on an assumed
rename; the group is still named `Quality Systems Department`. Re-queried with `startswith(displayName,'Quality')`.
- **`/tmp` path tripped the block-tmp-path hook** on a Bash call (Git-Bash `/tmp` mismatch); reran with a
scratchpad path.
## Configuration Changes
- No changes to the BirthBio tenant/SharePoint yet (investigation only; the re-author fix is pending go).
- Repo: rewrote `.claude/memory/reference_m365_app_sharepoint_rest_vs_graph.md` (secret->cert correction) +
updated its `MEMORY.md` index line. Added this session log. (BUG-024/musl log from earlier today is separate.)
## Credentials & Secrets
- No new secrets. SharePoint access = **Tenant Admin app CERT** (vault `msp-tools/computerguru-tenant-admin.sops.yaml`:
`cert_thumbprint_b64url` + `cert_private_key_pem_b64`), minted by remediation-tool `get-token.sh <tenant> sharepoint`.
The app's `client_secret` (same entry) is Graph/EXO only — rejected by SharePoint.
- **Gap flagged:** BirthBio's `sysadmin@birthbiologic.com` **M365** password is NOT in the SOPS vault (only the
Google Workspace one is, `clients/birth-biologic/google-workspace.sops.yaml`). Mike says the M365 sysadmin
creds are in **1Password** for BirthBio. Not needed for the fix (cert tier suffices), but worth vaulting.
## Infrastructure & Servers
- **Tenant:** birthbiologic.com / `19a568e8-9e88-413b-9341-cbc224b39145`.
- **Tenant Admin app:** `709e6eed-0711-4875-9c44-2d3518c47063` (secret = Graph/EXO; CERT = SharePoint `Sites.FullControl.All`).
- **QSD site:** `https://birthbiologic.sharepoint.com/sites/QualitySystemsDepartment`; site id `3173c017-58bd-406a-8858-2c969667336f`;
drive `b!F8BzMb1YakCIWCyWlmczb09LHqtxDxVMpLT6kAwYmsM7NUY4oPLSRq7ng3tJq-E9`.
- **QSD M365 group:** `QualitySystemsDepartment@birthbiologic.com`, id `166dbc15-f60b-4dfc-9581-236980032da4`;
Members (=site Edit): sysadmin, Julie Beck, Brandy Burgess, Mary Ster, Vicki Fountain, Alicia Meneely, Kristin Steen, Operations.
- **Documents library:** `ForceCheckout=false`, `EnableModeration=false`, `EnableVersioning=true`, `EnableMinorVersions=false`.
## Commands & Outputs
- Mint SP cert token: `cd .claude/skills/remediation-tool && bash scripts/get-token.sh birthbiologic.com sharepoint`
-> token roles `["Sites.FullControl.All"]`. (`sharepoint-admin` tier = the `-admin.sharepoint.com` resource.)
- Ownership (Graph): `GET /drives/{drive}/root/children?$select=name,createdBy,lastModifiedBy` -> all `SharePoint App`.
- Author contrast (SP REST): `GET .../lists/getbytitle('Documents')/items?$select=FileLeafRef,CheckoutUserId,Author/Title,Editor/Title&$expand=Author,Editor`
-> migrated = `SharePoint App`, `Quality General` = `Julie Beck`; `CheckoutUserId` null throughout.
- Secret-vs-cert: SP REST with the **secret** token -> `Unsupported app only token`; with the **cert** token -> works.
## Pending / Incomplete Tasks
- **Execute the fix (warranty):** re-stamp `Author`/`Editor` on app-owned items to a real user via cert CSOM
`SystemUpdate`. **Decision needed from Mike:** target owner (`sysadmin@` neutral, or a Quality lead like
Julie/Brandy). Test one folder -> Quality user confirms Move -> bulk across Quality, Admin, Donor Services, Supply.
- **Cert-only standardization (follow-up):** switch `get-token.sh` to cert-first for every tier; register the
cert on any secret-only apps; retire secrets. Prevents this recurring.
- **Vault the BirthBio `sysadmin@` M365 password** (currently only in 1Password).
## Reference Information
- Authoritative SP access doc: `.claude/skills/remediation-tool/references/app-permissions-and-sharepoint.md` + `app-suite.md`.
- Memory: `.claude/memory/reference_m365_app_sharepoint_rest_vs_graph.md` (corrected to cert).
- Wiki: `wiki/clients/birth-biologic.md` (File Storage / SharePoint Site Map; QSD site+drive IDs already recorded there).
- Symptom class: greyed-out Move/Copy/Rename + silent drag-drop fail on items authored by `SharePoint App`/`System Account`.

View File

@@ -19,6 +19,8 @@ Categories (the `[type]` tag): _(none)_ = skill/command execution failure ·
<!-- Append entries below this line --> <!-- Append entries below this line -->
2026-07-06 | GURU-5070 | remediation-tool/sharepoint | [correction] assumed SP REST app-only was blocked ('Unsupported app only token') and to use delegated/PnP; correct is the app suite has a CERT-based sharepoint tier — get-token.sh <tenant> sharepoint mints a cert token with Sites.FullControl.All. Secret is Graph/EXO only. [ctx: client=birth-biologic ref=reference_m365_app_sharepoint_rest_vs_graph]
2026-07-06 | GURU-5070 | wiki-compile | [correction] ran --full (Sonnet rebuild) for a routine post-work capture; plain /wiki-compile client:<slug> (update mode) is the fast incremental path for 'just did work, capture it' — reserve --full for structural drift/clean rebuilds 2026-07-06 | GURU-5070 | wiki-compile | [correction] ran --full (Sonnet rebuild) for a routine post-work capture; plain /wiki-compile client:<slug> (update mode) is the fast incremental path for 'just did work, capture it' — reserve --full for structural drift/clean rebuilds
2026-07-06 | GURU-5070 | dev-alerts/visibility | [correction] did substantial RMM/Dev + live-client-machine work (Lonestar Unraid boot-config edits, service cycling, v0.6.77 deploy, promotions) WITHOUT firing #dev-alerts; Howard+Mike lost live visibility. Fire post-bot-alert.sh at the START of such work. [ctx: ref=feedback_fire_dev_alerts_for_client_work] 2026-07-06 | GURU-5070 | dev-alerts/visibility | [correction] did substantial RMM/Dev + live-client-machine work (Lonestar Unraid boot-config edits, service cycling, v0.6.77 deploy, promotions) WITHOUT firing #dev-alerts; Howard+Mike lost live visibility. Fire post-bot-alert.sh at the START of such work. [ctx: ref=feedback_fire_dev_alerts_for_client_work]

View File

@@ -168,7 +168,7 @@ Site IDs hardcoded in `$SITE_MAP` hashtable in the migration script. QSD site ID
- **SharePoint 409 Conflict on retry:** If a chunked upload session is interrupted, a partial item remains in SharePoint. Subsequent upload sessions against the same path return 409 Conflict. Fix: DELETE the item before creating a new upload session. - **SharePoint 409 Conflict on retry:** If a chunked upload session is interrupted, a partial item remains in SharePoint. Subsequent upload sessions against the same path return 409 Conflict. Fix: DELETE the item before creating a new upload session.
- **Long Windows paths (>260 chars) require `\\?\` prefix:** The Datto source tree contains paths exceeding MAX_PATH. Use `\\?\` prefix for `[IO.File]` reads in PowerShell. Note: `Rename-Item` and `File.Move` in PS5.1 do NOT support `\\?\` — use `robocopy` or SPMT for long-path rename/move operations. - **Long Windows paths (>260 chars) require `\\?\` prefix:** The Datto source tree contains paths exceeding MAX_PATH. Use `\\?\` prefix for `[IO.File]` reads in PowerShell. Note: `Rename-Item` and `File.Move` in PS5.1 do NOT support `\\?\` — use `robocopy` or SPMT for long-path rename/move operations.
- **SharePoint single-session upload throttles ~40 Mbps:** For large migrations, parallel-stream uploaders (multiple concurrent file uploads, larger chunk sizes) would significantly improve throughput. - **SharePoint single-session upload throttles ~40 Mbps:** For large migrations, parallel-stream uploaders (multiple concurrent file uploads, larger chunk sizes) would significantly improve throughput.
- **Tenant Admin app cannot delete M365 groups (403):** The Tenant Admin app has GroupMember write only, not Group.ReadWrite.All. `DELETE /groups/{id}` returns 403 via Tenant Admin app. Use the **User Manager app** for group deletes (returns HTTP 204). Also: the Tenant Admin app cannot manage SP site lock/spoke-site grants (`Unsupported app only token` on SP REST) — use PnP PowerShell as SharePoint Admin. - **Tenant Admin app cannot delete M365 groups (403):** The Tenant Admin app has GroupMember write only, not Group.ReadWrite.All. `DELETE /groups/{id}` returns 403 via Tenant Admin app. Use the **User Manager app** for group deletes (returns HTTP 204). Also: **SharePoint app-only REST/CSOM requires the Tenant Admin app's CERTIFICATE, not the secret** — a `client_secret` token returns `Unsupported app only token` on every SP endpoint. Mint the cert token via `get-token.sh <tenant> sharepoint` (roles `Sites.FullControl.All`); do NOT reach for the secret or conclude "no SP access" (corrected 2026-07-06; the earlier "use PnP" note was wrong — the cert tier works app-only).
- **Byte-array stringification bug — RETIRED path:** The 2026-06-26 custom-script upload path passed file bytes as `"$bytes"`, which stringified the .NET byte array to space-separated decimal text instead of raw binary. Corrupt files are inflated ~3-4x; headers are decimal (e.g. `80 75 3 4...` for PK, `37 80 68 70...` for %PDF). 84 files were corrupted and restored from Datto source. This code path is permanently retired. **Never stringify a byte array in PowerShell** — use `[IO.File]::WriteAllBytes` for binary output. - **Byte-array stringification bug — RETIRED path:** The 2026-06-26 custom-script upload path passed file bytes as `"$bytes"`, which stringified the .NET byte array to space-separated decimal text instead of raw binary. Corrupt files are inflated ~3-4x; headers are decimal (e.g. `80 75 3 4...` for PK, `37 80 68 70...` for %PDF). 84 files were corrupted and restored from Datto source. This code path is permanently retired. **Never stringify a byte array in PowerShell** — use `[IO.File]::WriteAllBytes` for binary output.
- **SPMT requires sysadmin to be SharePoint admin:** SPMT destination access requires the running account to have SharePoint admin rights. Confirm before scheduling future SPMT runs. - **SPMT requires sysadmin to be SharePoint admin:** SPMT destination access requires the running account to have SharePoint admin rights. Confirm before scheduling future SPMT runs.
- **Syncro comment rendering:** Use `<br>` for line breaks in Syncro comments. `<ul>/<li>` collapses into a single line in the Syncro renderer. - **Syncro comment rendering:** Use `<br>` for line breaks in Syncro comments. `<ul>/<li>` collapses into a single line in the Syncro renderer.
@@ -185,6 +185,7 @@ Site IDs hardcoded in `$SITE_MAP` hashtable in the migration script. QSD site ID
- **Tenant's real Business Premium skuId is `cbdc14ab-d96c-4c30-b9f4-6ada7cdc1d46`:** The scope doc had a stale GUID (`cbdc14ab-d96c-4132-b7f4-1f3a3a819bb4`). License assign 400'd until corrected. Pull skuId live from Graph `/subscribedSkus` before any license assignment. - **Tenant's real Business Premium skuId is `cbdc14ab-d96c-4c30-b9f4-6ada7cdc1d46`:** The scope doc had a stale GUID (`cbdc14ab-d96c-4132-b7f4-1f3a3a819bb4`). License assign 400'd until corrected. Pull skuId live from Graph `/subscribedSkus` before any license assignment.
- **Datto→SharePoint additive push caused "reappearing files":** The April 2026 SPMT/script run was additive (never a live sync). Files deleted from Datto after April remained in SharePoint, creating phantom files that appeared to "reappear." Resolved 2026-06-27 by treating Datto as source of truth and mirroring SP to it (deleted 1,564 stale SP files to recycle bin). SharePoint and Datto are now synchronized. - **Datto→SharePoint additive push caused "reappearing files":** The April 2026 SPMT/script run was additive (never a live sync). Files deleted from Datto after April remained in SharePoint, creating phantom files that appeared to "reappear." Resolved 2026-06-27 by treating Datto as source of truth and mirroring SP to it (deleted 1,564 stale SP files to recycle bin). SharePoint and Datto are now synchronized.
- **Quality content two-site confusion:** A `Quality Department` site (/sites/QualityDepartment) was the original April 2026 migration landing target; `Quality Systems Department` (/sites/QualitySystemsDepartment) was created 2026-06-02 as the canonical site. The old site was soft-deleted 2026-06-29 after content parity was verified and the one divergent file was preserved in QSD. Do not expect /sites/QualityDepartment to exist after ~2026-09-29 (recycle bin expiry). - **Quality content two-site confusion:** A `Quality Department` site (/sites/QualityDepartment) was the original April 2026 migration landing target; `Quality Systems Department` (/sites/QualitySystemsDepartment) was created 2026-06-02 as the canonical site. The old site was soft-deleted 2026-06-29 after content parity was verified and the one divergent file was preserved in QSD. Do not expect /sites/QualityDepartment to exist after ~2026-09-29 (recycle bin expiry).
- **Migrated content owned by `SharePoint App` -> users get greyed-out Move/Copy/Rename (2026-07-06, warranty):** Everything migrated into SharePoint via the app-only Graph/SPMT uploads is authored by `SharePoint App` (no human owner). SharePoint disables Move/Copy/Rename + silently fails drag-drop on system/app-authored items, so users can move their *own* content but not the migrated tree — confirmed by contrast: `Quality General` (author Julie Beck) moves fine; every migrated folder (author `SharePoint App`) does not. NOT a permissions gap (Members group has `write`, inheritance intact, staff are all Members), NOT checkout (`ForceCheckout=false`, `CheckoutUserId` null), NOT retention (no labels). **Fix:** re-stamp `Author`/`Editor` to a real user via cert-token CSOM `SystemUpdate` (SystemUpdate preserves Modified dates — matters for the QMS). Affects ALL migrated libraries (Quality, Admin, Donor Services, Supply — same upload path). Diagnosed via the cert `sharepoint` tier (see the SharePoint cert note above).
## Active Work ## Active Work