Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .agents/handoffs/3241.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
schema_version: 1
task_id: "3241"
from: Implementer
to: GitHub
owner: GitHub
status: verifying
artifact:
- path: packages/sync/src/providers/microsoft/microsoft-calendar.adapter.ts
- path: packages/sync/src/providers/microsoft/microsoft-calendar.adapter.test.ts
- path: packages/sync/src/providers/microsoft/microsoft-calendar-colors.ts
- path: packages/sync/src/providers/microsoft/microsoft-calendar-capabilities.ts
- path: packages/sync/src/providers/microsoft/microsoft-error.ts
- path: packages/sync/src/providers/microsoft/microsoft-http.constants.ts
- path: packages/sync/src/providers/__contract__/microsoft.contract.test.ts
evidence:
- command: bun test:sync
result: pass
- command: bun run type-check
result: pass
- command: bun lint
result: pass
- command: bun knip
result: pass
- command: bun run verify --strict
result: "VERDICT: PASS"
assumptions:
- "Microsoft is not registered in ProviderRegistry until M-09 (#3249)."
- "Named color enum hex fallbacks use Outlook preset values when Graph omits hexColor."
open_risks: []
next_deadline: 2026-09-05T02:00:00Z
retry: 0
approval: allow
waiting_on: null
escalation: null
---

M WP-02: Microsoft calendar discovery adapter with injectable Graph list API, paging, color fallback, access-role mapping, and error classification.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { generateKeyPair, type KeyLike, SignJWT } from "jose";
import { type AuthContractCase } from "@sync/providers/__contract__/auth.contract";
import { type DiscoveryContractCase } from "@sync/providers/__contract__/discovery.contract";
import exchangeFixture from "@sync/providers/__contract__/fixtures/microsoft/exchange-success.json";
import refreshRevokedFixture from "@sync/providers/__contract__/fixtures/microsoft/refresh-invalid-grant.json";
import refreshSuccessFixture from "@sync/providers/__contract__/fixtures/microsoft/refresh-success.json";
Expand All @@ -9,6 +10,11 @@ import {
type MicrosoftTokenEndpoint,
type MicrosoftTokenResponse,
} from "@sync/providers/microsoft/microsoft-auth.adapter";
import {
MicrosoftCalendarAdapter,
type MicrosoftCalendarListApi,
type MicrosoftCalendarListPage,
} from "@sync/providers/microsoft/microsoft-calendar.adapter";
import { ProviderAuthError } from "@sync/providers/provider-auth.port";

const CLIENT_ID = "microsoft-client-id";
Expand Down Expand Up @@ -172,3 +178,61 @@ describeAuthCases(
},
],
);

class ContractCalendarListApi implements MicrosoftCalendarListApi {
async listPage(): Promise<MicrosoftCalendarListPage> {
return {
items: [
{
id: "primary-cal",
name: "Calendar",
color: "lightBlue",
hexColor: "#0078D4",
canEdit: true,
isDefaultCalendar: true,
},
{
id: "shared-cal",
name: "Shared",
color: "lightGreen",
hexColor: "#107C10",
canEdit: false,
isDefaultCalendar: false,
},
],
nextLink: null,
};
}
}

const MICROSOFT_DISCOVERY_CASES: DiscoveryContractCase[] = [
{
name: "detects a primary calendar, colors, access roles, and a cursor",
username: "user@contoso.com",
password: "secret",
run: async (adapter) => {
const result = await adapter.discoverCalendars({
accessToken: "contract-access-token",
});
const primary = result.calendars.filter((calendar) => calendar.primary);
expect(primary).toHaveLength(1);
expect(result.calendars.some((calendar) => calendar.color !== null)).toBe(
true,
);
expect(result.calendars[0]?.accessRole).toBe("owner");
expect(result.calendars[1]?.accessRole).toBe("viewer");
expect(result.cursor).toBeNull();
},
},
];

describe("microsoft discovery contract", () => {
for (const testCase of MICROSOFT_DISCOVERY_CASES) {
it(testCase.name, async () => {
const adapter = new MicrosoftCalendarAdapter(
() => new ContractCalendarListApi(),
);
await testCase.run(adapter);
});
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { type SyncCalendarCapabilities } from "@core/types/sync/connection.contracts";

// Per-calendar operational capabilities for a discovered Microsoft calendar.
// Push subscriptions are a connection-level fact (changeNotifications); every
// listed calendar can be watched when the connection grants calendar access.
export function microsoftDiscoveredCalendarCapabilities(
canEdit: boolean,
): SyncCalendarCapabilities {
return {
canReadEvents: true,
canWriteEvents: canEdit,
canReadBusy: true,
canInviteAttendees: canEdit,
};
}
27 changes: 27 additions & 0 deletions packages/sync/src/providers/microsoft/microsoft-calendar-colors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Outlook calendar theme presets when Graph returns the named color enum but
// no hexColor (common for auto and never-customized calendars).
export const MICROSOFT_CALENDAR_COLOR_HEX: Readonly<
Record<string, string | null>
> = {
auto: null,
lightBlue: "#0078D4",
lightGreen: "#107C10",
lightOrange: "#D83B01",
lightGray: "#666666",
lightYellow: "#FFB900",
lightTeal: "#008272",
lightPink: "#E3008C",
lightBrown: "#986F0B",
lightRed: "#D13438",
maxColor: null,
};

export function resolveMicrosoftCalendarColor(
hexColor: string | null | undefined,
color: string | null | undefined,
): string | null {
const trimmedHex = hexColor?.trim();
if (trimmedHex) return trimmedHex;
if (!color) return null;
return MICROSOFT_CALENDAR_COLOR_HEX[color] ?? null;
}
Loading
Loading