Skip to content

feat(m365): add customer Graph Read consent#2511

Merged
ToddHebebrand merged 47 commits into
mainfrom
feat/m365-customer-graph-read-consent
Jul 16, 2026
Merged

feat(m365): add customer Graph Read consent#2511
ToddHebebrand merged 47 commits into
mainfrom
feat/m365-customer-graph-read-consent

Conversation

@ToddHebebrand

Copy link
Copy Markdown
Collaborator

Summary

Adds Breeze-owned, customer-tenant Microsoft 365 Graph Read consent and lifecycle management on top of the M365 control-plane foundation.

  • defines the canonical least-privilege permission manifest and shared executor contracts
  • stores organization-scoped consent sessions and connection lifecycle state with RLS and compare-and-set protections
  • adds the public admin-consent/identity callback flow with one-time browser binding, tenant/admin verification, and audit attribution
  • isolates credentials and Microsoft Graph access in a private, narrowly scoped executor
  • adds connect, re-consent, status, drift, retest, suspend/revoked, and local-disconnect UI states
  • ships Compose, release, supply-chain, deployment, and disposable-tenant validation guidance

Why

This keeps Breeze as the approval and orchestration control plane while isolating customer-tenant Graph credentials and execution. It avoids making Delegant a second general authentication control plane and provides a safe foundation for future approval-tiered M365 tools.

Security and correctness

The final two-pass security review and independent whole-branch review are clean. Follow-up tests and fixes cover:

  • local disconnect preserving the database-valid manifest while clearing binding state
  • query-free request logging for OAuth callback artifacts
  • callback audit attribution to the initiating user only after trusted session validation
  • serialized retest generations so stale completions cannot overwrite newer results
  • fixed own-service-principal proof so permission removal reaches last-known/unavailable reconciliation
  • correct unknown versus last-known grant health across executor, API, and UI
  • nullable unknown grant names and revoked client identity across API/UI contracts
  • retest controls only for executable lifecycle states

Validation

  • latest-HEAD focused suites: shared 10/10, executor 185/185, API 299/299, web 90/90
  • real-role PostgreSQL RLS/lifecycle suites: 16/16; disconnect lifecycle regression: 3/3
  • full shared suite: 1,212/1,212
  • full web suite with Node 26 experimental web storage disabled: 3,750/3,750
  • API, web, and executor lint/typecheck/build gates passed
  • migration replay: 339 migrations; drift ledger: 404 files
  • docs build: 146 pages
  • supply-chain hardening, executor Docker build, and production Compose configuration passed

The local full API regression run completed with 13,436 passes, 31 failures, and 71 skips; the failures were unrelated timeout/load failures in existing OAuth/MCP/metrics/encryption/partner-route suites. The complete M365-focused API suite and API lint/typecheck/build gates are green.

Live Microsoft consent and disposable-tenant permission mutation were not run because dedicated tenant/executor/Azure credentials were not present. The runbook scopes those checks to a disposable tenant.

Dependency

This is a stacked draft PR based on #2495 (agent/m365-control-plane-foundation). Review and merge #2495 first; GitHub will then reduce this PR to the Graph Read phase.

Delegant removal is intentionally out of scope until Breeze-owned consumers are migrated.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 14, 2026

Copy link
Copy Markdown

Deploying breeze with  Cloudflare Pages  Cloudflare Pages

Latest commit: 6d051a1
Status: ✅  Deploy successful!
Preview URL: https://ddb0e2b4.breeze-9te.pages.dev
Branch Preview URL: https://feat-m365-customer-graph-rea.breeze-9te.pages.dev

View logs

@ToddHebebrand

Copy link
Copy Markdown
Collaborator Author

Final CI follow-up on head 52abb4d3e:

  • Fixed the branch-attributable GDPR cascade contract by adding m365_consent_sessions to the tenant-erasure table set.
  • Fresh-database tenant cascade integration suite: 5/5 passed locally.
  • Full CI integration job: passed (1,077 tests), including request database-role enforcement and RLS coverage.
  • 27 CI jobs passed overall.
  • The workflow conclusion remains red only because pnpm audit received HTTP 410 from the npm registry retired audit endpoints; CI Success consequently failed as the aggregate gate. This is external and unrelated to this branch.

Run: https://github.com/LanternOps/breeze/actions/runs/29379256113

@ToddHebebrand
ToddHebebrand marked this pull request as ready for review July 16, 2026 04:08
@ToddHebebrand
ToddHebebrand force-pushed the agent/m365-control-plane-foundation branch 2 times, most recently from 11c9433 to 416d8d6 Compare July 16, 2026 04:21
Base automatically changed from agent/m365-control-plane-foundation to main July 16, 2026 04:30
@ToddHebebrand
ToddHebebrand force-pushed the feat/m365-customer-graph-read-consent branch from 52abb4d to 422b646 Compare July 16, 2026 04:36
if (!Number.isFinite(issuedAt)) throw new ClientAssertionError();

const certificate = new X509Certificate(input.certificatePem);
const x5t = createHash('sha1').update(certificate.raw).digest('base64url');
@ToddHebebrand
ToddHebebrand force-pushed the feat/m365-customer-graph-read-consent branch from 422b646 to 5ec279a Compare July 16, 2026 04:53
ToddHebebrand added a commit that referenced this pull request Jul 16, 2026
)

**Problem:** the `typecheck` job (`tsc --noEmit` over the full
`apps/api` project + `astro check`) OOMs — `FATAL ERROR: ... JavaScript
heap out of memory` — on the type-heaviest feature branches (e.g. #2548
extensions, #2511 m365). Reproduced deterministically on re-run. Main
passes today but is at the edge as the Drizzle type graph grows.

**Fix:** set `NODE_OPTIONS=--max-old-space-size=8192` at the `typecheck`
job level (covers both `tsc` and `astro check`). No type errors were
involved — this is purely heap headroom. ubuntu-latest runners have ~16
GB, so 8 GB old-space is safe.

Unblocks the release: the OOM'ing feature PRs pass Type Check once
rebased onto this.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Todd Hebebrand <todd@lanternops.io>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ToddHebebrand
ToddHebebrand force-pushed the feat/m365-customer-graph-read-consent branch from 5ec279a to 6d051a1 Compare July 16, 2026 05:12
@ToddHebebrand
ToddHebebrand merged commit bdf32c2 into main Jul 16, 2026
46 of 47 checks passed
@ToddHebebrand
ToddHebebrand deleted the feat/m365-customer-graph-read-consent branch July 16, 2026 05:52
ToddHebebrand added a commit that referenced this pull request Jul 16, 2026
Technical-docs sweep for the **v0.96.0** release (range
`v0.95.1..main`). Companion to the tagged release; no code changes, docs
+ docs-review mapping only.

## Pages updated
- **TD SYNNEX AI tool** — `features/mcp-server.mdx`, `features/ai.mdx`,
`features/distributor-integrations.mdx`: new
`lookup_distributor_product` (Tier 2 / `ai:write`; live EC Express
price+availability; reseller cost redacted for org-scoped callers,
error+no outbound call when no partner in context) (#2559)
- **M365 customer Graph-read host config** — `deploy/environment.mdx`
new "Customer Microsoft 365 Graph-read consent (optional)" section: 9
env vars, **all required only when
`M365_CUSTOMER_GRAPH_READ_ONBOARDING_ENABLED=true`** (dark by default);
`deploy/production.mdx` optional `m365-graph-read-executor` sidecar
note; `features/identity-integrations.mdx` pointer (#2495, #2511)
- **Quotes** — `features/quotes.mdx`: cloning, change-customer-on-draft,
per-table subtotals, customer bill-to "Prepared for" block, optional
send message, click-to-zoom, Pax8-order-on-acceptance cross-link (#2535,
#2584, #2549)
- **Backups** — `backup/overview.mdx` (per-device "Run backup now",
timezone-correct next-run, `breeze-backup` helper now bundled by
shell-script installs), `backup/restoring.mdx` (System image badge,
mode+mtime + system-image restore), `backup/verification.mdx` (integrity
= size+SHA-256 vs manifest, real corruption detection) (#2558, #2564,
#2581)
- **Enrollment keys** — `agents/enrollment-keys.mdx`: required Site
selector on create form (#2557)

## Mapping / tracker
- `scripts/docs-review/mapping.json`: repoint pax8 →
`distributor-integrations.mdx`; add `pax8Orders`, `pax8Order*`,
`quoteToPax8Order`, `aiToolsCatalog`, `m365ControlPlane/**`,
`runtimeConfig.ts`→environment,
`m365CustomerGraphRead`/`m365ConsentCallback`,
`apps/m365-graph-read-executor/**`, quote pdf/email services,
`agent/scripts/install/**`
- `scripts/docs-review/last-reviewed.json`: bumped to `20276393e`
(2026-07-16)

## Verification notes
- The M365 consent **callback URL derives from `PUBLIC_URL`** in the
API; the `..._CALLBACK_URL` env var is consumed by the **executor app**,
not the API — documented accordingly (not as dead config).
- The `m365-graph-read-executor` sidecar is **published (GHCR) but not
wired into the tracked compose files** — documented as such, not as a
turnkey service.
- `apps/docs` `astro build` passes.

Intentionally **skipped** (no admin/integrator doc surface): versioned
extension contract #2548 (developer-only SDK), Windows helper RDS
lifecycle #2520 (internal agent fix), backup execution/result-recording
internals #2554/#2556.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Todd Hebebrand <todd@lanternops.io>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ToddHebebrand added a commit that referenced this pull request Jul 18, 2026
…ion (#2615)

## Summary

First consumer value from the M365 control plane (#2495/#2511): a
closed, typed catalog of 12 Microsoft Graph **read** actions on the
`customer-graph-read` connection, exposed as six `m365_query_*` AI tools
(in-app chat + Breeze MCP). Risk-tier T1 only — no mutations, no
approval layer, no manifest bump (all actions fit the already-consented
manifest v2), and **zero DB migrations**.

- **Shared contracts** (`packages/shared/src/m365/readActions.ts`):
12-action Zod discriminated union with bounded params, per-action
field-projection allowlists, and typed failure codes.
- **Executor** (`m365-graph-read-executor`): one new bounded op `POST
/v1/read-action` (same Ed25519 body-hash-bound internal auth), fixed
Graph URL templates, status-aware error mapping (license-403 vs
permission-403, 404, 429 w/ bounded Retry-After), truncate-don't-throw
pagination, projection so no non-allowlisted field ever leaves the
executor. Credentials still never cross the boundary.
- **API** (`m365ControlPlane/readActionService.ts`): authz ladder —
site-restriction gate (`allowedSiteIds`), org resolution,
`M365_GRAPH_READ_TOOLS_ENABLED` + org allowlist (boot-validated),
connection state (`active`/`degraded` only), fail-closed Redis budget
(30/min, 2,000/day per connection), executor call, audit (fixed 4-key
metadata, never Graph payloads) +
`breeze_m365_graph_read_actions_total{action,outcome}`.
- **AI tools**: six tools registered across all gate surfaces (aiTools
hub, guardrails, input schemas, system prompt, chat-SDK `TOOL_TIERS`/MCP
server) with cap-accurate descriptions.
- **Docs**: deploy env vars + read-action acceptance section (R1–R5) in
the real-tenant runbook.

Ships **dark**: `M365_GRAPH_READ_TOOLS_ENABLED=false` by default;
deploy-order safe in both directions (old executor 404 →
`executor_unavailable`).

Spec:
`docs/superpowers/specs/2026-07-18-m365-typed-graph-read-tools-design.md`
· Plan:
`docs/superpowers/plans/2026-07-18-m365-typed-graph-read-tools.md`

## Review

Subagent-driven build: per-task spec+quality reviews on every task, plus
a final whole-branch review (verdict: ready to merge). Notable catches
fixed along the way: Zod v4 `z.record` arity, site-gate signal corrected
to `allowedSiteIds` (`canAccessSite` is always defined for org scope),
chat-SDK tool registration drift, boot validation of the tools
allowlist.

## Verification

- Shared 1,259/1,259 · executor 240/240 · API 14,927 passed (0 failed) —
full suites
- `tsc --noEmit` clean (API + executor + shared), eslint clean, `git
diff --check` clean
- Real-tenant read-action acceptance checklist added to the runbook (to
run at rollout, tenant-side)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Todd Hebebrand <todd@lanternops.io>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants