… 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>
Summary
Build-time AI translation (Option C — no runtime service/DB/spinner) + a shared
@fuzefront/i18nReact runtime. English is the only maintained language; a curated ~10-language set is AI-translated at build time via the in-cluster LiteLLM gateway, and a CI job opens a bot PR with the generated locale files. Git is the store.Packages
@fuzefront/i18n(packages/i18n) — frontend runtime<I18nProvider>(init i18next, load bundled locale JSON, restore saved language, wire direction manager),<LanguageSelector>,useT(),setLanguage(),useDir()+applyDocumentDirection/attachDirectionManager, and a typed language registry ({code,name,nativeName,dir}).<LanguageSelector>: styled entirely from "fuse seam" token CSS variables (no hard-coded color/spacing/type — there is a vitest assertion that enforces this), full label/htmlFora11y, native language names, and RTL via CSS logical properties so it mirrors automatically.<html dir/lang>. Documented that design-system components should use logical properties to mirror.publishConfig→ GitHub Packages,@fuzefrontscope,access: restricted,repositoryfield set. Added to lerna publish config.@fuzefront/i18n-translate(packages/i18n-translate) — build-time CLIbinCLI (fuzefront-i18n-translate): readslocales/en/*.json+i18n.languages.json, diffs vs existinglocales/<lng>/*.json, and translates only missing/changed keys via an OpenAI-compatible/chat/completionsendpoint (endpoint/key/model from env; LiteLLM gateway).{{interpolation}}tokens, enforces a do-not-translate glossary ("FuzeFront", "fuse seam", …), and uses a per-key source hash for idempotency (byte-identical re-runs, never re-spends on the LLM). LLM is mocked in all tests — zero live network.Source + config
locales/en/common.json(10 keys incl. an ICU pluralapps.countand an interpolationgreeting.welcome).i18n.languages.json: en, es, fr, de, pt, ru, zh, ja, hi (LTR) + ar, he (RTL).CI
.github/workflows/i18n-translate.yml: triggers only onlocales/en/**/i18n.languages.jsonchanges (+workflow_dispatch), runs the CLI against LiteLLM (endpoint/key/model as secrets), and opens a bot PR with regenerated locales. Never translates on every build. Passesactionlint.Verification (local)
@fuzefront/i18n:tsc --noEmitexit 0;vitest run src→ 18 passed (incl. he/ar RTL flip, a11y label association, hidden-label, and token-only styling assertions);rollup -cproduceddist/index.js+index.esm.js+index.d.ts.@fuzefront/i18n-translate: see PR checks / comments for the final counts.actionlinton the workflow → exit 0.Decisions / deferred (flagged per instructions)
workspacesnot modified. Following the established repo pattern (frontend, sdk, services are NOT in rootworkspacesto avoid theos=linux/native-binary install gotcha at rootnpm ci); these packages are wired through lerna for publishing instead. Per-package lockfiles and local.npmrcos-clear workarounds are git-ignored so the Linux CI install is unaffected.design-system/is not yet a resolvable@fuzefront/design-systemnpm package (no package.json/exports). The selector therefore consumes the design-system tokens directly and mirrors the DSSelectmarkup rather than importing the component. When the DS is published, swap the inner control for the imported<Select>. Also: the DSSelect.jsxuses physicalright/padding properties — it should adopt logical properties to mirror in RTL like this selector does.@fuzefront/i18nis the intended shared package; this PR does not touch the identity branch — convergence is a later step.Notes
release.yml(orchestrator-owned).🤖 Generated with Claude Code