feat(ingestion): add Workday HR connector (RaaS report contract, BambooHR parity)#1305
Conversation
…ooHR parity) Add the Workday connector following the BambooHR pattern end to end: - Declarative manifest (CDK v7.0.4): two full-refresh streams (workers, leave_requests) fetched from customer-built RaaS custom reports with ISU Basic auth. Workday has no fixed bulk API, so the connector ships a report contract (exact column XML aliases + From_Date/To_Date prompts); extra report columns flow into raw_data. - dbt chain mirroring bamboohr model-for-model: RMT promotion, SCD2 snapshot (Last_Functionally_Updated deliberately untracked), field-level history, identity_inputs (DELETE on Worker_Status=Terminated), and class_people / class_hr_events / class_hr_working_hours staging views. - Specs: PRD + DESIGN (cpt validate PASS, registered in artifacts.toml); workday.md rewritten as the v1 overview. - Local test rig: RaaS fixtures + mock server enforcing Basic auth, format=json, and leave prompts. Verified: check SUCCEEDED, read returns 5 workers / 4 leave requests with 0 errors, raw_data passthrough of non-contract columns, deterministic full-refresh re-read. There is no public Workday developer tenant; real-tenant validation runs against the customer Sandbox during onboarding (check gates the report contract). Signed-off-by: Roman Mitasov <Roman.Mitasov@constructor.tech> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
📝 WalkthroughWalkthroughThis PR implements a complete Workday HR Directory connector, migrating from a v1 specification into a v2 system. It includes comprehensive PRD/DESIGN documentation, an Airbyte declarative manifest with two streams (workers and leave requests), a dbt Bronze→Silver transformation pipeline, testing fixtures with a mock RaaS server, and K8s Secret configuration for deployment. ChangesWorkday HR Connector
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 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 `@docs/components/connectors/hr-directory/workday/specs/DESIGN.md`:
- Around line 414-428: The code block showing the Connection topology in
DESIGN.md is an unlabeled fenced block (the topology fence) which triggers
MD040; update the fence to include a language tag by changing the opening ``` to
```text so the block becomes a labeled "text" fence; locate the topology block
beginning with "Connection: workday-{source-id}" in
docs/components/connectors/hr-directory/workday/specs/DESIGN.md and add the
language tag to the opening fence to resolve the lint and improve parsing.
In `@src/ingestion/connectors/hr-directory/workday/connector.yaml`:
- Around line 257-273: The leave_requests stream is dropping the full report
payload because the transformations only add tenant_id, source_id, and
unique_key; add a field to forward the entire raw payload (e.g., an
AddedFieldDefinition with path [raw_data] and value set to the whole record) in
the same transformations block so the stream preserves all original columns
outside the inline schema; update the transformations list (the AddFields entry
that contains AddedFieldDefinition items) to include this raw_data field
alongside tenant_id, source_id, and unique_key.
In `@src/ingestion/connectors/hr-directory/workday/dbt/workday__hr_events.sql`:
- Around line 26-29: The LEFT JOIN against {{ source('workday', 'workers') }}
(alias w) is missing the connector instance key and can mix identities; update
the join condition in the workday__hr_events.sql join where lr.Employee_ID =
w.Employee_ID AND lr.tenant_id = w.tenant_id to also include lr.source_id =
w.source_id so the join keys are (Employee_ID, tenant_id, source_id) to prevent
cross-instance mixing and duplicate/incorrect rows.
In `@src/ingestion/connectors/hr-directory/workday/fixtures/mock_raas.py`:
- Around line 57-59: The mock currently accepts any Authorization header; update
the authentication check in the handler to require HTTP Basic credentials by
validating that self.headers contains "Authorization" and that its value starts
with "Basic " (reject otherwise), and call self._reply(401, ...) when missing or
not Basic so Bearer or other schemes no longer pass; locate the check around
self.headers and the self._reply call and replace the loose presence check with
this scheme validation.
🪄 Autofix (Beta)
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
Run ID: e72811f6-ce80-454a-a9e9-433c2ffbdab5
📒 Files selected for processing (19)
cypilot/config/artifacts.tomldocs/components/connectors/hr-directory/workday/specs/DESIGN.mddocs/components/connectors/hr-directory/workday/specs/PRD.mddocs/components/connectors/hr-directory/workday/workday.mdsrc/ingestion/connectors/hr-directory/workday/README.mdsrc/ingestion/connectors/hr-directory/workday/connector.yamlsrc/ingestion/connectors/hr-directory/workday/dbt/schema.ymlsrc/ingestion/connectors/hr-directory/workday/dbt/workday__bronze_promoted.sqlsrc/ingestion/connectors/hr-directory/workday/dbt/workday__hr_events.sqlsrc/ingestion/connectors/hr-directory/workday/dbt/workday__identity_inputs.sqlsrc/ingestion/connectors/hr-directory/workday/dbt/workday__to_class_people.sqlsrc/ingestion/connectors/hr-directory/workday/dbt/workday__workers_fields_history.sqlsrc/ingestion/connectors/hr-directory/workday/dbt/workday__workers_snapshot.sqlsrc/ingestion/connectors/hr-directory/workday/dbt/workday__working_hours.sqlsrc/ingestion/connectors/hr-directory/workday/descriptor.yamlsrc/ingestion/connectors/hr-directory/workday/fixtures/leave_requests.jsonsrc/ingestion/connectors/hr-directory/workday/fixtures/mock_raas.pysrc/ingestion/connectors/hr-directory/workday/fixtures/workers.jsonsrc/ingestion/secrets/connectors/workday.yaml.example
- Forward raw_data on leave_requests too: the leave report is also customer-built, so extra columns must survive into Bronze (manifest transformation + schema, DESIGN table, README note). - Add source_id to the hr_events workers JOIN to prevent cross-instance identity mixing when one tenant runs multiple workday sources. - Mock RaaS now requires the Basic auth scheme, not just any Authorization header, so connector auth regressions fail the rig. - Label the deployment topology fence in DESIGN.md (MD040). Re-verified against the mock: read returns 5 workers / 4 leave_requests with 0 errors, leave records carry raw_data, Bearer auth is rejected. Signed-off-by: Roman Mitasov <Roman.Mitasov@constructor.tech> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The publish-chart job both patch-bumps the umbrella `version` and rewrites `ingestion.toolboxImage` in charts/insight/values.yaml from the tree checked out at this run's trigger SHA. When two version-bumping PRs merge back-to-back — especially two that touch src/ingestion/** and so both rebuild the toolbox — the second run's trigger SHA does not yet contain the first run's `chore(release)` commit, so both runs: * compute the SAME next umbrella version, and * rewrite the SAME `ingestion.toolboxImage` line to their own build tag. The first run pushes its release commit; the second run's commit-back is then rejected (non-fast-forward). The retry's `git pull --rebase` hits a conflict in charts/insight/values.yaml — specifically on the `ingestion.toolboxImage` line (the version line auto-merges because both runs wrote the same value) — and aborts -> exit 1. A plain job re-run repeats this forever because `checkout` re-pins the same stale SHA. Secondary symptom: both runs `helm push` the same chart version with different contents, so the chart published to GHCR diverges from what main records as that version. Fix: re-anchor the working tree to the live branch tip (`git fetch` + `git reset --hard origin/$GITHUB_REF_NAME`) before any value is computed, so version-compute, the toolboxImage ref and the commit-back are all consistent with the branch tip — and a re-run recomputes against the refreshed tip instead of replaying the stale SHA. Combined with the existing per-ref `concurrency` serialisation the final push fast-forwards, so the fragile rebase-retry is replaced with a fail-loud guard. Reproduced by run 27535189783 (constructorfabric#1306 Figma then constructorfabric#1305 Workday merged back-to-back, both touching src/ingestion/**). Note: bump-descriptors shares the same rebase-retry pattern; left untouched here to keep this fix scoped to the reproduced failure. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The publish-chart job both patch-bumps the umbrella `version` and rewrites `ingestion.toolboxImage` in charts/insight/values.yaml from the tree checked out at this run's trigger SHA. When two version-bumping PRs merge back-to-back — especially two that touch src/ingestion/** and so both rebuild the toolbox — the second run's trigger SHA does not yet contain the first run's `chore(release)` commit, so both runs: * compute the SAME next umbrella version, and * rewrite the SAME `ingestion.toolboxImage` line to their own build tag. The first run pushes its release commit; the second run's commit-back is then rejected (non-fast-forward). The retry's `git pull --rebase` hits a conflict in charts/insight/values.yaml — specifically on the `ingestion.toolboxImage` line (the version line auto-merges because both runs wrote the same value) — and aborts -> exit 1. A plain job re-run repeats this forever because `checkout` re-pins the same stale SHA. Secondary symptom: both runs `helm push` the same chart version with different contents, so the chart published to GHCR diverges from what main records as that version. Fix: re-anchor the working tree to the live branch tip (`git fetch` + `git reset --hard origin/$GITHUB_REF_NAME`) before any value is computed, so version-compute, the toolboxImage ref and the commit-back are all consistent with the branch tip — and a re-run recomputes against the refreshed tip instead of replaying the stale SHA. Combined with the existing per-ref `concurrency` serialisation the final push fast-forwards, so the fragile rebase-retry is replaced with a fail-loud guard. Reproduced by run 27535189783 (constructorfabric#1306 Figma then constructorfabric#1305 Workday merged back-to-back, both touching src/ingestion/**). Note: bump-descriptors shares the same rebase-retry pattern; left untouched here to keep this fix scoped to the reproduced failure. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The publish-chart job both patch-bumps the umbrella `version` and rewrites `ingestion.toolboxImage` in charts/insight/values.yaml from the tree checked out at this run's trigger SHA. When two version-bumping PRs merge back-to-back — especially two that touch src/ingestion/** and so both rebuild the toolbox — the second run's trigger SHA does not yet contain the first run's `chore(release)` commit, so both runs: * compute the SAME next umbrella version, and * rewrite the SAME `ingestion.toolboxImage` line to their own build tag. The first run pushes its release commit; the second run's commit-back is then rejected (non-fast-forward). The retry's `git pull --rebase` hits a conflict in charts/insight/values.yaml — specifically on the `ingestion.toolboxImage` line (the version line auto-merges because both runs wrote the same value) — and aborts -> exit 1. A plain job re-run repeats this forever because `checkout` re-pins the same stale SHA. Secondary symptom: both runs `helm push` the same chart version with different contents, so the chart published to GHCR diverges from what main records as that version. Fix: re-anchor the working tree to the live branch tip (`git fetch` + `git reset --hard origin/$GITHUB_REF_NAME`) before any value is computed, so version-compute, the toolboxImage ref and the commit-back are all consistent with the branch tip — and a re-run recomputes against the refreshed tip instead of replaying the stale SHA. Combined with the existing per-ref `concurrency` serialisation the final push fast-forwards, so the fragile rebase-retry is replaced with a fail-loud guard. Reproduced by run 27535189783 (#1306 Figma then #1305 Workday merged back-to-back, both touching src/ingestion/**). Note: bump-descriptors shares the same rebase-retry pattern; left untouched here to keep this fix scoped to the reproduced failure. Co-authored-by: Roman Mitasov <Roman.Mitasov@constructor.tech> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Summary
Adds the Workday HR connector — full parity with the BambooHR pattern: current-state extraction, SCD2 history at the Silver layer, identity feed.
Workday has no fixed bulk-extraction API, so the connector defines a report contract instead: the customer builds two RaaS custom reports (
Insight_Employee_Sync,Insight_Leave_Sync) with exact column XML aliases andFrom_Date/To_Dateprompts, shares them with an ISU, and the connector fetches them as JSON. Extra customer columns flow intoraw_datawithout connector changes.Connector (
src/ingestion/connectors/hr-directory/workday/)workers+leave_requests(full refresh),Report_Entryextraction,unique_key/tenant_id/source_id/raw_datainjectiondescriptor.yaml(semver 1.0.0,bronze_workday,tag:workday+), Secret example, README with the full report contract + ISU domain checklistdbt (mirrors bamboohr model-for-model)
workday__bronze_promoted→ RMT promotionworkday__workers_snapshot→ SCD2 (Last_Functionally_Updateddeliberately untracked to avoid spurious versions)workday__workers_fields_history→ per-field change logworkday__identity_inputs→ identity observations; DELETE onWorker_Status='Terminated'workday__to_class_people/workday__hr_events/workday__working_hoursstaging viewsSpecs
docs/components/connectors/hr-directory/workday/specs/{PRD,DESIGN}.md— cpt validate PASS, registered incypilot/config/artifacts.toml(also fixed the staleworkday.mdregistry path)workday.mdrewritten as the v1 overview (previous draft described a different, effective-dated design)Local test rig (no public Workday dev tenant exists)
fixtures/— RaaS-shaped fixtures +mock_raas.pyenforcing Basic auth,format=json, and leave promptsTest plan
source.sh validate— Manifest is validsource.sh checkagainst mock — SUCCEEDEDsource.sh read— 5 workers / 4 leave_requests, 0 errors; all records carrytenant_id/source_id/unique_key; non-contract column (Cost_Center) lands inraw_data; second read deterministicdbt parse+dbt ls -s tag:workday— all 7 models + tests resolvecpt validateon PRD/DESIGN — PASSNote: the manifest passes the repaired
validate-strictgate from #1311 (pinned CDK 6.60.9 +$refresolution) — verified locally: "Manifest is strictly valid (Builder-UI compatible)".🤖 Generated with Claude Code
Summary by CodeRabbit