Skip to content

feat: add Workday integration plugin - #475

Merged
devjain32 merged 23 commits into
corsairdev:mainfrom
Dhirenderchoudhary:feat/workday-clean
Aug 12, 2026
Merged

feat: add Workday integration plugin#475
devjain32 merged 23 commits into
corsairdev:mainfrom
Dhirenderchoudhary:feat/workday-clean

Conversation

@Dhirenderchoudhary

@Dhirenderchoudhary Dhirenderchoudhary commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator

Description

This PR introduces the official Workday Integration Plugin (packages/workday) to the Corsair monorepo.

Key implementation details:

  • Endpoint Structure: Parsed and registered Workday's massive API surface (across business, job, payroll, time, and worker domains) into standard REST endpoints.
  • Dynamic Catch-All Validation: Since Workday's API dictates hundreds of nested fields, all inputs and outputs currently utilize flexible .catchall(z.unknown()) schemas to ensure stable data flow without strict upfront typing (in compliance with rule R7 for zero any usage).
  • Webhooks: Hooked up a mock worker.updated event matcher to demonstrate standard Workday webhook lifecycle handling.
  • PR Rules Compliant: The plugin meets the >5 assertion test criteria in api.test.ts, restricts modifications solely to the authorized plugin boundaries, and correctly hooks into packages/corsair/core/constants.ts.

Closes #474

Checklist

Before submitting your PR, please verify the following:

  • I have run pnpm lint and all checks pass
  • I have run pnpm typecheck and there are no TypeScript errors
  • I have run pnpm build and all packages build successfully
  • I have run pnpm test and all tests pass
  • I have added or updated tests where applicable
  • I have added or updated necessary documentation

Screenshots / Demos (if applicable)

Screenshot 2026-07-19 at 5 44 16 PM

Additional Notes

  • Note for reviewers: ts-jest can occasionally throw local execution errors on .js files due to global ESM/CommonJS merging settings in jest.config.cjs, but this does not affect the actual static typing, build step, or core plugin architecture.
  • This PR cleanly branches from main without capturing any transient history.

Summary by CodeRabbit

  • New Features
    • Added Workday as a supported provider.
    • Introduced Workday integration with OAuth authentication, tenant configuration, and authenticated API access.
    • Added 85 typed operations across staffing, recruiting, payroll, absence, compensation, and related services.
    • Added request validation, response schemas, caching, rate-limit handling, and structured error reporting.
    • Added webhook support for 13 Workday event types, including tenant matching and signature verification.
    • Added schemas for workers, jobs, payroll, prospects, interviews, and absence balances.
  • Tests
    • Added comprehensive coverage for API operations, authentication, webhooks, errors, and caching.

@vercel

vercel Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

@Dhirenderchoudhary is attempting to deploy a commit to the corsair Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions github-actions Bot added the core Changes in packages/corsair label Jul 19, 2026
@greptile-apps

greptile-apps Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces the packages/workday integration plugin, a substantial new addition covering 85 REST endpoints across Workday's staffing, payroll, absence, recruiting, and common APIs, plus 13 Composio-aligned webhook triggers.

  • Core infrastructure: client.ts builds typed OAuth and service base URLs, with makeWorkdayRequest now correctly rethrowing ApiError so RATE_LIMIT_ERROR can read status and retryAfter. The endpoint factory resolves {ID} path placeholders with encodeURIComponent, aliases id/workerId to the ID path param, and routes errors through error-handlers.ts.
  • Endpoints and schemas: All 85 routes have distinct paths, correct HTTP verbs, required path-param validation (e.g. ID, subresourceID), pagination fields on list endpoints, and per-route riskLevel (read vs. write). Input schemas use .passthrough() with explanatory comments for Workday's open payload structure.
  • Webhooks: All 13 triggers call verifyWorkdayWebhookSignature, which delegates to verifyHmacSignature from corsair/http (constant-time, hex-encoded). The pipeline guards against null parse results and returns HTTP 401 on verification failure.

Confidence Score: 5/5

  • The plugin is safe to merge. All previously identified functional defects — error wrapping, timing-unsafe signature comparison, null dereference in webhook parsing, literal {ID} in URLs, undifferentiated endpoint paths, and uniform riskLevel: 'write' — are resolved in this iteration.
  • Every structural and behavioral concern flagged on previous review rounds has been addressed: makeWorkdayRequest preserves ApiError identity so the rate-limit handler works correctly, webhook signature verification now uses verifyHmacSignature from corsair/http, path templates are resolved dynamically with encodeURIComponent, each route has a distinct path and HTTP verb, and riskLevel is assigned individually per route. The only fresh findings are two missing justification comments for any/unknown usages — one in the test mock and one on an internal exported type — which are non-functional style nits.
  • No files require special attention. packages/workday/mocks/corsair-core-mock.ts and packages/workday/endpoints/factory.ts have minor missing comments but no functional issues.

Important Files Changed

Filename Overview
packages/workday/client.ts HTTP client with OAuth URL builders and makeWorkdayRequest. Now correctly rethrows ApiError directly so RATE_LIMIT_ERROR can read status/retryAfter, addressing the previously flagged error-wrapping bug.
packages/workday/endpoints/factory.ts Endpoint factory with resolvePath, buildQuery, requestBody, and executeWorkdayOperation. Uses unknown as the output type parameter on the exported WorkdayEndpoint type without an accompanying justification comment, violating the repo's any/unknown documentation rule.
packages/workday/endpoints/routes.ts Defines 85 Workday REST routes with distinct paths, correct HTTP methods, proper path params, query params, and per-route riskLevel assignments. Addresses previously flagged issues around duplicate paths, wrong HTTP verbs, and uniform riskLevel: 'write'.
packages/workday/endpoints/types.ts Input and output zod schemas for all 85 routes. Each schema has required path params (ID, subresourceID), optional pagination (limit, offset), and .passthrough() for open Workday payloads, with explanatory comments for every unknown usage.
packages/workday/error-handlers.ts Rate-limit, auth, and default error handlers. RATE_LIMIT_ERROR correctly checks error instanceof ApiError and reads error.retryAfter, which now works because client.ts no longer wraps ApiError in WorkdayAPIError.
packages/workday/webhooks/types.ts Webhook payload schema, 13 trigger event schemas, event matcher, and HMAC signature verifier. verifyWorkdayWebhookSignature now delegates to verifyHmacSignature from corsair/http (constant-time, hex-encoded). createWorkdayEventMatch guards against null parse results before accessing .type.
packages/workday/webhooks/triggers.ts Registers all 13 Composio-aligned webhook triggers with createTriggerWebhook. Each trigger calls verifyWorkdayWebhookSignature in its handler and returns HTTP 401 on failure, properly wiring the signature verification into the webhook pipeline.
packages/workday/index.ts Main plugin export with workday() factory. endpointMeta assigns riskLevel per endpoint (read for GETs, write for mutations). pluginWebhookMatcher checks for either x-workday-signature or x-workday-event headers, with downstream handlers enforcing signature verification.
packages/workday/api.test.ts 17 tests covering plugin initialization, URL construction, path interpolation, riskLevel assignments, rate-limit error handling, webhook event matching, and direct endpoint invocations for 5 of 85 endpoints (createJobChange, getJobById, listBalances, retrieveWorkerLeaveOfAbsenceSubresource, updateMessageTemplateById). The remaining 80 endpoints have no invocation test.
packages/workday/mocks/corsair-core-mock.ts Test mock for corsair/core. Uses as any on CORSAIR_INTERNAL without an accompanying justification comment, violating the repo rule requiring all any/unknown usages to be documented.
packages/corsair/core/constants.ts Registers workday in BaseProviders, ProviderDisplayNames, and AllProviders — the three required constant registration points for a new plugin.

Sequence Diagram

sequenceDiagram
    participant Caller
    participant Factory as executeWorkdayOperation
    participant Client as makeWorkdayRequest
    participant Http as corsair/http
    participant Errors as errorHandlers
    participant Webhook as WebhookPipeline

    Caller->>Factory: invoke endpoint(ctx, input)
    Factory->>Factory: normalizeInputAliases + zod parse input
    Factory->>Factory: "resolvePath (encode {ID} placeholders)"
    Factory->>Client: path, apiKey, method/body/query/service/version
    Client->>Http: request(config, options)

    alt success
        Http-->>Client: response T
        Client-->>Factory: T
        Factory->>Factory: zod parse output schema
        Factory-->>Caller: typed result
    else ApiError 429
        Http-->>Client: throw ApiError
        Client-->>Factory: rethrow ApiError (status + retryAfter preserved)
        Factory-->>Errors: RATE_LIMIT_ERROR.match
        Errors-->>Caller: maxRetries 5 + headersRetryAfterMs
    end

    Note over Webhook: Incoming webhook POST
    Webhook->>Webhook: pluginWebhookMatcher (x-workday-signature or x-workday-event header)
    Webhook->>Webhook: matchWorkdayTenantWebhook (tenant_external_id)
    Webhook->>Webhook: createWorkdayEventMatch (header type or body.type)
    Webhook->>Webhook: verifyWorkdayWebhookSignature via verifyHmacSignature

    alt signature valid
        Webhook-->>Caller: success true
    else signature invalid
        Webhook-->>Caller: success false statusCode 401
    end
Loading

Reviews (11): Last reviewed commit: "fix(workday): preserve ApiError retryAft..." | Re-trigger Greptile

Comment thread packages/workday/endpoints/job.ts Outdated
Comment thread packages/workday/webhooks/types.ts
Comment thread packages/workday/endpoints/types.ts Outdated
Comment thread packages/workday/api.test.ts
Comment thread packages/workday/index.ts Outdated
@github-actions

Copy link
Copy Markdown

Plugin PR scorecard — packages/workday

Check Status Notes
R1 — Scope: plugin files only
R2 — Tests with assertions
R3 — Description complete
R3 — Linked issue / claim
R4 — Demo video / recording

Rules: PLUGIN_PR_RULES.md · re-runs on every push

@github-actions

Copy link
Copy Markdown

Hey @Dhirenderchoudhary, thanks for the contribution! 🏴‍☠️ Before a maintainer reviews, please fix the items below — the review re-runs automatically on your next push.

Must fix

  • P1 packages/workday/endpoints/job.ts:13All endpoints share a single undifferentiated URL per domain
    Every endpoint in a given domain is routed to the same static path (e.g., all 20 job endpoints call v1/job/api, all 9 worker endpoints call v1/worker/api, all organization endpoints call v1/organization/api). Read operations additionally use method: 'POST' instead of 'GET'. As written, getJobById, createJobChange, listJobPostings, and updateJobChangeBusinessTitle are indistinguishable at the network layer — they all POST to v1/job/api — so every GET-oriented call will silently fail or return the wrong data. Each endpoint needs its own path (v1/jobs/{jobId}, v1/jobs/changes, etc.) and the correct HTTP verb.

Rule Used: Every endpoint must validate inputs and outputs wi... (source)

  • P1 packages/workday/webhooks/types.ts:45Webhook signature verification is a no-op stub
    verifyWorkdayWebhookSignature always returns { valid: true } as long as a secret is configured, without performing any cryptographic check against the actual request body or signature header. Any caller relying on this to authenticate incoming Workday webhooks will accept every payload unconditionally, making the webhook endpoint open to spoofed events. A real HMAC-SHA256 (or equivalent) comparison against the x-workday-signature header is required.
  • P1 packages/workday/endpoints/types.ts:20Input schemas accept no fields — required parameters are unvalidated
    All input schemas are defined as z.object({}).optional(). This means parameters that are intrinsically required for specific operations — a workerId for worker-scoped endpoints, a jobId for getJobById, query parameters for list endpoints — are silently ignored and never validated. Any caller omitting required fields will receive no zod error; the request will be sent to the (already wrong) URL with an empty body. Additionally, list endpoints (listJobPostings, listBalances, listJobs, etc.) have no pagination fields whatsoever, violating R7's requirement to support pagination where the provider API offers it.

Rule Used: Every endpoint must validate inputs and outputs wi... (source)

  • P1 packages/workday/api.test.ts:39Tests only verify plugin structure — no endpoint behavior is tested
    All five tests check static shape (plugin.id === 'workday', a property is toBeDefined()), but none of them invoke an endpoint function, assert on what URL was fetched, what HTTP method was used, or what the response shape is. With 60+ endpoints implemented, zero actual invocation tests exist. Per R2 and rule auto-74ac9d3b, every implemented endpoint should have a corresponding test — the current suite provides no coverage for any real behavior.

Rule Used: Flag any types on exported or public surfaces as... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

  • P1 packages/workday/index.ts:760Read-only endpoints are universally labelled riskLevel: 'write'
    Every endpoint — including pure lookups like getCurrentUser, getCurrencies, getJobById, getWorkerInfo, listBalances, and listCountries — is registered with riskLevel: 'write'. In the Corsair permission model this means customers must grant write-level access to use any endpoint, including safe read operations. Read endpoints should use riskLevel: 'read'.

If anything remains after your next push, a bot commit will clean it up; a maintainer always does the final review and merge.

@github-actions github-actions Bot added the bot:round-1 Review bot posted consolidated findings label Jul 19, 2026
Comment thread packages/workday/webhooks/types.ts
@github-actions github-actions Bot added the bot:round-2 Review bot pushed an automated fix label Jul 19, 2026
@github-actions

Copy link
Copy Markdown

Remaining findings are being fixed by a bot commit — it will be re-reviewed automatically.

Comment thread packages/workday/webhooks/types.ts
Comment thread packages/workday/index.ts
Comment on lines +1077 to +1079
pluginWebhookMatcher: (request) => {
return 'x-workday-signature' in request.headers;
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 security Signature verification never wired into the webhook pipeline

pluginWebhookMatcher accepts any request that carries an x-workday-signature header, but never calls verifyWorkdayWebhookSignature. The HMAC-SHA256 verification logic is correctly implemented and exported, yet it is not invoked anywhere in the webhook routing path. As a result, the plugin accepts any payload delivered with this header regardless of whether it passes cryptographic validation, leaving the webhook endpoint open to spoofed events.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

fixed check

@github-actions

github-actions Bot commented Jul 19, 2026

Copy link
Copy Markdown

Maintainer review needed

Automated rounds are exhausted. Remaining findings:

  • P1 packages/workday/client.ts:131ApiError properties lost when re-wrapping
    makeWorkdayRequest catches the ApiError thrown by request() and converts it to a WorkdayAPIError, discarding status and retryAfter. In error-handlers.ts, RATE_LIMIT_ERROR.match checks error instanceof ApiError && error.status === 429 — but that guard is always false here because the error reaching the handler is a WorkdayAPIError, not an ApiError. The fallback msg.includes('429') may accidentally fire depending on the message text, but even then error.retryAfter is unavailable on WorkdayAPIError, so headersRetryAfterMs is always undefined and Workday's Retry-After header is silently ignored. Either re-throw the original ApiError (or a subclass that preserves status/retryAfter), or extend WorkdayAPIError to carry those fields and update the matcher accordingly.

@github-actions github-actions Bot added the needs-maintainer Automated rounds exhausted - human review needed label Jul 19, 2026
Comment thread packages/workday/index.ts
Comment thread packages/workday/endpoints/job.ts Outdated
WorkdayEndpointOutputs['getJobById']
>('v1/job/getJobById/{id}', ctx.key, {
method: 'GET',
// Justification: The makeWorkdayRequest client expects a generic unknown record.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Literal {id} placeholder never interpolated into URL

The URL 'v1/job/getJobById/{id}' is a plain string — JavaScript does not substitute {id} at runtime. The actual HTTP request goes to https://api.workday.com/v1/job/getJobById/{id} literally, which will return a 404 or an error from Workday. The id value from input is instead appended as a query parameter (via the query spread), so the path parameter is never filled. The URL must be constructed dynamically — for example `v1/job/jobs/${input?.id}` — or the input needs to be used to build the path before calling makeWorkdayRequest.

@Dhirenderchoudhary
Dhirenderchoudhary marked this pull request as draft July 19, 2026 13:02
@Dhirenderchoudhary
Dhirenderchoudhary marked this pull request as ready for review July 22, 2026 08:39
Comment thread packages/workday/webhooks/types.ts Outdated
Comment on lines +66 to +71
const expected = crypto
.createHmac('sha256', secret)
.update(bodyString)
.digest('base64');
if (signature !== expected) {
return { valid: false, error: 'Invalid webhook signature' };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 security Timing-attack-vulnerable signature comparison

signature !== expected is a regular string comparison, which leaks information about how many leading bytes of a forged signature match the real one via response-time differences. The shared verifyHmacSignature utility already exported from corsair/http uses crypto.timingSafeEqual for a constant-time comparison and also uses hex encoding (rather than base64). An attacker making repeated webhook requests can exploit the timing oracle to recover the HMAC secret character-by-character. Replace the comparison with verifyHmacSignature(bodyString, secret, signature) from corsair/http.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed check

Replace fabricated v1/{domain}/{op} stubs with staffing/absence/
recruiting/payroll/common/person routes, tenant-hosted OAuth, and
the 13 Composio-aligned triggers.
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@Dhirenderchoudhary, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 7 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

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.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 790e8325-1087-4746-9ab0-b430dd37e61c

📥 Commits

Reviewing files that changed from the base of the PR and between f68c244 and 5d2ff59.

📒 Files selected for processing (10)
  • packages/workday/api.test.ts
  • packages/workday/client.ts
  • packages/workday/endpoints/factory.ts
  • packages/workday/endpoints/routes.ts
  • packages/workday/endpoints/types.ts
  • packages/workday/error-handlers.ts
  • packages/workday/index.ts
  • packages/workday/package.json
  • packages/workday/schema/database.ts
  • packages/workday/webhooks/types.ts
📝 Walkthrough

Walkthrough

The PR adds a Workday provider package with 85 REST operations, typed request and response validation, OAuth authentication, webhook triggers, cache synchronization, error handling, schemas, tests, and Corsair provider registration.

Changes

Workday integration

Layer / File(s) Summary
Package and provider foundation
packages/workday/package.json, packages/workday/schema/*, packages/workday/tsconfig*, packages/workday/tsup.config.ts, packages/corsair/core/constants.ts
Adds package configuration, eight Workday entities, build and test setup, provider registration, and test mocks.
Route catalog and endpoint contracts
packages/workday/endpoints/routes.ts, packages/workday/endpoints/types.ts, packages/workday/endpoints/operations.ts, packages/workday/endpoints/index.ts
Defines 85 routes, route metadata, Zod input and output schemas, typed endpoint mappings, nested operation groups, and barrel exports.
Client and endpoint execution
packages/workday/client.ts, packages/workday/endpoints/factory.ts, packages/workday/endpoints/cache-sync.ts, packages/workday/error-handlers.ts
Normalizes Workday connections, constructs authenticated requests, validates operations, synchronizes caches, logs results, and handles retries and authentication errors.
Plugin factory and public Workday surface
packages/workday/index.ts
Adds OAuth configuration, plugin options and types, endpoint metadata, credential resolution, endpoint wiring, webhook wiring, and public exports.
Webhook matching and verification
packages/workday/webhooks/*
Adds payload schemas, event matching, HMAC verification, tenant matching, OAuth tenant linking, and 13 event triggers.
Validation suite
packages/workday/api.test.ts
Tests plugin initialization, routes, OAuth URLs, request construction, schemas, webhooks, errors, metadata, and cache synchronization.

Estimated code review effort: 5 (Critical) | ~90 minutes

Possibly related PRs

  • corsairdev/corsair#327: Adds a provider plugin with analogous client, endpoint, schema, webhook, and error-handling structures.
  • corsairdev/corsair#353: Uses the same provider registry updates in constants.ts.
  • corsairdev/corsair#375: Adds parallel endpoint, cache, schema, error-handling, package, and provider-registration patterns.

Suggested labels: plugin

Suggested reviewers: devjain32

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

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.
Linked Issues check ❓ Inconclusive The PR covers Workday APIs, authentication, flexible schemas, and webhook infrastructure, but the summary confirms only worker.updated explicitly. Confirm that worker.created and job.changed webhook triggers are implemented and tested.
✅ Passed checks (3 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 primary change: adding the Workday integration plugin.
Out of Scope Changes check ✅ Passed The changes support the Workday integration, including API routes, schemas, authentication, webhooks, tests, and provider registration.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

Keep vercel/whatsapp from main with workday registration;
regenerate lockfile.
Comment thread packages/workday/client.ts Fixed
@Dhirenderchoudhary

Copy link
Copy Markdown
Collaborator Author

@greptile review

Comment on lines +119 to +124
try {
return await request<T>(config, requestOptions);
} catch (error) {
if (error instanceof Error) throw new WorkdayAPIError(error.message);
throw new WorkdayAPIError('Unknown error');
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Error wrapping silently discards ApiError status and retry metadata

makeWorkdayRequest converts every thrown error into a plain WorkdayAPIError, stripping the original type. The rate-limit handler in error-handlers.ts then checks error instanceof ApiError && error.status === 429 (line 7) and reads error.retryAfter (line 13), but because the error is now a WorkdayAPIError, the instanceof ApiError branch never executes and retryAfterMs is always undefined. Workday's Retry-After header value is permanently lost, so rate-limited callers will always retry without the server-requested delay. The catch block should either rethrow the original ApiError directly or copy status and retryAfter onto WorkdayAPIError.

Rule Used: Every endpoint must validate inputs and outputs wi... (source)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 798f5f1

Comment on lines +124 to +129
try {
return await request<T>(config, requestOptions);
} catch (error) {
if (error instanceof Error) throw new WorkdayAPIError(error.message);
throw new WorkdayAPIError('Unknown error');
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 ApiError properties lost when re-wrapping

makeWorkdayRequest catches the ApiError thrown by request() and converts it to a WorkdayAPIError, discarding status and retryAfter. In error-handlers.ts, RATE_LIMIT_ERROR.match checks error instanceof ApiError && error.status === 429 — but that guard is always false here because the error reaching the handler is a WorkdayAPIError, not an ApiError. The fallback msg.includes('429') may accidentally fire depending on the message text, but even then error.retryAfter is unavailable on WorkdayAPIError, so headersRetryAfterMs is always undefined and Workday's Retry-After header is silently ignored. Either re-throw the original ApiError (or a subclass that preserves status/retryAfter), or extend WorkdayAPIError to carry those fields and update the matcher accordingly.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 798f5f1

@Dhirenderchoudhary

Copy link
Copy Markdown
Collaborator Author

@greptile review

@coderabbitai coderabbitai 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.

Actionable comments posted: 12

🧹 Nitpick comments (12)
packages/workday/endpoints/routes.ts (1)

164-206: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Two route entries define the same endpoint with different query sets.

getJobChangeReasonValues and getJobChangeReasons both target GET /values/jobChangesGroup/reason on Staffing v6, but they declare different queryParams and different output schemas (WorkdayCollectionSchema for both, yet different filter surfaces). getCollectionOfJobs and listJobs carry an explicit alias comment; these two do not.

If the duplication is an intentional Composio alias, add the same explanatory comment and align queryParams. If it is not intentional, remove one entry.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/workday/endpoints/routes.ts` around lines 164 - 206, Resolve the
duplicate GET route definitions for /values/jobChangesGroup/reason by either
removing one of getJobChangeReasonValues or getJobChangeReasons, or, if they are
intentional aliases, adding the established alias comment and making both
queryParams lists identical. Preserve a single consistent endpoint contract and
output schema.
packages/workday/endpoints/cache-sync.ts (4)

113-123: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

The cacheItems fallback can cache a collection envelope as an entity.

If a collection response uses a list key that is not in rule.listKeys, the function falls through to return [response]. cacheEntityId then reads id from the envelope. Workday collection envelopes carry total and data, so the risk is low today, but a response shaped like { id, items: [...] } would store the envelope under an entity id.

Restrict the single-resource fallback to responses that do not look like collections.

🛡️ Proposed change
 	for (const key of rule.listKeys ?? []) {
 		const value = response[key];
 		if (Array.isArray(value)) return value.filter(isRecord);
 	}
 
+	// A collection envelope without a recognized list key must not be cached as an entity.
+	if (typeof response.total === 'number') return [];
+
 	return [response];
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/workday/endpoints/cache-sync.ts` around lines 113 - 123, Update
cacheItems so the single-resource fallback only returns [response] when the
record does not contain collection-shaped fields such as total, data, or
array-valued properties; otherwise return an empty list. Preserve the existing
array filtering and configured rule.listKeys handling.

186-192: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Cache upserts run one at a time on the request path.

List routes such as getWorkersCollectionStaffing, listJobPostings, and getCollectionOfPayroll return up to 100 items per call, because limit is capped at 100 in types.ts. This loop awaits one database write per item, so a single API call adds up to 100 serial round trips before executeWorkdayOperation returns.

Run the upserts concurrently with a bound, or add a batch upsert to the cache client.

♻️ Proposed change
 		if (!client.upsertByEntityId) return;
 
-		for (const item of cacheItems(response, rule)) {
-			const entityId = cacheEntityId(item, rule);
-			if (!entityId) continue;
-			await client.upsertByEntityId(entityId, item);
-		}
+		const pending = cacheItems(response, rule)
+			.map((item) => ({ item, entityId: cacheEntityId(item, rule) }))
+			.filter((entry): entry is { item: Record<string, unknown>; entityId: string } =>
+				Boolean(entry.entityId),
+			);
+
+		const CONCURRENCY = 8;
+		for (let i = 0; i < pending.length; i += CONCURRENCY) {
+			await Promise.all(
+				pending
+					.slice(i, i + CONCURRENCY)
+					.map(({ entityId, item }) => client.upsertByEntityId?.(entityId, item)),
+			);
+		}
 	} catch (error) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/workday/endpoints/cache-sync.ts` around lines 186 - 192, Update the
cache upsert loop in the request path around client.upsertByEntityId to avoid
serial awaits: execute item upserts concurrently with a bounded concurrency
mechanism, or use an available batch-upsert API. Preserve entityId filtering and
ensure all scheduled upserts complete before the surrounding operation returns.

134-148: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The delete branch is unreachable, and cacheDeleteEntityIds returns at most one id.

No route in routes.ts uses method DELETE, and no route uses riskLevel: 'destructive'. So the branch at Lines 176-184 never executes in this PR. Also cacheDeleteEntityIds returns a single-element array despite the plural name, because it returns on the first matching key.

Either add the DELETE routes that this branch serves, or rename the helper to cacheDeleteEntityId and return one optional id. Keep the code if the DELETE routes arrive in a follow-up; state that intent in a comment.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/workday/endpoints/cache-sync.ts` around lines 134 - 148, Resolve the
unreachable delete flow by either adding the DELETE/destructive routes it
supports or, if those are intentionally deferred, retain the implementation and
add a comment documenting that follow-up intent. Also align cacheDeleteEntityIds
with its single-value behavior by renaming it to cacheDeleteEntityId and
returning one optional ID, updating all call sites accordingly.

159-173: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The ctx.db cast hides the cache client contract.

ctx.db is cast to an inline structural type inside the function. Any change to the generated database client breaks silently at runtime instead of at compile time. Extract a named type and place it next to the CacheEntity union, then have syncWorkdayOperationCache accept it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/workday/endpoints/cache-sync.ts` around lines 159 - 173, Extract the
inline database client shape from syncWorkdayOperationCache into a named type
placed alongside the CacheEntity union, then use that type for the function’s
ctx.db contract instead of casting ctx.db inline. Preserve the existing optional
upsertByEntityId and deleteByEntityId methods and entity lookup behavior.
packages/workday/endpoints/types.ts (1)

21-24: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

PaginationQueryShape is unused, and the query-value union is repeated about fifty times.

Every schema re-declares limit, offset, and the z.union([z.string(), z.number(), z.boolean(), z.array(z.string())]) shape inline. Extract shared helpers and spread them. This removes roughly a thousand duplicated lines and keeps the pagination bounds in one place.

♻️ Proposed refactor
 const PaginationQueryShape = {
 	limit: z.number().int().min(1).max(100).optional(),
 	offset: z.number().int().min(0).optional(),
 };
+
+const FilterValue = z
+	.union([z.string(), z.number(), z.boolean(), z.array(z.string())])
+	.optional();
+
+const IdShape = {
+	ID: z.string().min(1),
+	id: z.string().min(1).optional(),
+	workerId: z.string().min(1).optional(),
+};

Then each entry becomes, for example:

getJobById: z.object({ ...IdShape, ...PaginationQueryShape }).passthrough(),
getJobChangeFrequencies: z
  .object({
    effectiveDate: FilterValue,
    event: FilterValue,
    ...PaginationQueryShape,
  })
  .passthrough(),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/workday/endpoints/types.ts` around lines 21 - 24, Replace the unused
PaginationQueryShape and repeated inline query-value unions in the endpoint
schemas with shared FilterValue and PaginationQueryShape helpers. Update each
affected z.object definition, including getJobById and getJobChangeFrequencies,
to spread these helpers while preserving endpoint-specific fields and
passthrough behavior; keep the existing limit and offset bounds centralized in
PaginationQueryShape.
packages/workday/endpoints/operations.ts (1)

9-180: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

workdayEndpointsNested duplicates the group field and can drift from routes.ts.

Each route already declares group. This object restates the grouping for all 85 operations by hand. If someone adds a route, the nested surface silently omits it, because no type checks completeness.

Derive the grouping from the route table, or add a compile-time completeness check.

♻️ Sketch of a derived grouping
type WorkdayGroup = (typeof workdayRoutes)[number]['group'];

export const workdayEndpointsNested = workdayRoutes.reduce(
	(acc, route) => {
		const group = (acc[route.group] ??= {});
		group[route.name] = workdayOperations[route.name];
		return acc;
	},
	{} as Record<WorkdayGroup, Record<string, WorkdayEndpoint>>,
);

If the named exports must keep precise per-group types, keep the literal object but add an assertion that every route name appears exactly once:

type NestedNames = {
	[G in keyof typeof workdayEndpointsNested]: keyof (typeof workdayEndpointsNested)[G];
}[keyof typeof workdayEndpointsNested];
// Fails to compile if a route is missing from workdayEndpointsNested.
const _completeness: Record<WorkdayRouteName, true> = {} as Record<
	NestedNames,
	true
>;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/workday/endpoints/operations.ts` around lines 9 - 180, Update
workdayEndpointsNested to derive its groups and operation entries from
workdayRoutes, preserving the existing workdayOperations mappings and exported
access shape where practical. Ensure every route name is represented
automatically; alternatively, retain the literal object but add a compile-time
assertion using workdayRoute names that fails when any route is missing or
duplicated.
packages/workday/endpoints/factory.ts (2)

160-171: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Input validation failures and connection failures are not logged.

.parse at Lines 164-167, resolveConnection at Line 170, and resolvePath at Line 171 all run before the try block. If any of them throws, the finally block never runs, so logEventFromContext records nothing. Every other failure path records status: 'failed'.

Move these calls inside the try block so that failures produce a log event.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/workday/endpoints/factory.ts` around lines 160 - 171, Move the input
parsing and connection/path resolution calls currently before the try block into
that try block, preserving their existing order and behavior. Update the
surrounding flow in the endpoint handler containing WorkdayEndpointInputSchemas,
resolveConnection, and resolvePath so exceptions from all three operations reach
the existing catch/finally logging path and produce the same failed event as
other errors.

53-67: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Confirm the positional mapping between path placeholders and pathParams.

resolvePath maps the Nth {...} placeholder to route.pathParams[index] and ignores the placeholder name unless the array is shorter. For all current routes the placeholder names equal the pathParams entries in the same order, so the behavior is correct today. A future route whose pathParams order differs from the placeholder order would resolve the wrong value silently.

Prefer name-based lookup, and keep the positional path only as a fallback.

♻️ Proposed refactor
 	let index = 0;
 	return path.replace(/\{([^}]+)\}/g, (_, placeholder: string) => {
-		const mappedKey = route.pathParams[index];
 		index += 1;
-		const key = mappedKey ?? placeholder;
+		const key = route.pathParams.includes(placeholder)
+			? placeholder
+			: (route.pathParams[index - 1] ?? placeholder);
 		const value =
 			resolvePathParam(input, key) ?? resolvePathParam(input, placeholder);
 		return encodePathPart(value);
 	});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/workday/endpoints/factory.ts` around lines 53 - 67, Update
resolvePath so each placeholder first resolves using its own name, while
retaining the positional route.pathParams[index] lookup only as a fallback when
name-based resolution is unavailable. Preserve encoding and existing behavior
for routes whose names and positional entries match.
packages/workday/webhooks/triggers.ts (1)

14-34: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Validate the payload with the matching event schema.

WorkdayTriggerEventSchemas in packages/workday/webhooks/types.ts defines a schema for every event, but the handler never parses the request. The handler declares the payload as WorkdayWebhookOutputs[K], so consumers receive a typed value that was never checked at runtime. A bridge that posts a malformed body then passes verification and reaches downstream code with the wrong shape.

Parse the payload after signature verification and return 400 when parsing fails.

♻️ Proposed refactor to validate the payload
-import {
-	createWorkdayEventMatch,
-	verifyWorkdayWebhookSignature,
-} from './types';
+import {
+	createWorkdayEventMatch,
+	verifyWorkdayWebhookSignature,
+	WorkdayTriggerEventSchemas,
+} from './types';
 			if (!v.valid) {
 				return {
 					success: false,
 					statusCode: 401,
 					error: v.error || 'Signature verification failed',
 				};
 			}
+			const parsed = WorkdayTriggerEventSchemas[eventType].safeParse(
+				request.payload,
+			);
+			if (!parsed.success) {
+				return {
+					success: false,
+					statusCode: 400,
+					error: `Invalid ${eventType} payload`,
+				};
+			}
 			return { success: true };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/workday/webhooks/triggers.ts` around lines 14 - 34, Update the
handler around verifyWorkdayWebhookSignature to parse the verified request with
the matching schema from WorkdayTriggerEventSchemas. Perform schema validation
only after signature verification, return success false with statusCode 400 when
parsing fails, and pass the parsed payload downstream so the
WorkdayWebhookOutputs[K] value is runtime-validated.
packages/workday/webhooks/types.ts (1)

83-87: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Header reads assume lowercase keys and scalar values. Both sites look up Workday headers with a lowercase literal key and assume a specific value shape. HTTP header names are case-insensitive, and the Corsair contract types header values as string | string[] | undefined. Add one shared header helper in the Workday package that lowercases the lookup key and collapses array values, then use it at both sites.

  • packages/workday/webhooks/types.ts#L83-L87: read x-workday-event through the shared helper instead of a direct lowercase index and a typeof === 'string' check.
  • packages/workday/index.ts#L953-L958: replace the two in checks with the shared helper so X-Workday-Signature and X-Workday-Event also match.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/workday/webhooks/types.ts` around lines 83 - 87, The Workday webhook
header checks need case-insensitive lookup and support for Corsair’s
string-or-array header values. Add one shared header helper in the Workday
package that lowercases the lookup key and collapses array values, then use it
in packages/workday/webhooks/types.ts:83-87 for x-workday-event and
packages/workday/index.ts:953-958 for X-Workday-Signature and X-Workday-Event,
replacing the direct index and in checks while preserving the existing matching
behavior.
packages/workday/index.ts (1)

166-170: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Constrain workdayEndpointSchemas with RequiredPluginEndpointSchemas.

Import the type from corsair/core and use satisfies RequiredPluginEndpointSchemas<typeof workdayEndpointsNested> so new endpoint paths require schema entries.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/workday/index.ts` around lines 166 - 170, Import
RequiredPluginEndpointSchemas from corsair/core and constrain the
workdayEndpointSchemas object with satisfies
RequiredPluginEndpointSchemas<typeof workdayEndpointsNested>, ensuring every
endpoint path has a corresponding schema entry while preserving the existing
schema mappings.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/workday/api.test.ts`:
- Around line 242-251: Update packages/workday/api.test.ts lines 242-251 to
exercise the webhook processing path with a known valid x-workday-signature and
assert that invalid and missing signatures are rejected; do not rely only on
trigger.match. Update packages/workday/api.test.ts lines 71-78 to assert
registration of the required worker.updated, worker.created, and job.changed
triggers, while preserving the existing trigger assertions as applicable.

In `@packages/workday/client.ts`:
- Around line 25-47: Update normalizeWorkdayHost to validate scheme-less inputs
as a hostname with an optional port and no path, userinfo, or other URL
components before returning them. When a value contains ://, rethrow any URL
parsing failure instead of falling through to the raw trailing-slash result,
while preserving the HTTPS-only requirement and normalized host return.

In `@packages/workday/endpoints/factory.ts`:
- Around line 191-198: Update the finally block around logEventFromContext so a
logging rejection cannot replace an existing error from makeWorkdayRequest or
output validation. Preserve the original operation error while handling or
suppressing logging failures, and keep successful request behavior unchanged.
- Around line 102-125: Update requestBody to always exclude the pagination keys
limit and offset, regardless of route.queryParams. Also exclude the optional id
and workerId alias fields unconditionally by including their declared aliases in
the filtered key set, while preserving existing path, query, control-key, and
undefined-value filtering.

In `@packages/workday/endpoints/routes.ts`:
- Around line 88-100: Remove the limit and offset entries from
getJobById.queryParams, leaving the single-resource GET route with only its ID
path parameter and existing metadata.
- Around line 1446-1456: Correct the Workday route definitions consumed by
getWorkerInfo and the associated service/version mappings: replace Compensation
v3 /workers/{ID} with the documented service and version, and update the
Recruiting and Staffing entries to documented versions for their configured
paths. Keep workdayRouteByName and getWorkdayRoute unchanged.

In `@packages/workday/endpoints/types.ts`:
- Around line 22-23: Apply z.coerce-based pagination validation to every limit
and offset field in WorkdayEndpointInputSchemas, rather than only updating the
unused PaginationQueryShape. Replace all duplicated declarations or reuse the
shared coercing shape, preserving the existing integer and range constraints; if
supporting Zod versions below 3.20 is required, raise the peer dependency
minimum accordingly.

In `@packages/workday/error-handlers.ts`:
- Around line 6-10: Update the rate-limit matcher in the error handler’s `match`
function to stop treating arbitrary occurrences of “429” in error messages as
rate limits. Preserve the existing ApiError status check and only accept a
delimited status-code token or another exact status representation, so unrelated
IDs, paths, and timestamps do not trigger retries.

In `@packages/workday/index.ts`:
- Around line 914-920: Update the OAuth configuration flow around
workdayOAuthUrls so authType === 'oauth_2' requires an explicit host, just as
tenant is required. Remove the wd2-impl-services1.workday.com fallback and
validate or reject missing host before constructing OAuth URLs, while preserving
the existing tenant-specific URL generation for valid configurations.

In `@packages/workday/package.json`:
- Around line 16-19: Create packages/workday/tsconfig.typecheck.json extending
the existing tsconfig.json and override emitDeclarationOnly to false. Update the
typecheck script in package.json to run tsc --noEmit --project
tsconfig.typecheck.json, while leaving the build configuration and scripts
unchanged.

In `@packages/workday/webhooks/types.ts`:
- Around line 106-113: Update the HMAC verification flow around
verifyHmacSignature to require request.rawBody as a string; if it is absent,
treat verification as failed instead of serializing request.payload. Preserve
signature verification only against the exact raw body received.
- Around line 83-87: Update the webhook event-header matching in the returned
predicate to locate x-workday-event case-insensitively and accept both string
and string[] header values, matching eventType when any supplied value matches.
Apply the same case-insensitive header lookup and array-aware matching in the
related logic near the workday webhook handling entry point, while preserving
body-based matching behavior.

---

Nitpick comments:
In `@packages/workday/endpoints/cache-sync.ts`:
- Around line 113-123: Update cacheItems so the single-resource fallback only
returns [response] when the record does not contain collection-shaped fields
such as total, data, or array-valued properties; otherwise return an empty list.
Preserve the existing array filtering and configured rule.listKeys handling.
- Around line 186-192: Update the cache upsert loop in the request path around
client.upsertByEntityId to avoid serial awaits: execute item upserts
concurrently with a bounded concurrency mechanism, or use an available
batch-upsert API. Preserve entityId filtering and ensure all scheduled upserts
complete before the surrounding operation returns.
- Around line 134-148: Resolve the unreachable delete flow by either adding the
DELETE/destructive routes it supports or, if those are intentionally deferred,
retain the implementation and add a comment documenting that follow-up intent.
Also align cacheDeleteEntityIds with its single-value behavior by renaming it to
cacheDeleteEntityId and returning one optional ID, updating all call sites
accordingly.
- Around line 159-173: Extract the inline database client shape from
syncWorkdayOperationCache into a named type placed alongside the CacheEntity
union, then use that type for the function’s ctx.db contract instead of casting
ctx.db inline. Preserve the existing optional upsertByEntityId and
deleteByEntityId methods and entity lookup behavior.

In `@packages/workday/endpoints/factory.ts`:
- Around line 160-171: Move the input parsing and connection/path resolution
calls currently before the try block into that try block, preserving their
existing order and behavior. Update the surrounding flow in the endpoint handler
containing WorkdayEndpointInputSchemas, resolveConnection, and resolvePath so
exceptions from all three operations reach the existing catch/finally logging
path and produce the same failed event as other errors.
- Around line 53-67: Update resolvePath so each placeholder first resolves using
its own name, while retaining the positional route.pathParams[index] lookup only
as a fallback when name-based resolution is unavailable. Preserve encoding and
existing behavior for routes whose names and positional entries match.

In `@packages/workday/endpoints/operations.ts`:
- Around line 9-180: Update workdayEndpointsNested to derive its groups and
operation entries from workdayRoutes, preserving the existing workdayOperations
mappings and exported access shape where practical. Ensure every route name is
represented automatically; alternatively, retain the literal object but add a
compile-time assertion using workdayRoute names that fails when any route is
missing or duplicated.

In `@packages/workday/endpoints/routes.ts`:
- Around line 164-206: Resolve the duplicate GET route definitions for
/values/jobChangesGroup/reason by either removing one of
getJobChangeReasonValues or getJobChangeReasons, or, if they are intentional
aliases, adding the established alias comment and making both queryParams lists
identical. Preserve a single consistent endpoint contract and output schema.

In `@packages/workday/endpoints/types.ts`:
- Around line 21-24: Replace the unused PaginationQueryShape and repeated inline
query-value unions in the endpoint schemas with shared FilterValue and
PaginationQueryShape helpers. Update each affected z.object definition,
including getJobById and getJobChangeFrequencies, to spread these helpers while
preserving endpoint-specific fields and passthrough behavior; keep the existing
limit and offset bounds centralized in PaginationQueryShape.

In `@packages/workday/index.ts`:
- Around line 166-170: Import RequiredPluginEndpointSchemas from corsair/core
and constrain the workdayEndpointSchemas object with satisfies
RequiredPluginEndpointSchemas<typeof workdayEndpointsNested>, ensuring every
endpoint path has a corresponding schema entry while preserving the existing
schema mappings.

In `@packages/workday/webhooks/triggers.ts`:
- Around line 14-34: Update the handler around verifyWorkdayWebhookSignature to
parse the verified request with the matching schema from
WorkdayTriggerEventSchemas. Perform schema validation only after signature
verification, return success false with statusCode 400 when parsing fails, and
pass the parsed payload downstream so the WorkdayWebhookOutputs[K] value is
runtime-validated.

In `@packages/workday/webhooks/types.ts`:
- Around line 83-87: The Workday webhook header checks need case-insensitive
lookup and support for Corsair’s string-or-array header values. Add one shared
header helper in the Workday package that lowercases the lookup key and
collapses array values, then use it in packages/workday/webhooks/types.ts:83-87
for x-workday-event and packages/workday/index.ts:953-958 for
X-Workday-Signature and X-Workday-Event, replacing the direct index and in
checks while preserving the existing matching behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8718de06-7e97-48b2-940c-b43e9d095d73

📥 Commits

Reviewing files that changed from the base of the PR and between f298e74 and f68c244.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (24)
  • packages/corsair/core/constants.ts
  • packages/workday/api.test.ts
  • packages/workday/client.ts
  • packages/workday/endpoints/cache-sync.ts
  • packages/workday/endpoints/factory.ts
  • packages/workday/endpoints/index.ts
  • packages/workday/endpoints/operations.ts
  • packages/workday/endpoints/routes.ts
  • packages/workday/endpoints/types.ts
  • packages/workday/error-handlers.ts
  • packages/workday/index.ts
  • packages/workday/jest.config.cjs
  • packages/workday/mocks/corsair-core-mock.ts
  • packages/workday/package.json
  • packages/workday/schema/database.ts
  • packages/workday/schema/index.ts
  • packages/workday/tsconfig.json
  • packages/workday/tsconfig.test.json
  • packages/workday/tsup.config.ts
  • packages/workday/webhooks/index.ts
  • packages/workday/webhooks/oauth-tenant-link.ts
  • packages/workday/webhooks/tenant-matcher.ts
  • packages/workday/webhooks/triggers.ts
  • packages/workday/webhooks/types.ts

Comment thread packages/workday/api.test.ts
Comment thread packages/workday/client.ts
Comment thread packages/workday/endpoints/factory.ts
Comment thread packages/workday/endpoints/factory.ts
Comment thread packages/workday/endpoints/routes.ts
Comment thread packages/workday/error-handlers.ts
Comment thread packages/workday/index.ts Outdated
Comment thread packages/workday/package.json
Comment thread packages/workday/webhooks/types.ts
Comment thread packages/workday/webhooks/types.ts Outdated
@devjain32
devjain32 merged commit ece99b8 into corsairdev:main Aug 12, 2026
9 of 10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bot:round-1 Review bot posted consolidated findings bot:round-2 Review bot pushed an automated fix core Changes in packages/corsair needs-maintainer Automated rounds exhausted - human review needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Integration Request: Workday API

3 participants