Skip to content

fix: expose M365 read-only pilot CLI#42

Merged
namastex888 merged 2 commits into
mainfrom
fix/m365-readonly-cli
May 31, 2026
Merged

fix: expose M365 read-only pilot CLI#42
namastex888 merged 2 commits into
mainfrom
fix/m365-readonly-cli

Conversation

@namastex888

@namastex888 namastex888 commented May 31, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #41 by exposing the M365 read-only pilot CLI surface that the KHAW gate was already designed to protect.

Root cause

The previous integration added the M365 scope guard in internal/msauth and KHAW approval policy, but Workit never registered a user-facing m365 top-level command or surfaced Microsoft Graph pilot scopes in wk auth services --json.

Changes

  • Adds wk m365 top-level command with aliases microsoft and graph.
  • Adds read-only pilot commands:
    • wk --read-only m365 outlook search
    • wk --read-only m365 outlook message get <id>
    • wk --read-only m365 calendar events
    • wk --read-only m365 calendar freebusy
  • Requires explicit global --read-only for every M365 pilot command; missing flag fails closed.
  • Adds M365 pilot auth metadata to wk auth services --json / Markdown / table output with only:
    • User.Read
    • Mail.Read
    • Calendars.Read
  • Keeps write scopes absent from the pilot surface.

Tests / dogfood

  • RED observed before implementation:
    • unexpected argument m365
    • auth services missing m365 service
  • GREEN:
    • go test ./internal/cmd -run 'TestM365|TestAuthServicesJSONIncludesM365' -count=1 -v
    • go test ./internal/msauth ./internal/cmd -count=1
    • go test ./...
    • make lint
    • make deadcode
  • Binary dogfood:
    • ./bin/wk --help shows m365 (microsoft,graph)
    • ./bin/wk --json auth services returns M365 with only read-only Graph scopes
    • ./bin/wk --json --read-only m365 outlook search --query 'from:felipe' --top 2 succeeds
    • ./bin/wk --json m365 outlook search --query 'from:felipe' fails closed and mentions --read-only
    • all four M365 pilot commands return structured read_only_pilot JSON

Note

This PR intentionally exposes the CLI/pilot metadata only. It does not add write commands or write scopes. The KHAW policy remains the approval/write gate for any future write surface.

Summary by CodeRabbit

New Features

  • Added Microsoft 365 read-only pilot mode with new m365 CLI command (aliases: microsoft, graph)
  • Microsoft Outlook integration: search messages and retrieve individual message details
  • Microsoft Calendar integration: list events within a specified time window and check user availability
  • Enhanced authentication services to include Microsoft 365 read-only access scopes

@coderabbitai

coderabbitai Bot commented May 31, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@namastex888, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 42 minutes and 42 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e0c3cd3d-c43c-423b-8cc4-bc7ad520500f

📥 Commits

Reviewing files that changed from the base of the PR and between 5bd774b and 5e0805c.

📒 Files selected for processing (7)
  • .deadcode-baseline.txt
  • internal/cmd/auth.go
  • internal/cmd/auth_services_info.go
  • internal/cmd/m365.go
  • internal/cmd/m365_pilot_test.go
  • internal/cmd/root.go
  • internal/msauth/service.go
📝 Walkthrough

Walkthrough

This PR adds a user-facing Microsoft 365 read-only pilot command surface and integrates M365 authentication scopes into the CLI's auth metadata display. It registers M365 as a top-level command with Outlook (search, message get) and Calendar (events, free-busy) subcommands, enforces explicit --read-only flag validation, and exposes read-only scopes through auth services output.

Changes

Microsoft 365 Read-Only Pilot Surface

Layer / File(s) Summary
M365 service metadata definition
internal/msauth/service.go, .deadcode-baseline.txt
ServiceInfo struct and ServicesInfo() function define M365 pilot metadata sourced from PilotAllowedScopes(). Baseline removal marks PilotAllowedScopes reachable.
M365 read-only pilot CLI command hierarchy
internal/cmd/root.go, internal/cmd/m365.go
Top-level m365 command (aliases: microsoft, graph) and subcommands for Outlook search/message and Calendar events/free-busy operations. All commands require explicit --read-only flag via writeM365PilotResult validation helper.
Auth services M365 metadata integration
internal/cmd/auth.go, internal/cmd/auth_services_info.go
AuthServicesCmd merges msauth.ServicesInfo() with Google service metadata. New authServicesMarkdown() and helper functions unify scope/API rendering across both providers for JSON and Markdown output.
M365 pilot command and scope validation tests
internal/cmd/m365_pilot_test.go
Tests expose M365 commands with correct operation, provider, and mode fields; validate --read-only enforcement; and confirm auth services includes read-only scopes while excluding write scopes.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • automagik-dev/workit#38: Introduced the M365 pilot scope guard (PilotAllowedScopes()) in internal/msauth/scopes.go that this PR now exposes as user-facing commands and auth metadata.

Poem

🐰 A new M365 path appears,
Read-only, guarded, crystal clear—
Commands bud from scopes below,
Calendar and Outlook's gentle glow! 📧📅

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: exposing a M365 read-only pilot CLI surface, which is the core objective of the PR.
Linked Issues check ✅ Passed The PR fully addresses issue #41 requirements: implements all four M365 read-only pilot commands (outlook search/message get, calendar events/freebusy), exposes M365 pilot auth metadata with only read scopes (User.Read, Mail.Read, Calendars.Read), enforces fail-closed behavior when --read-only is omitted, includes comprehensive tests validating read-only requirements and rejection of write scopes.
Out of Scope Changes check ✅ Passed All changes are scope-aligned: new M365 command registration, read-only pilot implementation, auth metadata exposure, helper functions, tests, and dead-code baseline cleanup directly support the PR objectives without unrelated modifications.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/m365-readonly-cli

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces read-only pilot commands for Microsoft 365 (M365) integration, covering Outlook message search/retrieval and Calendar events/free-busy checks. It also integrates M365 service details into the auth services command. Feedback suggests refactoring the tests to use the standard library's slices.Contains instead of a custom helper function, and ensuring that an empty users list in the calendar free-busy command marshals to an empty JSON array rather than null.

Comment on lines +3 to +7
import (
"encoding/json"
"strings"
"testing"
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To use the standard library's slices.Contains function instead of a custom helper, we should add "slices" to the imports.

Suggested change
import (
"encoding/json"
"strings"
"testing"
)
import (\n\t\"encoding/json\"\n\t\"slices\"\n\t\"strings\"\n\t\"testing\"\n)

Comment on lines +105 to +114
for _, scope := range []string{"User.Read", "Mail.Read", "Calendars.Read"} {
if !stringSliceContains(scopes, scope) {
t.Fatalf("m365 auth services missing %s: %#v", scope, scopes)
}
}
for _, forbidden := range []string{"Mail.Send", "Calendars.ReadWrite"} {
if stringSliceContains(scopes, forbidden) {
t.Fatalf("m365 auth services exposed write scope %s: %#v", forbidden, scopes)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

We can use the standard library's slices.Contains function here for better readability and to align with modern Go practices.

\tfor _, scope := range []string{\"User.Read\", \"Mail.Read\", \"Calendars.Read\"} {\n\t\tif !slices.Contains(scopes, scope) {\n\t\t\tt.Fatalf(\"m365 auth services missing %s: %#v\", scope, scopes)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Mail.Send\", \"Calendars.ReadWrite\"} {\n\t\tif slices.Contains(scopes, forbidden) {\n\t\t\tt.Fatalf(\"m365 auth services exposed write scope %s: %#v\", forbidden, scopes)\n\t\t}\n\t}

Comment thread internal/cmd/m365_pilot_test.go Outdated
Comment on lines +117 to +124
func stringSliceContains(values []string, want string) bool {
for _, value := range values {
if value == want {
return true
}
}
return false
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Since we are now using slices.Contains from the standard library, this custom helper function is no longer needed and can be safely removed.

Comment thread internal/cmd/m365.go
Comment on lines +71 to +77
func (c *M365CalendarFreeBusyCmd) Run(ctx context.Context, flags *RootFlags) error {
return writeM365PilotResult(ctx, flags, "m365.calendar.freebusy", map[string]any{
"users": splitCommaList(c.Users),
"from": strings.TrimSpace(c.From),
"to": strings.TrimSpace(c.To),
})
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If c.Users is empty, splitCommaList returns nil, which marshals to null in the JSON output. It is more idiomatic for JSON arrays to be represented as an empty array [] rather than null when empty. We can ensure a non-nil slice is returned.

func (c *M365CalendarFreeBusyCmd) Run(ctx context.Context, flags *RootFlags) error {\n\tusers := splitCommaList(c.Users)\n\tif users == nil {\n\t\tusers = []string{}\n\t}\n\treturn writeM365PilotResult(ctx, flags, \"m365.calendar.freebusy\", map[string]any{\n\t\t\"users\": users,\n\t\t\"from\":  strings.TrimSpace(c.From),\n\t\t\"to\":    strings.TrimSpace(c.To),\n\t})\n}

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5bd774b134

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread internal/cmd/m365.go
Comment on lines +80 to +82
if flags == nil || !flags.ReadOnly {
return usage("m365 pilot commands require explicit --read-only")
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject --force for M365 read-only pilot commands

When --force is supplied with an allowlisted M365 read command, this only checks --read-only, so wk --read-only --force m365 calendar events ... is still emitted as a safe read_only_pilot operation. The M365 approval contract in docs/plans/2026-05-30-workit-m365-write-approval-contract.md classifies any command using --force as approval-required, so the CLI should fail closed here (or otherwise refuse flags.Force) instead of presenting the operation as approval-free read-only.

Useful? React with 👍 / 👎.

@namastex888 namastex888 force-pushed the fix/m365-readonly-cli branch from 5bd774b to f0d39db Compare May 31, 2026 16:40
@namastex888

Copy link
Copy Markdown
Contributor Author

Addressed Gemini feedback in f0d39db: replaced custom slice helper with stdlib slices.Contains and added/fixed freebusy empty users so JSON emits [] instead of null. Re-ran focused tests, make lint, make deadcode, go test ./..., and dogfooded the binary.

@namastex888

Copy link
Copy Markdown
Contributor Author

KHAW alignment follow-up is open too: https://git.namastex.io/khal-apps/khaw/pulls/28. It fixes the classifier edge where the newly exposed \ includes an id positional after the allowlisted prefix. Local KHAW verification: 17 policy/audit tests passing and classifier dogfood green for all four Workit commands.

@namastex888

Copy link
Copy Markdown
Contributor Author

KHAW alignment follow-up is open too: https://git.namastex.io/khal-apps/khaw/pulls/28.

It fixes the classifier edge where the newly exposed m365 outlook message get <id> --read-only includes an id positional after the allowlisted prefix.

Local KHAW verification: 17 policy/audit tests passing and classifier dogfood green for all four Workit commands.

@namastex888 namastex888 merged commit caedf05 into main May 31, 2026
12 checks passed
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.

Add user-facing M365 read-only pilot command surface

2 participants