# Cascades of Tucson — Chris Knight accounting mailbox delegation

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

---

## Session Summary

Chris Knight needed access to the Cascades accounting email as an attached account in Outlook — able to read and to send. Routed the request through the `remediation-tool` skill against the Cascades M365 tenant (`cascadestucson.com`, `207fa277-e9d8-4eb7-ada1-1064d2221498`).

The opening consent audit graded the tenant RED, reporting "app NOT consented (token mint failed)" for all four app tiers. That was a false negative. The wiki recorded that the Exchange Operator app had worked against this tenant as recently as 2026-06-12 (the `grievances@`/`Surveys@` shared-mailbox delegations), which contradicted the audit. Testing the token mint directly surfaced the real fault: `get-token.sh` resolves the vault from `~/.claude/identity.json`, but this machine's identity and `vault_path` (`D:/vault`) live in the repo at `.claude/identity.json`. With `VAULT_ROOT_ENV=D:/vault` the `exchange-op` token minted immediately. The tenant was fully consented the whole time.

Read-only recon via the EXO REST `InvokeCommand` endpoint identified `accounting@cascadestucson.com` as a SharedMailbox ("Accounting Dept.", account disabled — normal for shared) and confirmed `accountingassistant@` is a distinct UserMailbox, not a delegate. The existing delegates — Lauren Hasselman, Ashley Jensen, Zachary Nelson — each held FullAccess + SendAs. The wiki independently records Chris Knight as "same access tier as Lauren Hasselman," so the grant pattern was unambiguous.

After explicit user confirmation, applied `Add-MailboxPermission` (FullAccess, `InheritanceType All`, `AutoMapping $true`) and `Add-RecipientPermission` (SendAs) via the `exchange-op` tier. Both returned OK. The first verification read-back showed FullAccess but **not** SendAs — Exchange replication lag, not a failed write. A re-read confirmed Chris on the SendAs list, and the trustee GUID returned by the write (`a1f1bb9e-0cee-4a49-8397-189e6137d680`) matched his `ExternalDirectoryObjectId`.

Closed out by updating `wiki/clients/cascades-tucson.md` with a new `accounting@` delegation record (delegates, grant pattern, replication gotcha) and a note on Chris's staff line. Logged two errorlog entries for the path-drift defects found along the way.

## Key Decisions

- **Did not trust the RED consent audit.** The wiki's 2026-06-12 record of a successful Exchange Operator write against this tenant contradicted it. Verifying the token mint directly was cheaper than acting on the audit — and acting on it would have meant emailing the client an admin-consent link they did not need.
- **Granted SendAs, not SendOnBehalf.** All three existing delegates hold SendAs; SendOnBehalf would render as "Chris Knight on behalf of Accounting Dept." to recipients and break parity with the rest of the accounting staff.
- **`AutoMapping $true`** was the specific mechanism the request required — "attached email account" means the mailbox self-attaches to the Outlook profile via AutoDiscover, rather than the user hand-adding it.
- **Re-read before declaring the SendAs write failed.** The write returned a trustee GUID; resolving that GUID against `Get-Mailbox` proved the grant landed and the read was stale.
- **Skipped a full `/wiki-lint` pass.** Two bullet edits to one existing article introduced no `[[wikilinks]]`. Ran the applicable checks (link integrity, article structure) instead of a whole-wiki scan.

## Problems Encountered

- **`consent-audit.sh` reported a false RED "app NOT consented" for all four tiers.** Root cause was a *local* vault-path lookup failure, not tenant consent. The script conflates "could not read the vault" with "tenant has not consented." Resolved with `VAULT_ROOT_ENV=D:/vault`. Logged to `errorlog.md`; the script should fail loud on a vault-path error rather than downgrading it to a consent finding. **This is the dangerous one** — the false negative points directly at sending a client an unnecessary consent link.
- **`get-token.sh` and peers read `~/.claude/identity.json`,** but the authoritative identity (with `vault_path`) is the repo's `.claude/identity.json`. Same path-drift family.
- **`.claude/commands/wiki-lint.md` hardcodes `cd D:/claudetools`,** but `claudetools_root` on this machine is `C:/claudetools`. Logged as `--friction`.
- **Post-write `Get-RecipientPermission` did not show the new SendAs trustee.** Exchange replication lag across replicas. Re-read confirmed. Recorded in the wiki so the next delegate-add does not misread this as a failure.

## Configuration Changes

| File | Change |
|---|---|
| `wiki/clients/cascades-tucson.md` | Added `accounting@` delegation record + grant pattern + replication gotcha (line ~291); appended accounting-mailbox note to Chris Knight's staff line (line ~214) |
| `errorlog.md` | 2 entries: `remediation-tool/consent-audit.sh` (false RED), `wiki-lint` (hardcoded path, `--friction`) |

**M365 tenant changes (Cascades):** two permission grants on `accounting@cascadestucson.com` — see Commands below. No code changes.

## Credentials & Secrets

None discovered, created, or rotated this session.

- Vault accessed read-only for app credentials: `msp-tools/computerguru-exchange-operator.sops.yaml`, `msp-tools/computerguru-security-investigator.sops.yaml`.
- Auth path was certificate-based (`[INFO] auth=cert`), client_credentials flow.
- Vault root on this machine: `D:/vault` (from repo `.claude/identity.json` → `vault_path`).

## Infrastructure & Servers

- **M365 tenant:** `cascadestucson.com` | Tenant ID `207fa277-e9d8-4eb7-ada1-1064d2221498`
- **EXO REST endpoint:** `https://outlook.office365.com/adminapi/beta/207fa277-e9d8-4eb7-ada1-1064d2221498/InvokeCommand`
- **Token scope:** `https://outlook.office365.com/.default`
- **Apps used:** ComputerGuru Exchange Operator `b43e7342-5b4b-492f-890f-bb5a4f7f40e9` (write); ComputerGuru Security Investigator `bfbc12a4-f0dd-4e12-b06d-997e7271e10c` (read)
- **Target mailbox:** `accounting@cascadestucson.com` — SharedMailbox, "Accounting Dept.", cloud-only, `AccountDisabled: true`, `GrantSendOnBehalfTo: []`
- **Grantee:** `Chris.Knight@cascadestucson.com` — UserMailbox | `ExternalDirectoryObjectId: a1f1bb9e-0cee-4a49-8397-189e6137d680` | `Guid: 04610882-68ab-4f15-a1e6-11966bbdc44c` | AD SID `S-1-5-21-3975168698-2031163149-733277609-62630518`
- **Adjacent:** `accountingassistant@cascadestucson.com` is a **UserMailbox**, not a delegate of `accounting@`

### `accounting@` delegates (verified post-change 2026-07-09)

| Delegate | FullAccess | SendAs |
|---|---|---|
| lauren.hasselman@ | yes | yes |
| ashley.jensen@ | yes | yes |
| zachary.nelson@ | yes | yes |
| Chris.Knight@ | yes (added) | yes (added) |

## Commands & Outputs

Token mint — the fix for the false RED:

```bash
# FAILS: scripts look at ~/.claude/identity.json
bash scripts/get-token.sh cascadestucson.com exchange-op
# ERROR: vault_path not set in /c/Users/Howard/.claude/identity.json and VAULT_ROOT_ENV env var not set

# WORKS:
VAULT_ROOT_ENV=D:/vault bash scripts/get-token.sh cascadestucson.com exchange-op
# [INFO] auth=cert
# eyJ0eXAiOiJKV1QiLCJub25jZSI6...
```

The two grants (`exchange-op` token, POST to `InvokeCommand`):

```json
{"CmdletInput":{"CmdletName":"Add-MailboxPermission","Parameters":{
  "Identity":"accounting@cascadestucson.com",
  "User":"Chris.Knight@cascadestucson.com",
  "AccessRights":"FullAccess","InheritanceType":"All","AutoMapping":true}}}
```
```json
{"CmdletInput":{"CmdletName":"Add-RecipientPermission","Parameters":{
  "Identity":"accounting@cascadestucson.com",
  "Trustee":"Chris.Knight@cascadestucson.com",
  "AccessRights":"SendAs","Confirm":false}}}
```

Outputs:
```
[OK] S-1-5-21-3975168698-2031163149-733277609-62630518 -> FullAccess (Deny=False)
[OK] a1f1bb9e-0cee-4a49-8397-189e6137d680 -> SendAs (Allow)
```

Verification (note the lag — first SendAs read omitted Chris, second included him):
```json
{"CmdletInput":{"CmdletName":"Get-MailboxPermission","Parameters":{"Identity":"accounting@cascadestucson.com"}}}
{"CmdletInput":{"CmdletName":"Get-RecipientPermission","Parameters":{"Identity":"accounting@cascadestucson.com"}}}
```

Errorlog:
```bash
bash .claude/scripts/log-skill-error.sh "remediation-tool/consent-audit.sh" "<false RED>" \
  --context "tenant=cascadestucson.com machine=HOWARD-HOME"
bash .claude/scripts/log-skill-error.sh "wiki-lint" "<hardcoded D:/claudetools>" \
  --friction --context "ref=identity.json/claudetools_root machine=HOWARD-HOME"
```

## Pending / Incomplete Tasks

- **Chris's Outlook:** AutoMapping attaches via AutoDiscover refresh — typically a few hours, reliably after an Outlook restart. SendAs can take ~60 min to replicate before sending from `accounting@` stops bouncing. Not yet confirmed on his endpoint (DESKTOP-N5G1ROO, GuruRMM agent `205025ee-2676-4498-8a27-e88562a6f69a`). Worth a spot-check if he reports the mailbox missing.
- **`consent-audit.sh` fix (real bug, unfixed):** must distinguish "vault unreadable" from "tenant not consented." Currently a local config fault renders as a client-facing consent gap. Highest-value of the three.
- **`get-token.sh` + sibling scripts:** resolve identity from repo `.claude/identity.json` (honoring `claudetools_root`), not `~/.claude/identity.json`. Until fixed, every remediation-tool invocation on this machine needs `VAULT_ROOT_ENV=D:/vault`.
- **`.claude/commands/wiki-lint.md`:** replace hardcoded `cd D:/claudetools`.
- **No Syncro ticket** was opened or referenced for this change. If it should be billed or tracked, it needs one.
- **No `/wiki-compile`** run — wiki was hand-edited directly.

## Reference Information

- Wiki article: `wiki/clients/cascades-tucson.md`
- Skill: `C:\Users\Howard\.claude\skills\remediation-tool` — `scripts/get-token.sh`, `scripts/consent-audit.sh`, `references/graph-endpoints.md`
- Prior art (same grant pattern, 2026-06-12): `grievances@` + `Surveys@` shared-mailbox delegations, Syncro ticket **#32417**
- Chris Knight workstation setup (2026-06-08): DESKTOP-N5G1ROO, Win 11 Pro for Workstations, AD account `chris.knight` (OU=Administrative)
- EXO app-auth note: when the MSP app cert is absent from the Windows cert store, use client_credentials → EXO-scoped token → `Connect-ExchangeOnline -AccessToken`
- Tenant consent link (only if genuinely needed — it was **not** this session):
  `https://login.microsoftonline.com/207fa277-e9d8-4eb7-ada1-1064d2221498/adminconsent?client_id=b43e7342-5b4b-492f-890f-bb5a4f7f40e9`
