feat(chat): chat-service (RAG) backend + @fuzefront/chat-client - #68
Merged
Conversation
…/Chat resources Unit 1 of the AI Chat (RAG) feature — deployment + authz foundation. A. Helm: add chat-service Deployment+Service template gated by chatService.enabled (default false). Port 3006 (3005 taken by provisioningService). Env includes LITELLM_URL, CHROMA_URL, BACKEND_URL, PERMIT_PDP_URL, KAFKA_BROKERS, DB_* and JWT_SECRET from chart Secret. Conditional ANTHROPIC_API_KEY / OPENAI_API_KEY / LITELLM_MASTER_KEY from Secret when set. Add chatService: block to values.yaml and three new empty-placeholder secret keys. B. Argo: add deploy/argocd/applications/litellm.yaml pointing at FuzeInfra/helm/litellm (companion FuzeInfra PR creates that chart). app-of-apps needs no change (directory sweep). No separate Argo app for chat-service (umbrella fuzefront chart handles it). C. Docs: docs/ai-chat/fuzeinfra-companion-spec.md — precise spec for the FuzeInfra companion PR: full LiteLLM Helm chart templates + model config, ChromaDB enablement (flip chromadb.enabled + template spec if missing), and fuzeinfra-ai-keys Secret spec. D. Permit: add Docs (action: read) and Chat (actions: stream, manage) resources to both backend/src and backend/security/src schema.ts files. Grant all three roles Docs:read and Chat:stream; restrict Chat:manage to admin only. Extend permit-schema.test.ts with 8 tests total (3 new role-grant assertions + updated resource list + idempotency paths). Helm: lint passes, template renders correctly (gated off by default, renders on enable). Tests: 8/8 permit-schema tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e viewer manage guard Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Add BILLING_LLM_USAGE to TOPICS const in shared/src/kafka/types.ts - Create billingLlmUsageSchemaV1 with uuid/int/datetime validators; no version in payload (lives on FuzeEvent envelope) - Export BillingLlmUsagePayloadV1 inferred type - Wire export through schemas/index.ts - Add 5-case describe block in email-service/tests/schemas.test.ts; update TOPICS count assertion Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rvice
Implements Unit 3 of the AI Chat RAG feature plan (tasks T1.1, T1.2, T1.3).
- New package packages/chat-client (@fuzefront/chat-client v1.0.0, MIT)
- src/types.ts: ChatStreamRequest, ChatStreamEvent union (7 variants), RagSource,
Conversation, ConversationMessage, ConversationWithMessages
- src/streaming.ts: parseSSEStream() generator — accepts ReadableStream<Uint8Array>
or AsyncIterable<string>; uses eventsource-parser v1.x; yields typed ChatStreamEvent;
stops at {type:'done'}; skips malformed JSON lines
- src/client.ts: ChatServiceClient class — streamChat (SSE), confirmTool, listConversations,
getConversation, submitFeedback; yields {type:'error'} on streamChat errors, throws on others
- src/index.ts: barrel re-export
- Registered in lerna.json packages array and root package.json workspaces array
- 22/22 tests pass (streaming.test.ts 8, client.test.ts 14); tsc --noEmit clean
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-limit, chat db migrations - New services/chat-service package (@fuzefront/chat-service 1.0.0, private:true) - Express app with GET /health (unauthenticated), graceful SIGTERM/SIGINT shutdown - config.ts reads all env vars set by Helm chat-service.yaml template; REDIS_URL falls back to fuzeinfra default (no Helm mismatch that requires template edits) - Stateless JWT auth middleware: jwt.verify -> req.userId + req.orgId; no DB lookup; no console.log noise (§10d) - Rate-limit middleware: express-rate-limit 7.x + rate-limit-redis 4.x; three factory fns (stream 20/min, confirm 60/min, global 100/min §10f); Redis injectable for tests; degrades to in-memory if Redis unavailable (lazyConnect, no startup crash) - DB knexfile mirrors backend/knexfile.ts; 001_create_chat_tables migration with exact SQL from plan §6e (4 tables: chat_conversations, chat_messages, chat_audit_log, chat_feedback); idempotent up/down - 4 test suites, 11 passing, 2 skipped (live-DB migration, no Postgres in env) - Dockerfile mirrors email-service (multi-stage node:18-alpine, user chatservice, port 3006) - services/chat-service added to lerna.json packages (not root workspaces — matches email-service pattern) - tsc --noEmit: clean Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…mitter
Implements the read-only RAG path + streaming chat backend on top of the
scaffold (Plan F / AI chat RAG):
- llm/litellm: OpenAI-compat LiteLLM client (chat completions + embeddings,
streaming SSE chunk parse); adopts the gateway, no provider SDK.
- rag/{chunker,embedder,chroma,indexer,retriever}: deterministic chunking,
ChromaDB REST client, content-hash idempotent indexer, top-k retriever.
- rag/index-docs: CLI entrypoint for the chat-doc-indexer Job.
- db/repositories/{conversations,messages,feedback}: scoped by JWT userId,
never request body (§10d).
- agent/prompt: injection-resistant system prompt, <doc>-wrapped context,
input sanitization (§10a/§10b).
- agent/{permit,confirmation,tools}: fail-closed PDP client, owner-scoped
confirmation state machine, read-only search_docs tool (mutating tools
deferred).
- agent/loop: retrieve -> rag_sources -> text_delta... -> done, usage report.
- billing/emitter: emits billing.llm.usage to Kafka, non-blocking on failure.
- routes/chat: POST /chat/stream (SSE), conversations, feedback,
confirm/:id; behind auth + per-route limiters; persists + bills.
- app/index: composition root wiring all of the above.
- helm: chat-doc-indexer Job template; chatService.resources limits +
docIndexer/embeddingModel values. Dockerfile copies docs corpus.
- shared/dist: regenerate kafka .d.ts (billing.llm.usage) + barrel export.
Wire format = chat-client's SSE event union (text_delta/rag_sources/done/
error), a deliberate deviation from plan §6f (AI-SDK data stream).
Verification: tsc --noEmit clean; jest 85 tests (83 pass, 2 skipped live-DB).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
# Conflicts: # lerna.json
#68 is now the chat-service backend + @fuzefront/chat-client only. The Helm templates (chat-service, doc-indexer), LiteLLM Argo app, and chat secret/values moved to the chat-devops PR (devops slice), which merges after this backend lands. Also merges origin/master so this branch no longer reverts the prod-CD work (observability, kafka-topics-job, node-request) it was behind on. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
izzywdev
marked this pull request as ready for review
June 22, 2026 11:43
izzywdev
added a commit
that referenced
this pull request
Jun 22, 2026
… app, chat secret/values (#84) Devops slice extracted from #68 (the chat feature was bundling deploy wiring). Owned/reviewed as the devops slice; merges after the chat-service backend (#68). Chart renders coherently (chat-service template + values + secret keys together). Co-authored-by: AppHub Developer <developer@apphub.dev> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
izzywdev
added a commit
that referenced
this pull request
Jun 22, 2026
… frontend (draft) (#81) * fix(e2e): repair Playwright sign-in flow (#71) * fix(e2e): seed provisioned personal org so the sign-in shell renders The authenticated shell renders behind WorkspaceProvisioningGate, which only mounts the app layout once the user has a personal org. The e2e seeds a bare admin user and relied on async login self-heal provisioning to create that org within the test window; it never appeared, so the gate stayed on the 'Creating your workspace…' card and the .app-layout/.top-bar/.main-content (and .app-grid-button) the specs assert never rendered. Seed the personal org + active owner membership directly (mirroring ensurePersonalOrg) so the gate opens immediately and the test is deterministic. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(api): stop 500 on GET /organizations from double-parsing jsonb settings/metadata are jsonb columns; the pg driver returns them already parsed as objects, so JSON.parse(org.settings) throws ('[object Object]' is not valid JSON) and the route 500s as soon as any org row is returned. That 500 also breaks WorkspaceProvisioningGate: its getOrganizations() poll rejects, the gate flips to its error state, and the authenticated shell never mounts. Add parseJsonColumn() that passes objects through and only JSON.parse()s strings (sqlite/json-column paths), falling back to {} on invalid input. Apply it to all four settings/metadata reads in the organizations routes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * style(e2e): use _ for unused seq loop var (actionlint SC2034 clean) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(api): GET /organizations hid all active orgs (boolean default vs string compare) The is_active query param defaults to the boolean `true` when not sent, but the filter compared it with `is_active === 'true'` — true === 'true' is false, so with no param the route filtered WHERE is_active = false and returned ZERO active orgs. The frontend WorkspaceProvisioningGate calls GET /organizations with no params, so it never saw the user's (active) personal org and stayed stuck on the 'Creating your workspace…' card — the actual reason the sign-in e2e never reached the app shell. Coerce both shapes: boolean true and string 'true' mean active. Verified against Postgres: old filter returns 0 rows / no personal org; new filter returns the personal org. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: AppHub Developer <developer@apphub.dev> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(prod-cd): app.fuzefront.com on Contabo k3s — overlay, app config, kafka topics, observability, CI gate (#69) * wip(prodcd): watchdog salvage checkpoint — unpushed agent work [skip ci] * feat(prod-cd): kafka topic pre-create Job (Phase D) Idempotent Helm post-install/post-upgrade hook Job that creates the identity/notify/billing prefixed topics with explicit partitions + retention, gated behind kafkaTopics.enabled (on in values-prod). Topic set reconciled from @fuzefront/shared TOPICS plus planned billing/chat events. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(prod-cd): observability + helm-validate + prod-smoke (Phases E, G, H part) - E: backend /metrics via prom-client (defensive require); prometheus.io scrape annotations on backend/security/applications pods; FuzeFront Grafana dashboard + Prometheus alert rules shipped as labeled ConfigMaps. - G: helm-validate.yml — helm lint + kubeconform (strict, k8s 1.29) of the chart vs values-local/prod on PRs touching deploy/helm/**. - H: prod-smoke.yml — poll app.fuzefront.com/api/health for 200 after a release: tag-bump. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(prod-cd): Contabo runbook + BUILDING_ON_FUZEFRONT guide (Phase H) - CONTABO_DEPLOYMENT.md → operational runbook: release flow, rollback via git revert of the tag-bump commit, prune:false data safety, 2nd-node join, sealed-secret rotation, kafka topics, observability. - BUILDING_ON_FUZEFRONT.md: downstream products on FuzeFront — Module-Federation app registration, @fuzefront/* packages, Authentik OIDC SSO, Permit scopes, the API, fuse-seam design language. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ci): add prom-client to lockfile so npm ci passes (#69 metrics dep) * fix(frontend): remove unused fireEvent import (TS6133) blocking Lint & Test * ci(claude): add @claude handler + companion auto-PR workflow (issue->PR autonomy) Mirrors FuzeInfra's claude.yml; claude-auto-pr opens a draft PR from pushed claude/** branches (claude-code-action only pushes+links, doesn't open PRs). Requires repo secret ANTHROPIC_API_KEY. --------- Co-authored-by: AppHub Developer <developer@apphub.dev> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ci): backend image build needs repo-root context (Dockerfile COPYs shared/ + backend/) (#73) Co-authored-by: AppHub Developer <developer@apphub.dev> * feat(billing-contract): OpenAPI 3.1 spec from real routes + spectral + generated client types [skip ci] * feat(infra): declarative node-request + dispatch-to-FuzeInfra reconcile loop (#76) * feat(infra): declarative node-request + dispatch-to-FuzeInfra loop FuzeFront declares infra needs (deploy/terraform node-request, references a FuzeInfra-owned contabo-k3s-node module) + Argo apps; CI path-watch fires a repository_dispatch to FuzeInfra (sole credential holder) to reconcile. FuzeFront holds no Contabo/cluster creds — only a scoped FUZEINFRA_DISPATCH_TOKEN. Decoupled IaC-as-a-service via git; gating = whitelist auto-apply on the FuzeInfra side. * fix(lint): remove unused react-hooks/exhaustive-deps disable in WorkspaceProvisioningGate (master lint was red) --------- Co-authored-by: AppHub Developer <developer@apphub.dev> * wip(billing-ui): scaffold @fuzefront/billing-ui package (tsup dual build, vitest, private publishConfig) [skip ci] * wip(billing-ui): i18n layer, status helpers, token-only stylesheet; add --scrim DS token [skip ci] * wip(billing-ui): primitives, accessible Modal, PlanCard, PlanPicker [skip ci] * wip(billing-ui): CheckoutModal (Stripe Payment Element), SubscriptionManager, UsagePanel, PaymentMethodPanel, barrel [skip ci] * wip(billing-ui): vitest unit + a11y + RTL tests (plans, checkout w/ mocked Stripe, subscription, panels, modal, status) [skip ci] * fix(billing-ui): named React event/type imports (no React namespace under jsx-runtime); ignore .npm-cache [skip ci] * test(billing-ui): scope plan-card assertions by region/selector (28→29 green) [skip ci] * build(billing-ui): wire @fuzefront/billing-ui into lerna publish pipeline + README * feat(agents): single-responsibility domain agents + contract-designer gate + honest-done contract (#82) * feat(agents): add single-responsibility domain agents + scope/done contract Adds five domain-scoped agent definitions (.claude/agents/) plus a README, each with an exclusive scope, explicit NOT-scope (named for the orchestrator), and a MANDATORY honest-"done" contract: SCOPE DONE (verified): <commands/results> OUT OF SCOPE — NOT DONE: <named unbuilt sibling layers> - backend-engineer — API/services/DB/migrations/events + own unit tests - frontend-engineer — design-system-first UI npm package vs the contract - test-engineer — INDEPENDENT acceptance/contract/e2e tests vs the spec - devops-engineer — Helm/Argo/CI/infra-request/sealed-secrets - docs-maintainer — consumer/integration docs only No agent ever declares the *feature* done/green — only its slice. A feature is complete only when every slice's PR is green and merged (orchestrator's call). Fixes the failure mode where one feature-agent reported DONE/GREEN while the UI and tests were unbuilt. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(agents): add contract-designer — the detailed-design phase before fan-out No prior agent owned *creating* the contract; backend/frontend/test all only consume it. contract-designer runs FIRST and alone: user story → frozen OpenAPI/Swagger + Kafka Zod event schemas + generated @fuzefront/<svc>-client, PR'd as the gate the parallel fan-out depends on. Designs the interface; does not implement behind it. README + sequence updated to make it the sequential gate before backend/frontend/test/devops/docs fan out. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(agents): equip each domain agent with its best-fit skills Wire the strongest available skills into each agent's How section: - contract-designer: + writing-plans, well-architected - backend-engineer: + test-driven-development, systematic-debugging, security-review, verification-before-completion - frontend-engineer: + a11y-debugging, web-perf, verification-before-completion - test-engineer: + test-driven-development, systematic-debugging, a11y-debugging, verification-before-completion - devops-engineer: + observability, well-architected, verification-before-completion - docs-maintainer: + writing-rules, verification-before-completion verification-before-completion is wired into every implementer so the honest-"done" report is backed by an actual verification pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: AppHub Developer <developer@apphub.dev> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * feat(fuzeone): family onboarding toolkit — "set me up as a FuzeOne member" (#83) * feat(fuzeone): toolkit skeleton — manifest, dependency-free sync.mjs, CLAUDE block, .npmrc, caller workflows [skip ci] WIP: reusable hub workflows + README + fuzefront-expert onboarding flow next. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(fuzeone): finish toolkit — real workflow bodies, generic helm-validate, README, shims - Caller workflows mirror the hub's actual workflows: claude/claude-auto-pr/auto-merge/ infra-dispatch self-contained; claude-ci-autofix + telegram call the izzywdev/AITools reusable workflows (the real hybrid — central fixes propagate). - helm-validate generalized to discover any chart under deploy/helm/. - Dropped deliverable-verify (no implementation exists yet). - README (FuzeOne layering + onboarding), cross-platform bin shims. - Verified: dry-run, conditional gating (has-helm/has-infra), var substitution, CLAUDE.md region merge, idempotent re-run, --check drift exit code. Depends on #82 (.claude/agents/*) merging — sync reads the canonical agents from the hub. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: AppHub Developer <developer@apphub.dev> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * feat(agents): frontend-engineer owns design-system; add frontend-test-engineer (#85) Fixes the cross-branch design-system duplication that stranded features (identity #65, i18n #72, billing #81 each independently re-edited design-system/ → merge conflicts → nothing converges). - frontend-engineer is now the SOLE owner of design-system/: derive components from the user story → add missing primitives to the DS FIRST (landed as a foundation; one PR when features run in parallel) → then build the feature UI. - All other agents: never edit design-system/ (consume only) — added to NOT-scope. - New frontend-test-engineer: INDEPENDENT UI verification via Playwright, pre-prod (ephemeral stack, gates merge) AND post-prod (smoke vs live app). Split out from test-engineer, which is now scoped to API/contract/integration/event tests. - README roles + sequence updated (DS foundation step; fe-test after fe-engineer). Co-authored-by: AppHub Developer <developer@apphub.dev> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * feat(chat): chat-service (RAG) backend + @fuzefront/chat-client (#68) * feat(chat): chat-service helm template, litellm argo app, permit Docs/Chat resources Unit 1 of the AI Chat (RAG) feature — deployment + authz foundation. A. Helm: add chat-service Deployment+Service template gated by chatService.enabled (default false). Port 3006 (3005 taken by provisioningService). Env includes LITELLM_URL, CHROMA_URL, BACKEND_URL, PERMIT_PDP_URL, KAFKA_BROKERS, DB_* and JWT_SECRET from chart Secret. Conditional ANTHROPIC_API_KEY / OPENAI_API_KEY / LITELLM_MASTER_KEY from Secret when set. Add chatService: block to values.yaml and three new empty-placeholder secret keys. B. Argo: add deploy/argocd/applications/litellm.yaml pointing at FuzeInfra/helm/litellm (companion FuzeInfra PR creates that chart). app-of-apps needs no change (directory sweep). No separate Argo app for chat-service (umbrella fuzefront chart handles it). C. Docs: docs/ai-chat/fuzeinfra-companion-spec.md — precise spec for the FuzeInfra companion PR: full LiteLLM Helm chart templates + model config, ChromaDB enablement (flip chromadb.enabled + template spec if missing), and fuzeinfra-ai-keys Secret spec. D. Permit: add Docs (action: read) and Chat (actions: stream, manage) resources to both backend/src and backend/security/src schema.ts files. Grant all three roles Docs:read and Chat:stream; restrict Chat:manage to admin only. Extend permit-schema.test.ts with 8 tests total (3 new role-grant assertions + updated resource list + idempotency paths). Helm: lint passes, template renders correctly (gated off by default, renders on enable). Tests: 8/8 permit-schema tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(chat): clarify FuzeInfra submodule bump in companion spec; restore viewer manage guard Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(shared): add billing.llm.usage kafka topic + zod schema - Add BILLING_LLM_USAGE to TOPICS const in shared/src/kafka/types.ts - Create billingLlmUsageSchemaV1 with uuid/int/datetime validators; no version in payload (lives on FuzeEvent envelope) - Export BillingLlmUsagePayloadV1 inferred type - Wire export through schemas/index.ts - Add 5-case describe block in email-service/tests/schemas.test.ts; update TOPICS count assertion Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(chat-client): @fuzefront/chat-client SSE+HTTP client for chat-service Implements Unit 3 of the AI Chat RAG feature plan (tasks T1.1, T1.2, T1.3). - New package packages/chat-client (@fuzefront/chat-client v1.0.0, MIT) - src/types.ts: ChatStreamRequest, ChatStreamEvent union (7 variants), RagSource, Conversation, ConversationMessage, ConversationWithMessages - src/streaming.ts: parseSSEStream() generator — accepts ReadableStream<Uint8Array> or AsyncIterable<string>; uses eventsource-parser v1.x; yields typed ChatStreamEvent; stops at {type:'done'}; skips malformed JSON lines - src/client.ts: ChatServiceClient class — streamChat (SSE), confirmTool, listConversations, getConversation, submitFeedback; yields {type:'error'} on streamChat errors, throws on others - src/index.ts: barrel re-export - Registered in lerna.json packages array and root package.json workspaces array - 22/22 tests pass (streaming.test.ts 8, client.test.ts 14); tsc --noEmit clean Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(chat-client): untrack built dist/ (CI/publish builds it) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(chat-service): scaffold service - config, health, jwt auth, rate-limit, chat db migrations - New services/chat-service package (@fuzefront/chat-service 1.0.0, private:true) - Express app with GET /health (unauthenticated), graceful SIGTERM/SIGINT shutdown - config.ts reads all env vars set by Helm chat-service.yaml template; REDIS_URL falls back to fuzeinfra default (no Helm mismatch that requires template edits) - Stateless JWT auth middleware: jwt.verify -> req.userId + req.orgId; no DB lookup; no console.log noise (§10d) - Rate-limit middleware: express-rate-limit 7.x + rate-limit-redis 4.x; three factory fns (stream 20/min, confirm 60/min, global 100/min §10f); Redis injectable for tests; degrades to in-memory if Redis unavailable (lazyConnect, no startup crash) - DB knexfile mirrors backend/knexfile.ts; 001_create_chat_tables migration with exact SQL from plan §6e (4 tables: chat_conversations, chat_messages, chat_audit_log, chat_feedback); idempotent up/down - 4 test suites, 11 passing, 2 skipped (live-DB migration, no Postgres in env) - Dockerfile mirrors email-service (multi-stage node:18-alpine, user chatservice, port 3006) - services/chat-service added to lerna.json packages (not root workspaces — matches email-service pattern) - tsc --noEmit: clean Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * wip(chat): watchdog salvage checkpoint — unpushed agent work [skip ci] * feat(chat-service): RAG retrieval, agent loop, chat routes, billing emitter Implements the read-only RAG path + streaming chat backend on top of the scaffold (Plan F / AI chat RAG): - llm/litellm: OpenAI-compat LiteLLM client (chat completions + embeddings, streaming SSE chunk parse); adopts the gateway, no provider SDK. - rag/{chunker,embedder,chroma,indexer,retriever}: deterministic chunking, ChromaDB REST client, content-hash idempotent indexer, top-k retriever. - rag/index-docs: CLI entrypoint for the chat-doc-indexer Job. - db/repositories/{conversations,messages,feedback}: scoped by JWT userId, never request body (§10d). - agent/prompt: injection-resistant system prompt, <doc>-wrapped context, input sanitization (§10a/§10b). - agent/{permit,confirmation,tools}: fail-closed PDP client, owner-scoped confirmation state machine, read-only search_docs tool (mutating tools deferred). - agent/loop: retrieve -> rag_sources -> text_delta... -> done, usage report. - billing/emitter: emits billing.llm.usage to Kafka, non-blocking on failure. - routes/chat: POST /chat/stream (SSE), conversations, feedback, confirm/:id; behind auth + per-route limiters; persists + bills. - app/index: composition root wiring all of the above. - helm: chat-doc-indexer Job template; chatService.resources limits + docIndexer/embeddingModel values. Dockerfile copies docs corpus. - shared/dist: regenerate kafka .d.ts (billing.llm.usage) + barrel export. Wire format = chat-client's SSE event union (text_delta/rag_sources/done/ error), a deliberate deviation from plan §6f (AI-SDK data stream). Verification: tsc --noEmit clean; jest 85 tests (83 pass, 2 skipped live-DB). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(chat): extract deploy wiring to chat-devops slice #68 is now the chat-service backend + @fuzefront/chat-client only. The Helm templates (chat-service, doc-indexer), LiteLLM Argo app, and chat secret/values moved to the chat-devops PR (devops slice), which merges after this backend lands. Also merges origin/master so this branch no longer reverts the prod-CD work (observability, kafka-topics-job, node-request) it was behind on. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: AppHub Developer <developer@apphub.dev> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(deploy): chat-service + doc-indexer Helm templates, LiteLLM Argo app, chat secret/values (#84) Devops slice extracted from #68 (the chat feature was bundling deploy wiring). Owned/reviewed as the devops slice; merges after the chat-service backend (#68). Chart renders coherently (chat-service template + values + secret keys together). Co-authored-by: AppHub Developer <developer@apphub.dev> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * ci: enforce in-repo packages resolve from source (prevent PR #65 404 class) (#86) Adds scripts/check-workspace-deps.mjs + a Workspace deps CI gate that fails when a consumer references an in-repo package by a registry spec (e.g. "^0.1.0") without it resolving as a local workspace — exactly the PR #65 break where frontend listed "@fuzefront/identity-ui": "^0.1.0" for an unbuilt in-repo package and `npm ci` 404'd against the registry. The check is dependency-free (no install), passes on master, and fails the #65-class violation with an actionable fix message. Also exposed as `npm run check:workspace-deps`. Pairs with fuzefront-ui-package skill rule #7. Co-authored-by: AppHub Developer <developer@apphub.dev> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * WIP: identity-management UI + API tokens (@fuzefront/identity-ui) (#65) * feat(security): add organization members CRUD endpoints Implements GET/POST/PUT/DELETE for /api/organizations/:id/members in the security service. GET returns a bare member array with nested user objects (firstName/lastName camelCase) to match the existing frontend contract in MembersManagement and OrganizationPage. POST creates a pending invitation row (same path as /:id/invitations). PUT/DELETE guard owner memberships with 403. Permit role assignment is non-blocking on all mutating routes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(security): filter members list to active + assert user.id in test Add .where('organization_memberships.status', 'active') to the GET /:id/members list query so only active members are returned. Add assertion on member.user.id in the GET happy-path test. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(security): add api_tokens migration (010) Creates the api_tokens table with SHA-256-hashed opaque tokens, polymorphic owner_id (no FK), created_by FK with ON DELETE SET NULL, scopes jsonb, and expiry/revocation timestamps. Enum creation guarded by DO $$ ... EXCEPTION block. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(security): implement API token service with TDD Add backend/security/src/services/api-token.ts with full token lifecycle: generateToken (ff_live_ format, base62 prefix, base64url body), hashToken, extractParts, createToken, verifyToken (timingSafeEqual, VerifyResult discriminated union), revokeToken, listTokensForOwner, getTokenById, updateLastUsed, and mapScopesToPermitRole (minimal-role algorithm from permitSchema). 48 unit tests cover all pure functions and mocked-DB operations including security invariants. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(security): parse api-token scopes on read; tighten ApiTokenRow type; add base62 test - Add parseScopes() helper (handles pg string or already-array mock) and call it on all four read paths: createToken return, verifyToken valid branch, listTokensForOwner map, getTokenById return — so callers always receive scopes as string[] not a JSON string. - Split ApiTokenRow into internal ApiTokenDbRow (includes token_hash) and exported ApiTokenRow = Omit<ApiTokenDbRow,'token_hash'|'scopes'> & { scopes: string[] } so callers cannot believe token_hash is present. - Export encodeBase62 and add describe('encodeBase62') with 6 tests including pinned known-vector (0xdeadbeef -> '44pZgF') to catch silent algorithm regressions. - Add scopes round-trip tests for createToken and verifyToken with mock DB returning scopes as a JSON string (real pg behaviour). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(security): API-token auth middleware + req.apiToken typing + rate limiting - Add authenticateFlexible middleware that branches on ff_live_ bearer tokens vs JWTs (JWT path delegates to core's authenticateToken unchanged) - PAT path: loads user row from DB, builds same User shape as core JWT middleware - Service-token path: synthetic svc_token:<id> principal with roles ['service'] - Add tokenAuthRateLimiter (express-rate-limit 7.2.0, skipSuccessfulRequests:true, 10 failed attempts per IP per 60s → 429) - Extend Express.Request with apiToken?: { id, scopes, ownerType, ownerId } - 11 tests covering all paths incl. rate-limit 11th-request 429 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(security): API token routes, Permit sync helpers, scope enforcement - Add syncServiceTokenToPermit / removeServiceTokenFromPermit to user-sync.ts - Create routes/api-tokens.ts: POST/GET/DELETE /api/tokens, GET /:orgId/tokens via orgTokensRouter, and the requireTokenScope middleware export - Mount /api/tokens + /api/organizations (org-tokens sub-route) in index.ts with tokenAuthRateLimiter - 28 tests covering all brief cases (create, 403, 400, ownership, org-admin, list, revoke, Permit-sync, requireTokenScope) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(security): deterministic token-route test + mount order + surface permit-sync false I-1: Replace setTimeout(20) timing hacks in api-tokens.routes.test.ts with deterministic microtask flushes (triple Promise.resolve()) for both the org-token create and org-token revoke fire-and-forget assertions. I-2: In index.ts, mount orgTokensRouter BEFORE organizationsRoutes so the specific /:orgId/tokens path cannot be shadowed by future wildcards. M-2: Change syncServiceTokenToPermit and removeServiceTokenFromPermit fire-and-forget calls from .catch-only to .then(ok=>warn-if-false).catch so false-return failures are surfaced in logs, not silently dropped. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(design-system): add Modal, DataTable, Textarea, FileDropZone + tokens - New tokens: --modal-max-w (560px), --modal-max-w-lg (720px) in spacing.css; --drop-active (rgba indigo 0.18) in colors.css (both themes) - Modal: accessible dialog shell in new overlay/ category — focus trap, Escape close, backdrop close, fuse-seam top bar, role/aria-modal/labelledby - DataTable: semantic table shell in new data/ category — headless-friendly (consumer renders <tbody>); sort carets + aria-sort; 5-row skeleton with --bg-quaternary pulse; emptyState slot - Textarea: mirrors Input.jsx exactly but renders <textarea> with vertical resize - FileDropZone: drag-drop target in forms/; keyboard-activatable (Enter/Space); --drop-active dragover fill; visible --accent-soft focus ring - Regenerated _ds_manifest.json and index.js: 18→22 components, 144→147 tokens Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * wip checkpoint: @fuzefront/design-system package + identity-ui scaffold start; SDD ledger [skip ci] * wip(identity): watchdog salvage — token/members/invite UI components (unverified) [skip ci] * feat(frontend): mount IdentityPage in OrganizationPage members tab + MF shared scope Wire @fuzefront/identity-ui into the host shell: replace MembersManagement with <IdentityPage>, add the package to frontend deps and Module Federation shared scope. Frontend build/type-check verified in CI (Windows-local Vite build is the documented os=linux gotcha). [skip ci] * fix(identity-ui): regenerate lockfile for new workspaces + tsc jest-dom types + CI coverage - package-lock.json was stale: it predated the `packages/identity-ui` and `design-system` workspace members being added to the root `workspaces`, so root `npm ci` failed ("Missing ... from lock file"). Regenerated cross-platform (lockfileVersion 3) so it includes both new workspaces, their deps (vitest, @tanstack/react-table, react-hook-form, papaparse, zod) and both linux-x64 and win32-x64 native binaries — `npm ci` now works on Linux CI and Windows. - identity-ui test setup: import `@testing-library/jest-dom/vitest` (not the bare entrypoint) so jest-dom augments vitest's `Assertion` interface — `tsc --noEmit` (the `type-check` script, which includes `src/**/*.test.tsx`) now recognises `toBeInTheDocument`. - ci.yml: add an `identity-ui-and-security` job (Linux) that runs the @fuzefront/identity-ui type-check + vitest + library build (asserting es/cjs/d.ts artifacts), plus the security-service API-token jest suite (DB-mocked, no Postgres). This is the canonical clean-Linux verification for PR #65. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(identity-ui,design-system): label/control association + EmptyState title + member test fixture [skip ci] * fix(identity-ui): assert exact 'Name is required' validation, not ambiguous /required|name/ The /required|name/i query matched both the 'Token name' label and the error, failing the empty-name unit test with 'found multiple elements'. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(frontend): resolve @fuzefront/identity-ui + design-system from source [skip ci] Remove unpublished @fuzefront/identity-ui from frontend deps (was 404ing npm ci); alias both @fuzefront UI packages to source in vite/vitest/tsconfig. Keep identity-ui in Module-Federation shared list. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(identity-ui): remove unused imports; consume built d.ts in frontend type-check Two real type errors remained after the source-resolution fix: 1. TS6133 unused React imports (5 files) + unused IconButton (TokenList) — noUnusedLocals + react-jsx automatic runtime. Removed. 2. TS2322 csstype CSSProperties clash in TokenList: the frontend's tsc was compiling identity-ui SOURCE, so identity-ui's React types (root @types/react) clashed with the frontend's own @types/react/csstype copy (frontend is not a root workspace, so it gets its own). Fix (2) mirrors @fuzefront/design-system: frontend/tsconfig.json now resolves @fuzefront/identity-ui to its built dist/index.d.ts instead of src, so tsc consumes validated types (skipLibCheck) rather than recompiling source under a duplicate csstype. vite/vitest still resolve from source via their aliases for bundling and tests. CI builds identity-ui before the frontend type-check. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(build): declare socket.io-client in shared; drop @fuzefront UI from federation shared - @fuzefront/shared/src/hooks/useSocketBus.ts imports socket.io-client but it was never declared (latent since initial commit; #65 CI now builds shared and TS2307'd). Add socket.io-client ^4.7.5 (matches backend socket.io server) + regen lockfile. - frontend vite federation `shared` listed @fuzefront/identity-ui + design-system, which are aliased to source FILES — the plugin read `<file>/package.json` → ENOTDIR and failed `vite build`. They are host-bundled; only react/react-dom stay shared. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: AppHub Developer <developer@apphub.dev> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(billing-ui): regenerate lockfiles on linux (avoid win32 EBADPLATFORM on CI) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(billing-ui): register billing-client + packages/billing-ui in root workspaces The agent's workspace registration was left unstaged; the merge commit omitted it, so CI's workspace-deps gate saw @fuzefront/billing-client (a peerDep ^1.0.0) as an unregistered in-repo package. Register both so it resolves from source. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(billing): renumber backend/security migration 010->011 (collision with identity 010_api_tokens) #65 (on master) added backend/security migration 010_create_api_tokens_table; billing's 010_add_billing_to_entities collided. Renumbered to 011 to restore a unique ordering. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: AppHub Developer <developer@apphub.dev> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
izzywdev
added a commit
that referenced
this pull request
Jun 30, 2026
…e values (#120) (#127) Contract-first keystone for the AI-chat extraction (#120), building on the existing #68/#79 prototype rather than reinventing it: - services/chat-service/openapi.yaml: frozen OpenAPI 3.1 contract derived from the real route handlers (src/app.ts, src/routes/chat.ts) — the single source of truth for @fuzefront/chat-client. SSE event union is AG-UI-compatible; continuous-thread + paginated history are marked x-status: planned. - deploy/helm/fuzefront/values.yaml: fix three duplicate top-level `chatService:` keys (merge artifact). YAML last-key-wins left the sparse copy (port 3007, no litellmUrl/chromaUrl/backendUrl/permitPdpUrl/docIndexer) winning, so `helm template` nil-pointers on chatService.docIndexer.enabled once enabled and renders the Deployment with the wrong port + empty upstreams. Consolidated to a single complete block (port 3006, all upstreams, docIndexer). - services/chat-service/EXTRACTION.md: architecture + RAG ingestion path + contract-first handoff of remaining streams (continuous-thread/pagination, AG-UI rendering, per-org RAG collections, deploy wiring) with owners. Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Israel Weinberg <izzywdev@users.noreply.github.com>
Merged
18 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The backend slice of AI chat. Rebased onto master (no longer reverts the prod-CD/observability/kafka-topics/node-request work it was behind on).
Scope (backend only)
services/chat-service/**— RAG retrieval, agent loop, chat routes, billing emitter, migrations, Dockerfile, unit tests.@fuzefront/chat-client(packages/chat-client) — SSE+HTTP client.shared/—billing.llm.usageKafka topic + Zod schema.Companion slices (fan-out of the original bundled PR)
@fuzefront/chat-ui).chat-devopsPR (Helm/Argo/secret/values) — merges after this.The deploy wiring was extracted out of this PR so a backend slice doesn't carry Helm/Argo.