Skip to content

feat(kb): add production knowledge base ingestion - #5174

Draft
rschlaefli wants to merge 24 commits into
v3-aifrom
kb-poc
Draft

feat(kb): add production knowledge base ingestion#5174
rschlaefli wants to merge 24 commits into
v3-aifrom
kb-poc

Conversation

@rschlaefli

@rschlaefli rschlaefli commented Jul 15, 2026

Copy link
Copy Markdown
Member

What this adds

This draft PR is the integration line for the knowledge-base workflow into v3-ai. W1 through W4 are now on the branch:

  1. Lecturers can manage blob and public-URL resources, ingest or retry them, and see operation history separately from the version currently served to AI.
  2. Ingestion uses the canonical dispatch, source-gateway, status-callback, and reconciliation contracts with ownership checks and bounded URL handling.
  3. Chatbots can use one enabled knowledge base at a time. Lecturers can connect, replace, or disconnect that binding from the KB detail page.
  4. Each KB retrieval request gets a five-minute ES256 scope token derived from database state. The token is sent only to the reserved KB MCP server.

The graph visualization and model-selection work remains parked in draft PR #5206 for W9. PR #5078 is reference material only; its useful behavior is being reimplemented selectively.

How it works

  • KBChatbot stores the KB-to-chatbot binding. PostgreSQL enforces the one-enabled-KB-per-chatbot rule with a partial unique index.
  • Attach, replace, and detach operations lock owner-scoped rows and reconcile both tutor and explainer MCP configurations to the exact doc_query tool.
  • The chat route resolves the enabled KB and owning thread from PostgreSQL before loading MCP tools. It never accepts a client-supplied KB id or signs a foreign thread id.
  • Scope tokens contain kb_id, chatbot_id, an opaque session subject, jti, iat, and exp. Participant identity is not included, and the existing participant JWT path is unchanged.
  • The reserved KB server loads only with scope_token authentication and complete KB, chatbot, and session context. Misconfiguration fails closed.
  • The KB_doc_query card is registered under the emitted runtime name. Its EN/DE UI shows generic failures and does not expose upstream error text.

Branch coverage

  • Base: v3-ai
  • Head: 69c2a499a
  • Reviewed: 24 commits, 151 files, 13,711 insertions and 369 deletions relative to origin/v3-ai
  • Covered: the original KB POC, W1 consolidation, W2 ingestion-contract alignment, W3 attempt history and serving state, W4 chatbot binding and scoped retrieval, generated artifacts, migrations, wiki and skill updates, review fixes, and browser evidence

Review focus

  • Check the partial unique index and the attach/replace transaction for concurrent writers.
  • Check the boundary between participant authentication and the separate ES256 retrieval scope.
  • Confirm the chart keeps the private signing key in the external chat secret. Only issuer, audience, and key id belong in the ConfigMap.
  • The live retrieval canary is deliberately deferred. The shared doc-query platform still needs the R4.3 Klicker tenant mount and its prerequisite isolation gates.

Verification

Current head:

  • pnpm run check:all in the Node 24 devcontainer: 25 typecheck tasks and six lint tasks passed; formatting, syncpack, AGENTS, and Prisma schema-sync checks passed.
  • pnpm run build: 22 of 22 production tasks passed.
  • Real-PostgreSQL KB GraphQL suite: 31 of 31 tests passed.
  • Full W4 chat/MCP suite: 24 of 24 tests passed. The final token and foreign-thread checks also passed as a focused 12-test run after review fixes.
  • Prisma seed reconciliation: four cases passed for enabled/disabled bindings and existing/new MCP configurations. A separate real-database probe confirmed both states.
  • Helm lint and rendered-template checks passed. The rendered ConfigMap contains no private key.
  • Documentation validation passed with 20 pre-existing hygiene warnings. The W4 Opengrep diff scan found no new issue.
  • Browser proof passed with delegated login at https://manage.klicker.kb-poc.localhost: attach, replacement warning, replace, disconnect, linked chatbot, no-KB warning, EN desktop, and DE mobile.
  • Independent contract, security, maintainability, branch-plan, and screenshot-evidence reviews are clean. All findings were fixed before push.
  • Data-hygiene review found no secrets, private keys, personal data, rosters, or production data.

Earlier branch verification:

  • W3 GitHub CI passed formatting, lint, syncpack, types, GraphQL, the Playwright build, all eight Playwright shards, and the fallback container build.

Failed/Warning:

  • The full local seed:raw command still stops in the pre-existing account seed with Prisma P2002 on (provider, providerAccountId). The changed MCP seed path is covered by the dedicated four-case suite and the real-database probe above.
  • GitGuardian incident 1509424 is an existing unrelated signal for intentional local test credentials. W4 adds no credential value.

Fresh GitHub CI for head 69c2a499a passed: formatting, lint, syncpack, types, GraphQL, build/compile, all eight Playwright shards and their status gate, and both fallback image builds are green. GitGuardian remains failed only on existing unrelated incident 1509424, as noted above.

Screenshots

W4 chatbot binding

English desktop binding

German mobile binding

English replacement warning

W4 chatbot status

English linked chatbot

English chatbot without a KB

W3 status and cutover

English desktop status history

German mobile replacement cutover

Security / privacy

  • Review: independent contract and security reviews covered ownership, tenant scope, token claims, foreign-thread rejection, secret placement, upstream error handling, and the database invariant.
  • Result: no open W4 finding.
  • Sensitive data: no secret, private key, participant identity, personal data, or production payload is committed or shown in the evidence.

Blocking before merge

  • Complete W5 through W9 on this integration line.
  • Pass the external retrieval gates in the roadmap, including R4.3 and R4.4.
  • Run the final full-branch, pilot, and current-head CI gates.
  • Keep the PR draft until the lecturer workflow reaches the agreed target.

Follow-up after merge

  • Roll out through the normal v3-ai path only after the external platform and pilot gates pass.

No merge is requested by this update.

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f78fc2cd-2863-4a17-b716-c9f9e6a51f90

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

@greptile-apps

greptile-apps Bot commented Jul 15, 2026

Copy link
Copy Markdown

Confidence Score: 4/5

Safe to continue W1 work on; none of the new issues are blocking, but three small gaps should be closed before lecturer staging exposure.

The core ingestion pipeline — SAS generation, two-phase upload confirmation, row-level locking, optimistic status claiming, and HMAC webhook verification — is well-implemented with race conditions explicitly handled. Three non-blocking gaps were found: the upload SAS is missing the 5-minute startsOn clock-skew backdate that the ingestion SAS already applies; the upload SAS allows HTTP where the ingestion SAS restricts to HTTPS; and the Hatchet task discards the undefined return from dispatchKBIngestion and always succeeds, so onFailure is never triggered in the dispatch-race path, leaving a narrow window where a QUEUED resource with no externalWorkflowRunId is skipped by the monitor.

Files Needing Attention: packages/graphql/src/services/knowledge.ts (upload SAS policy), packages/hatchet/src/index.ts (ignored dispatch return value), packages/util/src/publicUrl.ts (IPv6 blanket block)

Important Files Changed

Filename Overview
packages/graphql/src/services/knowledge.ts Core knowledge service: requestKbFileUpload generates a blob-scoped SAS without startsOn clock skew protection; confirmKbFileUpload handles idempotency well via upsert + P2002 recovery; row-lock pattern in deleteKb/deleteKbResource is deliberate.
packages/hatchet/src/kbIngestion.ts External Hatchet bridge with recovery, cancellation, and timeout logic; dispatchKBIngestion returns undefined in certain race conditions that the calling Hatchet task treats as success, skipping onFailure signaling; ingestion SAS correctly applies clock-skew startsOn backdate.
packages/hatchet/src/index.ts ingestKBResource task discards the undefined return from dispatchKBIngestion and always returns { success: true }; onFailure handler correctly sends FAILED webhook when all retries are exhausted.
packages/graphql/src/services/knowledgeWebhooks.ts HMAC-SHA256 webhook verification is timing-safe and correctly validates timestamp replay window; updateMany with ingestionAttemptId guard is idempotent by design; always returning 200 for valid-signature requests is intentional.
packages/util/src/publicUrl.ts SSRF guard covers RFC 1918, link-local, loopback, and special-use ranges; blanket IPv6 rejection prevents any IPv6 URL (including legitimate public hosts); does not perform DNS resolution (by design).
packages/prisma/src/prisma/schema/knowledge.prisma Defines KB and KBResource models with two-migration incremental rollout; missing @@unique([blobName]) guard (flagged in previous review) and no uniqueness constraint on externalWorkflowRunId.
packages/kb-management/src/components/KnowledgeBaseFileDropzone.tsx Two-phase upload (SAS request → Azure PUT → confirm) is correctly implemented; uses BlobServiceClient with account URL + blob-scoped SAS, which is a valid but non-standard pattern that works because the SDK propagates the SAS through container/blob navigation.
apps/backend-docker/src/app.ts Webhook endpoint correctly uses express.raw to capture raw body for HMAC verification before any JSON parsing; route is placed before the GraphQL handler and not gated by JWT middleware.

Sequence Diagram

sequenceDiagram
    participant U as Lecturer UI
    participant GQL as GraphQL API
    participant AZ as Azure Blob
    participant H as Hatchet Worker
    participant EH as External Hatchet
    participant WH as Webhook Handler

    Note over U,WH: Phase 1 — Upload
    U->>GQL: requestKbFileUpload(kbId, fileName, mimeType, size)
    GQL->>AZ: createIfNotExists(container)
    GQL-->>U: uploadSasURL, containerName, blobName
    U->>AZ: PUT blob (SAS-authenticated)
    U->>GQL: confirmKbFileUpload(kbId, blobName, title)
    GQL->>AZ: blobClient.exists() + getProperties()
    GQL->>GQL: upsert KBResource (ADDED)
    GQL-->>U: KBResource status ADDED

    Note over U,WH: Phase 2 — Ingestion
    U->>GQL: ingestKbResource(id, speedMode)
    GQL->>GQL: CAS updateMany status QUEUED
    GQL->>H: tasks.ingestKBResource.runNoWait(payload)
    GQL-->>U: KBResource status QUEUED

    H->>EH: client.runNoWait(workflowName, payload)
    H->>GQL: updateMany externalWorkflowRunId

    Note over H,WH: Phase 3 — Monitor + Webhook
    loop every monitor tick
        H->>EH: runs.get_status(runId)
        EH-->>H: RUNNING / COMPLETED / FAILED
        H->>WH: POST /api/webhooks/kb-ingestion (signed)
        WH->>WH: HMAC verify + timestamp check
        WH->>GQL: updateMany status where ingestionAttemptId matches
    end

    alt Hatchet task fails all retries
        H->>WH: onFailure POST status FAILED (signed)
        WH->>GQL: updateMany status FAILED
    end
Loading

Reviews (6): Last reviewed commit: "feat(kb): add management and ingestion P..." | Re-trigger Greptile

Comment thread project/2026-07-15-pr-5174-kb-poc-plan.md Outdated
Comment thread project/2026-07-15-pr-5174-kb-poc-plan.md
Comment thread project/2026-07-15-pr-5174-kb-poc-plan.md
Comment thread project/2026-07-15-pr-5174-kb-poc-plan.md
Comment thread project/2026-07-15-pr-5174-kb-poc-plan.md
Round 2 rulings: reusable package (not standalone app), no simulated
ingestion, workload-identity blob reads, base v3-ai, allow docx/pptx.
@gitguardian

gitguardian Bot commented Jul 15, 2026

Copy link
Copy Markdown

⚠️ GitGuardian has uncovered 2 secrets following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

🔎 Detected hardcoded secrets in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
1509424 Triggered Generic Password d898fad .devcontainer/docker-compose.yml View secret
1509424 Triggered Generic Password d898fad .devrouter.yml View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secrets safely. Learn here the best practices.
  3. Revoke and rotate these secrets.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

@rschlaefli
rschlaefli changed the base branch from v3 to v3-ai July 15, 2026 21:07
Comment on lines +34 to +49
sizeBytes Int
blobName String
blobHref String

status KBResourceStatus @default(UPLOADED)
statusMessage String?
ingestedAt DateTime?

kb KB @relation(fields: [kbId], references: [id], onDelete: Cascade, onUpdate: Cascade)
kbId String @db.Uuid

createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

@@index([kbId, status])
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Missing uniqueness guard on blobName

blobName is server-minted as <uuid>.<ext> in requestKbFileUpload, but neither the schema nor the planned confirmKbFileUpload service checks whether a KBResource row already exists for that blob before inserting. A client that retries confirmKbFileUpload after a network timeout (the blob is already present from the first call, so blobClient.exists() passes) will create a second row pointing to the same Azure blob. When either row is later deleted via deleteKbResource, the shared blob is removed from storage while the other row remains — producing a dangling KBResource whose ingestion or re-download will silently fail.

Adding @@unique([blobName]) to KBResource (UUIDs are globally unique, so the column is safe to uniquify without a composite key) gives a DB-level guarantee that the confirmKbFileUpload mutation is idempotent under retries, with no service-layer coordination needed.

Fix in Codex Fix in Claude Code

@jabbadizzleCode jabbadizzleCode left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

lgtm :)

jabbadizzleCode and others added 2 commits July 26, 2026 20:17
Merge W1 into the kb-poc integration branch. Knowledge-graph visualization and model selection remain parked in draft PR #5206 for W9.
@greptile-apps

greptile-apps Bot commented Jul 26, 2026

Copy link
Copy Markdown

Too many files changed for review. (151 files found, 100 file limit)

Bypass the limit by tagging @greptile-apps to review.

@rschlaefli rschlaefli changed the title feat(kb): knowledge base management POC with upload, ingestion trigger, and status webhooks feat(kb): add knowledge base management and canonical ingestion Jul 26, 2026
@rschlaefli rschlaefli changed the title feat(kb): add knowledge base management and canonical ingestion feat(kb): add production knowledge base ingestion Jul 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants