Skip to content

feat: JazzHR candidate corpus + wildcard scope for all providers - #309

Open
imaustink wants to merge 1 commit into
mainfrom
feat/jazzhr-corpus-and-wildcard-scope
Open

imaustink wants to merge 1 commit into
mainfrom
feat/jazzhr-corpus-and-wildcard-scope

Conversation

@imaustink

Copy link
Copy Markdown
Owner

What

Adds a new jazzhr knowledge-base connection type that fetches candidates from JazzHR (Resumator) and ingests them into Qdrant for searching, and a scope.all wildcard that every corpus provider now supports.

JazzHR driver — apps/connection-broker/src/drivers/jazzhr.ts

  • A Corpus is scoped to one job (scope.jobID) or every permitted job (scope.all). fetch renders a candidate — profile, questionnaire answers, and the decoded resume — to Markdown for embedding. Resume is the inline profile text when present, otherwise the best resume file: PDF via unpdf, DOCX via OOXML, plain text sniffed (MIME is frequently wrong/empty).
  • Built from a real, working integration (bitovi/RatingCandidates), not the published docs, which are incomplete/wrong. The quirks it reproduces, each learned live there:
    • Auth is a query param, sent twice: ?apikey=<k>&api_key=<k>. No header auth.
    • A WAF 403s the default runtime User-Agent — every call sets one explicitly.
    • Pagination is a URL path segment: /page/N, 100/page, stop on a short page; a one-item listing comes back as a bare object, not a 1-element array.
    • 240/min rate limit: retry only on 429, honouring Retry-After.
  • probeGranularity: "connection" — JazzHR has no per-candidate ACL, so there is nothing to probe per candidate.

Wildcard scope (scope.all) — all providers

Requested: a corpus that covers all jobs, and the same capability on every existing corpus type.

  • Shared listWildcard helper: a wildcard reaches the Connection's allowedScopes when capped, otherwise the units the driver enumerates — jazzhr /jobs, slack users.conversations, confluence /spaces. gdrive requires a folderIDs cap (Drive has no cheap "every folder" query; indexing a whole account is a footgun), and refuses an uncapped wildcard with a clear error.
  • The cap is carried to the driver as scope.allowedUnits (the security boundary reaches the driver). A wildcard corpus re-lists in full each reconcile — per-unit cursors don't compose into one incremental cursor, the reconcile pass is the source of truth, and the worker's content-hash dedupe re-embeds nothing that didn't change.

CRD / controller

  • Connection.provider enum += jazzhr; allowedScopes.jobIDs added.
  • CorpusScope += jobID + all; the exactly-one CEL rule now counts jobID and all.
  • checkScope gains a jazzhr case and permits any known provider's wildcard (bounded at enumeration, not by a single-unit match).
  • Samples, connection-broker apiOrigins.jazzhr, and the generated CRDs regenerated via make manifests generate.

Ingest depth

Profile + resume file (the recommended default — richest searchable text). Easy to narrow to profile-only if preferred.

Tests

  • Broker unit suite: 475 passing (+32). New: jazzhr.test.ts (auth/WAF, path-segment pagination, bare-object, wildcard capped+uncapped, resume inline/file/download/decode, 429 retry, error classification), wildcard.test.ts, dispatch/binding cases, and slack/gdrive wildcard cases.
  • Go controller envtest: passing — jazzhr job scope, wildcard permitted under a cap, named-job cap still enforced.
  • e2e: fake-jazzhr manifest + a wildcard corpus spec asserting the wildcard reached candidates from both jobs. ⚠️ The cluster e2e suite needs minikube and was not run locally — please let CI validate that portion.

Not touched

Nothing in the chunk/embed/Qdrant-write path, RBAC, KnowledgeBase CRD, or server routes — those are generic over the Driver interface.

🤖 Generated with Claude Code

https://claude.ai/code/session_01MtYgf8xTKWvrgHCDcDkSEd

… all providers

Add a `jazzhr` knowledge-base connection type that ingests JazzHR
(Resumator) candidates into Qdrant for retrieval, and a `scope.all`
wildcard supported by every provider.

JazzHR driver (apps/connection-broker/src/drivers/jazzhr.ts):
- A Corpus is scoped to one job (scope.jobID) or every permitted job
  (scope.all). `fetch` renders a candidate — profile, questionnaire and
  the decoded resume (inline, or a file: PDF via unpdf, DOCX via OOXML,
  text sniffed) — to Markdown.
- Built from a working integration (bitovi/RatingCandidates), not the
  published docs: dual apikey/api_key query auth, a WAF-satisfying
  User-Agent, path-segment pagination (/page/N, 100/page), the
  bare-object-for-one-item shape, and 429 retry honouring Retry-After.
- probeGranularity "connection": JazzHR has no per-candidate ACL.

Wildcard scope (scope.all) across confluence/slack/gdrive/jazzhr:
- Shared listWildcard helper: a wildcard reaches the Connection's
  allowedScopes when capped, otherwise the units the driver enumerates
  (jazzhr /jobs, slack users.conversations, confluence /spaces). gdrive
  requires a folderIDs cap — it has no cheap "every folder" query.
- Bounded on the scope as allowedUnits, re-lists in full each reconcile
  (per-unit cursors don't compose; reconcile is the source of truth and
  content-hash dedupe re-embeds nothing unchanged).

CRD/controller: Connection provider enum += jazzhr and allowedScopes.jobIDs;
CorpusScope += jobID + all with an updated exactly-one CEL rule; checkScope
gains a jazzhr case and permits any known provider's wildcard. Samples,
connection-broker apiOrigins.jazzhr, and an e2e fake + wildcard spec added.

Tests: jazzhr + wildcard unit tests, dispatch/binding cases, Go controller
cases (all green); full broker suite 475 passing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MtYgf8xTKWvrgHCDcDkSEd
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.

1 participant