Jac-Scale Release Notes#
This document provides a summary of new features, improvements, and bug fixes in each version of Jac-Scale. For details on changes that might require updates to your existing code, please refer to the Breaking Changes page.
jac-scale 0.2.30 (Latest Release)#
New Features#
- Feature:
--no-image --experimentalfor the microservice fleet: with--experimental, the no-image bootstrap git-clones[plugins.scale.kubernetes].jaseci_repo_url@jaseci_branchinto a venv on the shared app volume and editable-installs jac + jac-scale[all] + jac-client, instead of pinning releases from PyPI - so an unreleased jac (e.g. a feature branch) can run in-pod. The repo/branch/commit are passed to the init container as env data rather than spliced into the bootstrap shell (injection-safe), git is installed via whichever package manager the base image ships (Debianaptor Alpineapk, only when missing), andjac-clientis installed only when the fork actually ships it. - Feature: no-image serves the fullstack frontend:
--no-imagenow supports fullstack apps, not just backend + admin.pack_sourceships a[plugins.scale]-stripped copy of jac.toml (so the pod gets the app's[dependencies.npm]/[plugins.client]build config without the deploy secrets, which a world-readable ConfigMap must not carry), and the gateway builds the client bundle in-pod (jac build <client.entry>) before serving, so the SPA is served at/. Non-fatal: the API + admin still come up if the client build fails. - Feature: injectable microservices config (platform deploys):
KubernetesMicroserviceTargetandMicroserviceManifestBuildernow accept amicroservices_config(the app's[plugins.scale.microservices]table). When set, routes / client entry / triggers / tracing are read from it instead ofget_scale_config()(the deploying process's own jac.toml), so a code-sync platform (jacBuilder/jachammer) can deploy a different app's fleet without mutating the global config singleton.
Bug Fixes#
- Fix:
--no-imagepods get the scale plugin's runtime deps, a startup probe, and a real jac.toml: the in-pod bootstrap installed barejac-scale(so thejac-scale:scaleplugin failed to load - gateway crashed, services 404'd on/healthz/*); it now installsjac-scale[all]+requests(a runtime dep jac-scale ships only in itstestextra) +jac-clientwhen present. No-image pods also get a startup probe (the multi-minute in-pod install/compile would be liveness-SIGKILLed otherwise) and a sanitized project jac.toml (the real one is excluded as a secret, butjac start <svc>.jacrequires a project jac.toml to exist). - Fix: gateway streams
/function/*and/walker/*passthrough: the gateway's builtin passthrough buffered the whole upstream body viaraw_forward, so a server-sent-events response from a client-served generator arrived all at once instead of frame-by-frame; it now usesstream_forward(status known from the response head, so the 404/405 "try the next service" fan-out still works) and streams SSE through live. - Fix: no-image pods default to build-sized memory: the in-pod jac install + compile (and the gateway's client build) OOM-kills at the 128Mi/1Gi Burstable defaults, so no-image pods now default to 4Gi/1Gi (services) and 8Gi/2Gi (gateway). Override via
[plugins.scale.kubernetes]or per-service config as before; built-image pods keep the small defaults. - Fix: microservice deploy waits for the fleet to roll out:
KubernetesMicroserviceTarget.deploy()returnedsuccess=Trueright after applying manifests (unlike the monolith target, which waits via_wait_for_deployment), so a caller would surface a "live" link at a not-yet-ready or crash-looping fleet. It now blocks until every fleet Deployment has a ready replica, and raises on a crash-looping pod so a broken fleet fails fast instead of timing out. - Fix: microservice deploy honors the deploy-wide
shared_ingress: the gateway Ingress was built only from[plugins.scale.microservices.ingress], so a platform that routes a subdomain through jac-scale'sshared_ingress(host=domain, e.g. an AWS ALB/ACM or a cert-manager nginx PaaS) got no Ingress at all and the public URL 404'd at the load balancer. The gateway Ingress now falls back toconfig.shared_ingress(host, class, caller annotations, plus a cert-managerspec.tlsblock when the issuer annotation is present), matching the monolith target. - Fix: no-image gateway auto-builds the client for a fullstack app: the in-pod client build only ran when
[plugins.scale.microservices.client].entrywas set, so a fullstack project deployed without that key served the API but 404'd at/. The gateway now defaults the client entry tomain.jacwhenever the project ships a.cl.jacclient, so the SPA is built and served without extra config.
jac-scale 0.2.29#
New Features#
- Feature: In-admin Pod Environment page: A new Operations -> Pod Env admin page shows the gateway pod's own process environment plus each pod's configured spec env, allowlist-redacted server-side: only known-safe keys (OTEL_/JAC_/K8S_/POD_/KUBERNETES_/TRACING_ + a few backend URLs) show their value, everything else is masked, and
secretKeyRefvalues are never resolved (rendered(from secret)), so no secret material can leave the cluster regardless of the allowlist. Allowlist (not denylist) by design - a key that wasn't anticipated is masked, not leaked. Reading other pods' env requires the opt-in read-only namespace RBAC ([plugins.scale.kubernetes].ops_console = true); this pod's own env always shows. - MongoDB optimistic-concurrency for the duplicate-node race (#6266):
MongoBackend._put_node_atomicapplies a read-gated version compare-and-swap, so concurrent find-or-create across pods converges on a single child instead of duplicating, while blind edge appends keep #5644's lock-free merge. Documents written before this change (nodata.version) are matched correctly on first write, so existing deployments upgrade cleanly. Because MongoDB has no cross-document rollback,MongoBackend.apply()runs a version precheck before staging any write, so a lost race is detected before the loser's child/edge documents are written -- no orphan in the common case. The narrow residual (a winner committing during the loser'sapply()) leaves a half-linked edge thatMongoBackend.fscknow sweeps along with the node it strands. -
Feature: Zero-config microservice mode (local and
--scale), with an optional no-image deploy: An app that splits itself withsv import from <module>/to sv:now "just works" with no[plugins.scale.microservices]config.jac start main.jacauto-detects the services (by parsing the entry file's sibling modules with the real Jac parser and inspecting their AST, sosv importtext in a docstring is never miscounted and both theto sv:section andsv { ... }block forms are recognized), gives each a default/api/<name>route, runs them as local subprocesses behind the gateway on:8000, and prints what it detected.jac start main.jac --scaledoes the same for Kubernetes: it auto-detects services, swaps to the microservice target, and propagates the discovered routes to every pod via aJAC_SV_ROUTESenv var (so the gateway andget_sv_registryresolve them even though the in-clusterjac.tomlhas no routes table). Theenabledflag is now tri-state -trueopts in explicitly,falseopts out (stays a monolith), and leaving it unset triggers auto-detection - andjac scale status/stop/restart/logs/destroywork in auto-detected mode too. Apps with nosv importare unaffected and run exactly as before. -
Feature: zero-config
--scaleprefers a real image, with no-image as a last resort: when no image or registry is configured (no[plugins.scale.kubernetes].docker_image_name/image_registryand no--build),jac start --scalenow picks the deploy strategy automatically instead of always copying source. If Docker is available and the target is a local dev cluster (kind / k3d / minikube), it builds a real image and loads it into the cluster with no external registry (kind load/k3d image import/minikube docker-env) - the production-grade path, for free. It falls back to the no-image deploy (tar+gzip the local source into a ConfigMap with a ~1 MiB guard, a bootstrap initContainer extracts it andpip installs the pinnedjaclang+jac-scalereleases into a shared volume, each service running on the genericpython:3.12-slimbase) only as a genuine last resort: when Docker is unavailable, or the cluster is remote with no registry. The chosen strategy and the reason are printed. Configuring an image/registry (or passing--build) keeps the normal image pipeline;--no-imageforces the copy-source path regardless. MongoDB/Redis are still provisioned so services share state. Cluster-side image builds (buildkit / buildah) are a tracked follow-up.
Bug Fixes#
- Fix: Undeclared query parameters no longer crash endpoints:
@restspecfunction and walker endpoints now ignore query parameters that are not in their signature, so extra params like browser cache-busting tokens or proxy tracking params return a normal response instead of a 500. - Fix: Deterministic out-edge order on the mongo backend: The scale backend now persists a node's out-edges in connection (insertion) order, matching the in-memory and SQLite backends. The atomic edge-merge previously used MongoDB set operations (
$setUnion/$setDifference), which sort and dedup, so reloaded edges came back in BSON sort order and scrambled ordered/nested OSP structures (ASTs, DOM/JSX trees, grammars).
jac-scale 0.2.28#
New Features#
- Feature: KEDA autoscaler engine for event-driven and scale-to-zero workloads: Adds a second autoscaler engine,
keda, alongside the existinghpaengine. Setautoscaler_engine = "keda"in[plugins.scale.kubernetes]to switch; existingjac.tomlfiles need no changes (default remains"hpa"). The KEDA engine createsScaledObjectCRs instead of KubernetesHorizontalPodAutoscalerobjects and supports the full KEDA trigger catalogue. Key capabilities added: scale-to-zero viaidle_replicas = 0; tunable timing viaautoscaler_polling_interval,autoscaler_cooldown, andautoscaler_initial_cooldown; global extra triggers via[[plugins.scale.kubernetes.extra_triggers]](any KEDA trigger type, e.g. Prometheus, Redis, RabbitMQ); per-service triggers in microservice mode via[[plugins.scale.microservices.services.<name>.triggers]]; and authenticated triggers via[...triggers.auth.secret_refs]which creates aTriggerAuthenticationCR backed by a Kubernetes Secret before theScaledObjectis applied. Apreflight()check runs before the first cluster write per deploy: if KEDA CRDs are absent it emits an install link pointing to the official KEDA docs (https://keda.sh/docs/latest/deploy/) and continues with static replicas rather than crashing. Switching between engines is safe: each engine removes the other engine's resource (ScaledObjectorHPA) onapply()anddestroy_collection()so two autoscalers never compete forspec.replicason the same Deployment. The engine-switch cleanup usesapp_name(a newAutoscalerSpecfield set to the base service name) rather than deriving the competing resource name fromscale_target_name, which carried a-deploymentsuffix in microservice mode and caused the delete to silently 404 leaving the old resource alive. The HPA code path is unchanged; all existing deployments keep working without modification. - Feature: In-admin Deploy Health page: A new Operations -> Deploy Health admin page reads the Kubernetes API (namespace-scoped, read-only) to show, kubeconfig-free, every deployment's ready/desired replicas + rollout status (complete / progressing / stuck) and every pod's phase, ready containers, restart count, node, and per-container spec-image-vs-resolved-imageID. Requires the opt-in read-only namespace RBAC (
[plugins.scale.kubernetes].ops_console = true); degrades to a clean "unavailable" off-cluster, never a 500. Pure reshape helpers are unit-tested against mocked API payloads. Registered on both the monolith server and the microservice gateway. - Feature: In-admin Endpoints & Storage page: A new Operations -> Endpoints & Storage admin page shows, namespace-scoped and kubeconfig-free, each service with its ready backing-endpoint count (0 ready = the classic "no endpoints -> 503", highlighted), the ingress rules resolved to host -> path -> service:port plus the load-balancer address actually serving traffic, and every PVC's bind phase / capacity / storage-class / claimant pods. Requires the opt-in read-only namespace RBAC (
[plugins.scale.kubernetes].ops_console = true); degrades to a clean "unavailable" off-cluster. Pure reshape helpers unit-tested.
jac-scale 0.2.27#
New Features#
- Feature: admin Workloads + Usage views backed by Prometheus: Adds a "Deployment" section to the admin portal with two tabs. Workloads lists every Deployment, StatefulSet, DaemonSet and Pod in the app's namespace with a traffic-light status (Healthy / Starting / Unstable / Broken), ready/desired replicas, live CPU and memory, restart count and age; a chip filter narrows by kind. Usage renders per-workload CPU and memory history charts with a 1h/6h/24h/7d time-range selector. Top-of-page cards summarize running/pending/failed pods, ready nodes, request rate, error rate and p95 latency. All data comes from the in-cluster Prometheus the monitoring stack already provisions (kube-state-metrics + node-exporter + cAdvisor); a new
utilities/metrics/prometheus_client.jacwraps the Prometheus query API and resolves the service URL fromK8S_APP_NAME(injected onto the app pod, withPROMETHEUS_URLoverride for local dev). When Prometheus is unreachable the endpoints degrade tometrics_available: falseso the UI shows a banner instead of erroring. - Fix: in-cluster Prometheus now actually scrapes app + container metrics: The app scrape job targeted the LoadBalancer port (80) on AWS instead of the container port and sent no credentials to the admin-protected
/metrics, so HTTP request/latency metrics were never collected. It now targets the container port and authenticates via Basic Auth (admin user +prometheus_admin_password), a cAdvisor scrape job (with the required RBAC + token) is added for per-pod CPU/memory, and the NetworkPolicy lets the app pod query Prometheus. - Feature: Gateway-runtime Kubernetes client + opt-in namespace RBAC: Adds a gateway-runtime Kubernetes API client helper (
admin/k8s_ops.jac) - in-cluster config loaded once, namespace resolved from the pod's ServiceAccount, and every accessor degrades to a clean "unavailable" (never a 500) when thekubernetesclient is absent or the process isn't running inside a cluster. Pairs it with an opt-in namespace-scoped read-onlyRole+RoleBindingbound to the gateway ServiceAccount (targets/kubernetes/microservice/ops_rbac.jac), provisioned by the microservice target only when[plugins.scale.kubernetes].ops_console = true; additional mutation verbs (patch/delete/scale) are appended only whenops_console_mutations = true. There is no ClusterRole, so each app's gateway can read only its own namespace, preserving per-app-namespace multi-tenancy. Both flags are off by default. - Feature: Crash-safe MongoDB persistence: MongoDB writes and deletes now flush through the shared unit-of-work contract in dependency order, so a process killed mid-request can no longer leave dangling references in the graph.
- Feature: MongoDB referential-integrity surface (#6619):
MongoBackendnow implements the read-path healing and integrity contract:quarantine_danglingfiles a dangling reference under theDANGLING_REFreason code,is_quarantineddistinguishes a permanent dangler from a recoverable schema-drift quarantine, andfsckscans the collection for dangling references and orphans (collecting them on repair).ScaleTieredMemoryconsults the newget_persistent_memoryhook before falling back to MongoDB / SQLite, so a third-party DB backend composes with the jac-scale stack.
Bug Fixes#
- Empty dictionary fields now persist reliably on every MongoDB version. Archetype
dictfields that are empty, or cleared back to{}, are now saved correctly instead of being silently dropped. This previously failed on MongoDB older than 6.1.0 (includingmongo:6.0, the image jac-scale provisions), which rejects empty embedded objects on the atomic write path; the empty value is now stored explicitly so a deliberate clear-to-{}persists identically on all versions, with no server-version detection or data migration.
jac-scale 0.2.26#
New Features#
- Cross-pod cache consistency: in multi-pod deployments, a write on one pod now evicts the affected node from sibling pods' in-memory caches over Redis, so they no longer serve stale graph reads.
- Lazy read-repair and quarantine auto-retry in the Mongo backend: Documents whose archetypes declare
__jac_schema__drift rules are repaired on load and the upgraded form is written back with compare-and-set on the stored fingerprint, so concurrent writers on older app versions safely win and the document repairs again on its next read. Quarantined documents are now stamped with a machine-readablereason_code(CLASS_MISSING,FIELD_RECONSTRUCT,DESER_ERROR,CASCADE), and at backend startup a capped auto-retry re-attempts the quarantined docs the new deploy plausibly fixed (newly registered classes, aliases, or drift rules) ("deploy the fix and the data heals itself"), withjac db recover-allremaining as the manual override. - Feature: In-admin "Signal self-check" health card: A new Operations -> Self-Check page in the jac-scale admin dashboard answers "why is observability/health broken?" without a kubeconfig.
GET /admin/ops/healthreturns, per signal (traces, logs, metrics, mongo, redis, admin API), a{status, chain[]}causal chain where each step is{name, ok, detail}, evaluated short-circuit so the first red link IS the diagnosis - e.g. "jac-scale[tracing] importable (HAS_OTEL): no" pinpoints a missing tracing extra that makes the Traces page returncount:0, and "Mongo ping reachable: failed" explains an/admin/loginreturning SPA HTML instead of JSON. Every check is an in-process read (HAS_OTEL, config,self.server) or a best-effort ~2s backend probe wrapped in try/except, so a dead Tempo/Loki/Prometheus/Mongo/Redis renders a red row, never a 500. The page renders each chain as a vertical green-check / red-x stepper with the first failing step highlighted. Registered on both the monolith server and the microservice gateway. No Kubernetes API access and no RBAC.
Bug Fixes#
- Fix: No duplicate startup banner in dev mode: The jac-scale server no longer prints its own banner (which advertised the API port as the URL to open) when a dev client is running; the main banner shows the correct URLs.
jac-scale 0.2.25#
Breaking Changes#
- jac-scale: webhook security hardening: Webhook auth and signing were reworked. The HMAC signature is now computed over
"<timestamp>." + bodyusing an independent per-keysigning_secretreturned once from/api-key/create(no longer the API key itself), and anX-Webhook-Timestampheader is required with replay-tolerance enforcement. API keys are now keyed by stableuser_id, support an optionalallowed_walkersscope, fail closed on unknown/revoked keys, and api_key tokens can no longer be used as user sessions. Adds request body size limits, an optional per-key rate limit, generic error responses (no traceback leakage), and per-request audit logging. Existing webhook integrations must update to the new signing scheme.
New Features#
- Feature: distributed tracing now covers the memory hierarchy. Each request's trace previously stopped at the HTTP hop boundary - one opaque span per service. Now the Redis (L2 cache) and MongoDB (L3 persistence) backend operations are traced as child spans nested under the request, so a trace shows which tier and which database call was slow, not just which service. Spans carry the backend (
redis/mongodb), tier (L2/L3), and operation (get/put/batch_get/commit/sync). Off unless tracing is enabled ([plugins.scale.microservices.tracing] enabled = true); a no-op otherwise. Covered bytest_memory_tracing.jac. - Feature: Build identity stamped into microservice images: Auto-built images now carry a
JAC_BUILD_SHAenv (12-char git commit SHA, suffixed-dirtywhen the build tree has uncommitted changes, baked in via a--build-argon the shipped/embeddedDockerfile.microservice), and every pod exposes aJAC_IMAGE_REFenv with its fully-resolved image ref including the content-addressed tag suffix. Together these let an in-pod self-check report "running build X" and answer "did my deploy actually update?" with zero RBAC. The build-identity layer is stamped last so a changing SHA only rebuilds that tiny build-time layer (note: because K-29's content-addressed tag is derived from the image digest, a new commit still forces a rollout - build identity is coupled to the commit, not just byte-content). (K-29's content-addressed image tag, which forces a rolling update and defeats the node image cache on rebuild, was already in place via_retag_with_content_id.) - Per-domain TLS on a shared nginx ingress. In
shared_ingressmode the Ingress now gets aspec.tlsblock ({app_name}-tlssecret for the configured domain) whenshared_ingress_tlsis set on an nginx class, so cert-manager's ingress-shim issues a per-domain Let's Encrypt cert. The issuer is supplied by the caller viashared_ingress_annotations(cert-manager.io/cluster-issuer). Gated on the nginx class, so non-nginx shared controllers (AWS ALB + ACM) that terminate TLS at the load balancer are unaffected. This lets one shared nginx controller front many domains, each with its own cert. root.sharedresolves in jac-scale deployments: The server maps the guest account to its root at startup so walkers can address the public graph viaroot.shared. (jaseci-labs/jaseci#6554)- Pinned-public mode for the graph visualizer. The
/graphpage accepts a?public=1query parameter that always shows the public (super root) graph, ignoring any stored session token, so embeds are unaffected by a logged-in session on the same origin. GET /__build_statuson the jac-scale server: Serves the hot reloader's build health as a documented endpoint.
Bug Fixes#
- Fix: creating graph nodes is now crash-safe with MongoDB persistence (#6488): if a server was killed or restarted (deploy, OOM, pod eviction) just after a
root ++> Node()connect, the graph could be left with an edge pointing at a node that was never saved, and every later traversal across it failed withNodeAnchor [...] is not a valid reference!. New nodes and their edges are now persisted in a crash-safe order, so an interrupted write can no longer leave a dangling edge that breaks traversal. (jaseci-labs/jaseci#6488)
Refactors#
- Refactor: Scheduler hardening: Dynamic jobs run as the creating user (stable
user_id) while static jobs continue running as__system__; system-role accounts are no longer loginable over HTTP;/jobsendpoints enforce per-job ownership onGET/PUT/DELETEand supportlimit/offset/trigger/created_bypagination; jobs are persisted before scheduling with MongoDB authoritative when configured; cron expressions are validated viaCronTrigger.from_crontab; thread-pool size, misfire grace, and shutdown drain timeout are configurable under[plugins.scale.scheduler]; scheduled jobs recordlast_run_at,last_status,last_error, andrun_count; graceful shutdown drains in-flight tasks onSIGTERM. - Admin portal UI drops redundant
clmarkers: Its.cl.jacsources rely on the file extension for client context. (jaseci-labs/jaseci#6557)
jac-scale 0.2.24#
New Features#
- Feature: distributed tracing for microservice mode. Adds OpenTelemetry tracing across the gateway and services: each request opens a span on every hop, the spans are linked into one trace, and that trace shares the same id as the request's log lines so you can pivot between logs and traces. Off by default - enable with
[plugins.scale.microservices.tracing] enabled = true(optionalendpointandsample_ratio). OpenTelemetry is an optional dependency (jac-scale[tracing]); without it tracing is a no-op and nothing changes. Covered bytest_tracing.jac. The trace-collection backend and the in-admin Traces page follow in separate PRs. - Feature: distributed tracing - trace storage and collection. The cluster side of distributed tracing, on top of the app-side OpenTelemetry SDK. Deploys Tempo as an in-cluster trace store and turns the existing Grafana Alloy log agent into an OpenTelemetry collector that forwards spans to it, adds a Tempo datasource to Grafana (so you can jump from a span to its logs by trace id), and points every pod at the in-cluster collector. Enable per app with
[plugins.scale.microservices.tracing] enabled = true. Logs-only, traces-only, and both are all supported, and teardown removes the trace store cleanly. Covered bytest_tempo_collection.jac. The in-admin Traces page follows in a separate PR. - Feature: distributed tracing - in-admin Traces page. Adds a Distributed Traces page to the jac-scale admin, completing the tracing feature. Under Monitoring you get a search bar (filter by service, errors only, minimum duration, and time window), a list of matching traces, and a flame-graph waterfall for the selected trace (one bar per span, sized by duration and nested by parent). It reads traces straight from the in-cluster Tempo store through admin-auth-gated endpoints, so no Grafana login is needed, and because a span and its log lines share the same trace id you can pivot between the Logs and Traces pages. Works in both monolith and microservice mode. Covered by
test_traces_admin.jac. - jac-scale server serves a conventionally-named client page at
/: Matching the core server, when[serve] base_route_appis unset the jac-scale (uvicorn) server serves a conventionally-named client page (app,index,main,home, orroot) at the root path; otherwise the root path stays the JSON API index. - Perf: Use cached
typecachemodule for field-type resolution in MongoBackend: ReplacedSerializer._get_field_typescalls with the new cachedget_field_typesfromtypecache, matching the jaclang-side optimization. - Feature: deliver SSO tokens to native/desktop loopback origins (RFC 8252): native/desktop "thin client" apps can now pass their own
http://127.0.0.1:<port>callback through the OAuthstateparameter so the token is redirected back to the origin that initiated the flow. The requested callback is validated with a newis_safe_loopback_redirecthelper, honored only when the scheme is http/https and the host resolves to a loopback address (127.0.0.1/localhost/::1), otherwise it falls back to the server-configuredclient_auth_callback_url, guarding againststatebeing abused as an open redirect to exfiltrate the token. As part of this work, the FastAPI route generator nowrepr()-escapes parameter descriptions when emitting endpoint source, so descriptions containing quotes, apostrophes, backslashes, or newlines no longer produce anunterminated string literalSyntaxErrorat server startup. - Feature: redesigned admin dashboard. The in-admin portal gets a clean, modern flat redesign: a sidebar layout, a light/dark theme toggle, and consistent lucide iconography across the Users, SSO, Metrics, Traces, and Logs pages. Login and reset screens are restyled to match. UI-only - no change to endpoints or behavior.
Bug Fixes#
- Fix: jac-scale CLI output no longer prints raw Rich markup: scale plan, microservice orchestrator banners, deployment status, and serve status lines use semantic
style=roles and console helpers instead of inline[red]/[cyan]/[bold]tags. - Fix: monolith deploys now get a meaningful
servicefield, populated service-filter dropdown, and a working pod regex in the admin Logs UI. The M-14.b structured-logging stack was reachable in monolith mode (the FastAPI middleware installs the JSON formatter for every request, monolith or microservice), but three monolith-specific blind spots made the in-admin Logs UI feel half-broken: (1)_SVC_NAMEread onlyJAC_SV_NAMEwhich K-track only injects on microservice pods, so every monolith line landed with"service":"unknown"; (2)_microservice_servicesreturned["gateway"] + routes.keys(), which on a monolith is just["gateway"](one bogus dropdown entry); (3)_service_to_pod_regexproduced<svc>-deployment-.*, but monolith pods are named<app_name>-<rs>-<rand>with no-deployment-infix, so filtering by service silently returned zero lines. Fix:_SVC_NAMEnow falls back throughJAC_SV_NAME -> K8S_APP_NAME -> "unknown";_microservice_servicesreturns[app_name]when the routes table is empty; and_service_to_pod_regexdrops the-deployment-infix in monolith mode. Microservice deploys are unaffected (existing code path runs verbatim whenever the routes table is non-empty). Regression tests intest_admin_logs.jacexercise both modes through pure helpers (_services_for_mode,_service_to_pod_regex_for_mode). - Fix: microservice gateway OOM-killed at its 1Gi memory limit, CrashLoopBackOff'ing the K8s deploy. The gateway pod runs
jac scale gatewayand loads the admin portal, admin-UI SPA, LLM-telemetry, log wiring and runtime OpenAPI aggregation that plain service pods (jac start <name>.jac) never touch, so its startup working set had grown up to the shared 1Gi service default inmanifest_builder._build_resourcesand exceeded it - the pod terminated withOOMKilled/ exit 137 and never finished its rollout. The gateway role now defaults to a 2Gi memory limit (service pods unchanged at 1Gi); the limit is still overridable per-service and via[plugins.scale.kubernetes]. Thek8s_e2efixture pins the gateway to 2Gi explicitly so a default regression can't silently reintroduce the crash loop, andDockerfile.microservice.exppinspython:3.12-slimby digest plus runspip freezeso unpinned-dependency drift is recorded in the build log.
Refactors#
- Refactor: introduce
Autoscalerabstraction and route HPA paths throughAutoscalerFactory: Adds anAutoscalerbase class withAutoscalerSpec/Triggermodels, anHPAAutoscalerengine wrapping the existing HPA logic, and anAutoscalerFactorywith a plugin registry. Both the monolith and microservice HPA deploy paths now resolve the engine viaAutoscalerFactory.create(autoscaler_engine, ...). A newautoscaler_enginefield (default"hpa") is added toKubernetesConfig; existingjac.tomlfiles require no changes. Operators upgrading will see two minor changes to existing HPAs: themetadatablock now includesnamespaceand anapplabel alongside the existingmanaged: jac-scalelabel; and the update path switches from a singlereplace(PUT) to aread+patch(GET + PATCH). Both changes are non-breaking.
jac-scale 0.2.23#
Bug Fixes#
- Fix: LLM telemetry and bare
/jobsendpoints were missing on the microservice gateway. Two endpoint groups the monolith registers inserve.core's_register_endpointswere not reachable through the microservice gateway, so requests that work in monolith mode 404'd (or returned the SPAindex.htmlinstead of JSON). (1) The gateway inheritedJacAPIServerAdmin+JacAPIServerLogsand calledregister_admin_endpoints+register_logs_endpointsin_install_admin_api, but never inheritedJacAPIServerLLMTelemetrynor calledregister_llm_telemetry_endpoints- so/admin/llm/telemetry/*(the in-admin LLM metrics page) was never materialized as FastAPI routes and the/admin/*dispatcher'scall_nexthit the SPA catchall. The gateway now wires the telemetry registrar alongside admin + logs. (2) The scheduler registers a bare/jobs(POST create, GET list) alongside/jobs/{job_id}, but the gateway's builtin passthrough only matched the/jobs/prefix and"/jobs".startswith("/jobs/")is False, so list/create 404'd while/jobs/{id}worked;/jobsis now in_BUILTIN_EXACT, mirroring how/graphhandles its own bare form. Regression guards added intest_gateway.jac.
jac-scale 0.2.22#
New Features#
- jac-scale: add shared ingress support for Kubernetes deployments: Added
shared_ingressandshared_ingress_classconfig options. Whenshared_ingress = true, jac-scale skips deploying a dedicated NGINX controller and instead attaches the app's Ingress routing rules to a pre-existing shared controller (default classnginx). The Ingress host field is set immediately at deploy time (not deferred to--enable-tls) so the shared controller can differentiate apps across namespaces by hostname.domainis required in this mode and is validated before any cluster resources are created. On local clusters the NodePort availability check is skipped. The post-deploy health check runs against the domain directly (http://{domain}{health_check_path}) instead oflocalhost:30080; a failure is reported as a warning rather than an error since the DNS record may not have propagated yet (A record on local clusters, CNAME on AWS). On destroy, only the Ingress rules are removed; the shared controller is left untouched. - jac-scale: support non-NGINX shared ingress controllers: Added
shared_ingress_annotationsandshared_ingress_tlstoKubernetesConfig. In shared-ingress mode, NGINX-specific annotations are now emitted only whenshared_ingress_classisnginx, and caller-supplied annotations are merged onto the Ingress metadata (caller values take precedence). This lets annotation-driven controllers such as the AWS Load Balancer Controller (ALB), Traefik, and GKE be used without baking any cloud-specific values into jac-scale, for example attaching apps to one shared ALB viaalb.ingress.kubernetes.io/group.namewith TLS from an ACM certificate.shared_ingress_tlsmakes the reported service URL usehttpsfor controllers that terminate TLS out-of-band (nospec.tlson the Ingress). Builds on the shared-ingress support in #6012. - Fix: in-admin Logs UI now shows newest-first with cursor pagination. The listing was sorted ascending and capped at 200 lines with no way to reach older entries, so the React panel showed only the oldest 200 lines in the window and stopped (the bottom looked like a hard cap rather than the end of the page). The
/admin/logsendpoint now sorts newest-first, accepts abefore=<nanosecond ts>cursor, and returnsnext_beforein the response; the LogsPage renders a "Load older" button that appends the next page beneath the current one. The trace-detail endpoint (/admin/logs/trace/{id}) still returns lines in causal (oldest-first) order so the trace journey side panel reads top-down.
Bug Fixes#
- Fix: in-admin Logs UI on the microservice gateway returned HTML / hit a bogus Loki URL. Two pieces of the M-14/A-05 work were dropped during PR-slicing and are restored here. (1) The microservice gateway lost its
JacAPIServerLogsinheritance and theregister_logs_endpoints()call in_install_admin_api: A-05c (#6153) correctly dropped the logs wiring as out-of-scope, but A-05a (#6211) only re-added it to the monolith server (serve.jac), not the gateway - so/admin/logs?…fell through to the SPA catchall and returnedindex.html(200 HTML), leaving the React Logs page spinning on "Loading logs...". (2)_loki_base_urllost itsK8S_APP_NAMEenv-var resolution +os.path.expandvarshandling - the manifest_builder half of that fix shipped in #6152 but the consumer half never made it into A-05a, so on K8s the URL resolved to a literalhttp://${K8S_APP_NAME}-loki-service:3100and DNS-failed. Both restore the EKS-validated state; atest_gateway.jacregression guard asserts the gateway exposes bothregister_admin_endpoints+register_logs_endpoints. - Fix: Redis L2 cache served stale empty edge list after edge writes: After any edge write, Redis could return
data.edges=[]for a node while MongoDB held the correct merged list, causing edge traversal ([-->(?:Type)]) to silently return nothing & breaking find-or-create logic._put_node_atomicnow reads back the authoritative merged edge set from MongoDB and invalidates the Redis entry instead of re-writing the un-merged in-memory snapshot, so the next read re-hydrates the correct edge list from MongoDB. Cross-pod L1 eviction is tracked separately in #6313. - Fix: uvicorn / FastAPI access logs bypassed M-14.b's JSON formatter, blanking trace_id on the in-admin Logs UI.
install_structured_loggingconfigured only the root logger, but uvicorn ships its own logger hierarchy (uvicorn,uvicorn.access,uvicorn.error,fastapi) with their own handlers andpropagate=False- so request access lines (INFO: 192.168.x.y:p - "GET /healthz HTTP/1.1" 200 OK) stayed raw text and never reached the JSON formatter. Result: the bulk of pod logs on Loki had noservice/level/trace_idfields and the trace-journey side panel in/admin/logslooked empty even for lines emitted during a real request. Fix:install_structured_loggingnow clears those four loggers' handlers and flipspropagate=Trueso every line (app code, uvicorn access, uvicorn error, FastAPI) lands on the root handler in the same JSON shape. Regression guard added intest_log_emit.jac. - Fix: clicking a trace in the in-admin Logs UI returned
Log backend unavailablebecause the generated LogQL contained a bare\[Loki's parser rejects asinvalid char escape._build_logql's trace-id pipeline emitted\[trace=...\]inside the line-filter string literal; Go'sstrconv.Unquote(used by Loki's LogQL parser) only allows the escapes\\ \" \n \r \tetc. and bails on\[. Brackets now render as\\[/\\]in the LogQL string so unescaping yields\[/\]for the regex engine. Also fixed an unrelated replace-order bug in thesearchfilter where backslashes were doubled after quotes had been escaped, re-doubling the just-inserted escapes and delivering stray backslashes to Loki's substring matcher. Regression tests intest_admin_logs.jac.
Refactors#
- Refactor: migrate jac-scale modules to updated jac runtime structure: Reorganized jac-scale package internals and tests to align with the latest compiler/runtime and plugin layout, including updated optional dependency wiring and microservice/admin module paths.
- Refactor: One-line JSX returns across the admin UI: Applied the updated formatter, collapsing short
return <Element/>;statements onto a single line across thejac-scaleadmin UI components, contexts, and pages.
jac-scale 0.2.21#
New Features#
- Feature: centralised log aggregation in the K8s monitoring stack (Loki + Grafana Alloy). Opt in for monolith deploys via
[plugins.scale.kubernetes].loki_enabled = trueand for microservice deploys via[plugins.scale.microservices.logs].enabled = true. Brings up a Loki StatefulSet (filesystem-backed, single-binary mode) plus a Grafana Alloy v1.6.0 DaemonSet (River-syntax config) that tails/var/log/pods/*viadiscovery.kubernetes+loki.source.fileand ships to Loki. Grafana gets a Pod Logs dashboard. Alloy supersedes Promtail, which went EOL on 2026-03-02. Alloy's--storage.pathis set to/tmp/alloyto sidestep a v1.6 remotecfg quirk where mkdir under a mounted emptyDir fails with EACCES. Microservice mode reuses the sameMonitoringDeployerso a singlejac start --scaledeploy withlogs.enabled = truebrings up Prometheus + Grafana + Loki + Alloy in one shot. (M-14.a) - Feature: structured-JSON log emission across microservice mode (M-14.b). Apps now emit one JSON document per log line on stdout instead of plain text, and Alloy's log pipeline parses the JSON, promotes bounded-cardinality fields (
service,level) to Loki labels, and keeps high-cardinalitytrace_idas a queryable JSON field. Switches the operational workflow fromkubectl logs ... | grep trace=abc12345to typed LogQL queries like{namespace="X"} | json | trace_id="abc12345",{namespace="X"} | json | service="gateway", level=~"ERROR|WARNING". Newinstall_structured_logging()helper injac_scale.microservices.runtime.log_emitwires a JSON formatter onto the root logger; the gateway calls it atsetup()time andJFastApiServer.request_context_middlewarecalls it once per process so every microservice emits JSON without per-app boilerplate.TraceIdLogFilternow setsrecord.trace_idas a first-class field (keeping the[trace=...]msg prefix for plain-text consumers). Builds on M-14.a's Loki + Alloy stack (#6155); enables A-05a's in-admin Logs UI. - Feature: in-admin Pod Logs UI (A-05a). The admin React bundle (mounted at
/admin/on the microservice gateway and the monolith server alike) gains a Monitor -> Logs tab that queries Loki directly through three new admin-auth-gated JSON endpoints (/admin/logs/services,/admin/logs?...,/admin/logs/trace/<id>). Replaces the "Grafana iframe" workflow for the common case - operators stay inside the admin UI, get a focused service+level+time filter row that auto-applies, a live-tail toggle, and a click-to-open side drawer per line that shows the line metadata + the whole trace journey (every other log line sharing the sametrace_idacross all services, in causal order). Builds on M-14.a's Loki + Alloy backend (#6155) and M-14.b's structured-JSON shape (#6210) soservice/levelcome from Loki labels andtrace_idfrom the JSON body. Microservice gateway gets the same admin-API plumbing the monolith server already has by addingJacAPIServerLogsto its inheritance.
Bug Fixes#
- Fix:
jac start --scaleno longer wipes TLS configuration on redeployment:_deploy_ingress_resourcewas callingreplace_namespaced_ingress(a full PUT) on every deploy, silently stripping thespec.tlsblock, rule host, and TLS annotations (cert-manager.io/issuer,ssl-redirect,force-ssl-redirect) that--enable-tlshad previously written. After any redeployment following TLS enablement, the app served the controller's default self-signed certificate on HTTPS while the cert-managerCertificateand TLS secret remained intact, masking the issue. The fix switches topatch_namespaced_ingressand removesspec.tlsandspec.rules[*].hostfrom the patch body entirely; fields jac-scale does not own are simply never sent, so the API server leaves them untouched. The same change applies to the RedisInsight Ingress. No read-before-write is required and there are no fields to carry forward.
jac-scale 0.2.20#
New Features#
- Added
suppress_health_check_logsoption under[plugins.scale.server]injac.toml. When set totrue, health-check endpoint access log entries (/docs,/,/openapi.json,/health,/healthz,/healthz/ready,/healthz/live) are suppressed from CLI output and Kubernetes pod logs to reduce noise. Defaults tofalse(logs shown by default). - Add: identity management, email verification, password reset, and pluggable emailer: Five new endpoints under
/user/*(add-identity,send-verification,verify-identity,forgot-password,reset-password) plus anEmailerabstraction that lets any backend (built-in SMTP, SendGrid, Mailgun, etc.) be plugged in viajac.toml.add-identityonly attaches identities;send-verificationdispatches the email and is retryable. Identity uniqueness is enforced atomically at the storage layer (Mongo unique sparse index, SQLite PK with transactional rollback), so concurrentadd-identityrequests for the same value resolve to a clean 409 instead of a race. Tokens are SHA256-hashed at rest, single-use, and TTL-bounded; persisted in MongoDB (TTL index) when configured, in-memory otherwise.forgot-passwordandsend-verificationare rate-limited (per recipient email and per authenticated user respectively) with budgets configurable under[plugins.scale.auth](forgot_password_rate_per_hour,forgot_password_burst,send_verification_rate_per_hour,send_verification_burst);send-verificationreturns429 RATE_LIMITEDwithretry_after_secondson rejection, whileforgot-passwordkeeps the 200 envelope to preserve the existence-leak guarantee. Structured audit events for both flows are routed through a dedicatedjac_scale.auditlogger so ops can ship them to file / syslog / ELK independently of regular logs. See Identity Management & Password Reset and Emailer for full docs. - Feature: S3 Storage Backend: Implemented a robust S3 storage provider using
boto3, supporting AWS S3, MinIO, and Cloudflare R2 with full file lifecycle support. - Feature: Configuration-Driven Storage: Added
StorageFactorysupport for dynamic switching between local and S3 backends viajac.tomlor environment variables (e.g.,JAC_STORAGE_TYPE=s3). - Feature: AWS Optional Dependency: Added
awsandtestoptional dependency groups topyproject.tomlto manageboto3andmotorequirements. - Refactor: Cluster provider detection now uses the Strategy pattern: Previously, cloud-provider-specific behaviour (service type, port validation, Prometheus scrape port, ingress controller service, NLB wait) was scattered across
kubernetes_target.jac,monitoring.jac, andingress.jacas repeatedif cluster_env == 'aws'string comparisons. These have been replaced by aClusterProviderbase class with concreteAWSProviderandLocalProvidersubclasses. A newget_cluster_provider()function detects the cluster at deploy time and returns the appropriate instance. Adding support for a new cloud provider (e.g. GCP, DigitalOcean) now requires only a single new subclass - no changes to deploy, monitoring, or ingress logic. - Feature:
jac start --scale --dry-runpreview with lint validation: A new dry-run mode renders the K8s deployment plan as a per-service card view (image, replicas, HPA bounds, cpu/mem resources, route, PDB, mounts) instead of dumping raw YAML. Inline lint diagnostics catch config bugs the manifest builder won't reject - HPAmin > max,cpu_request > cpu_limit, invalid resource units, missing images, PDB drain-deadlocks, etc. Exit code 2 if errors are found. The raw multi-doc YAML stream is gated behind--show-yamlforkubectl diffworkflows. MongoBackendnative pushdown via capabilities: declares{'type_pushdown', 'field_pushdown', 'id_in', 'slice'}and implementsexecute_planto translate aQueryPlaninto a singlecollection.find(filter)+skip/limit.ensure_indexes()(idempotent, called frompostinit) creates the(arch_type, type)compound index plus a descendingupdated_atindex so type-based queries IXSCAN instead of COLLSCAN.get_rootsnow uses the indexed filter rather than scanning the whole collection.- Feature: K8S_APP_NAME and K8S_NAMESPACE env vars on every K-track pod: In-pod code (Loki URL builder, log shippers, future observability helpers) had no reliable way to learn the deployed app name.
jac.tomltemplating likeapp_name = "${K8S_APP_NAME}"is taken literally because the config loader doesn't expand env-var placeholders, and stock K-track pods had no upstream env var carrying the app name.MicroserviceManifestBuilder._build_envnow emitsK8S_APP_NAMEandK8S_NAMESPACEon every microservice container alongside the existingJAC_SV_NAMEsentinel, sourced fromk8s_configat deploy time. Matches the convention already in place forMONGODB_URI/REDIS_URLwhere in-pod code reads fromos.environinstead of re-parsingjac.toml. - Feature: admin JSON endpoints (
/admin/login,/admin/me,/admin/users, ...) on the microservice gateway: Previously the static admin UI loaded on the microservice gateway but everyfetch()from the React bundle fell through to the SPA fallback - soPOST /admin/loginreturned<!DOCTYPE html>and React died withUnexpected token '<'.MicroserviceGatewaynow inheritsJacAPIServerAdminand gains an_install_admin_api()step insidesetup()that wraps the gateway's existing FastAPI app in aJFastApiServer, wires upUserManager+ApiKeyManager, registers the admin endpoints via the inheritedregister_admin_endpoints(), and callscreate_server()to materialize the queued JEndPoints as real FastAPI routes. The dispatcher middleware grew a/admin*branch that delegates to FastAPI's router viacall_nextwhen the API is installed and falls back to the statichandle_adminpath otherwise. Partial-install failures (Mongo unreachable, etc.) resetself.server = Noneso the static-UI fallback stays reachable instead of routing into an empty FastAPI router.bootstrap_admin_uialso gained an editable-install fallback: whenjac-scale/admin/_dist/is missing (becausepip install -eskips the release pipeline that pre-builds the bundle) it invokes the inheritedbuild_admin_client()to runjac build main.jacinadmin/ui/. Drops the need for downstream consumers to add their ownRUN jac run scripts/build_admin_ui.jacstep.
Bug Fixes#
- Fix:
recover_allnow processes nodes before edges, and warns when a re-link target is missing: Quarantine recovery previously iterated in undefined DB order -- if anEdgeAnchorwas restored before its connectedNodeAnchor, the re-link step silently no-oped and leftdata.edgesempty even though both records were nominally recovered. The batch is now sorted so everyNodeAnchoris written back first. Additionally, both the SQLite and Mongo backends now emit alogger.warningwhen a re-link target is not found (missingelsebranch in SQLite; discardedmatched_countin Mongo), giving operators a clear signal when recovery is partial. - Fix:
_deploy_databasessignature mismatch in microservice provisioner: #5840 dropped thecluster_envparameter fromKubernetesTarget._deploy_databases()and updated the monolith call site but missed the microservice path indatabase_provisioner.jac, breaking everyjac start --scale --experimentaldeploy withtakes 5 positional arguments but 6 were given. Aligned the microservice call site to the new 4-arg signature. - Fix:
jac-scaleplugin hooks (SSO, auth,/healthz, admin) now apply reliably when the module is imported outside thejacCLI, restoring SSO endpoints, the/healthzprobe, and authenticated/metrics. - Fix: graph writes no longer silently lost on MongoDB deployments: Every node update that involved an edge change (connecting a child node, adding an edge from a walker) was being silently discarded on MongoDB 6.x. The internal atomic edge-merge operation uses MongoDB's aggregation-pipeline
$set, which rejects empty embedded documents with error 40180. Because the default access-control field (access.roots.anchors) always serialises as{}, every write through this path failed. The fix strips empty dictionaries from the serialised node data before it reaches MongoDB. Existing data does not need migration; the deserialiser restores empty dicts automatically on load. - Fix:
jac start --scaleno longer silently no-ops as a dry-run (#6115): removes the workaround inplugin.jacthat read the underscored arg name to dodge the upstream phantom-key bug; with the registry +HookContext.get_argfix landing in jaclang, either spelling resolves correctly.jac start --scalenow reliably hits the deploy path;jac start --scale --dry-runreliably hits the plan path. - Fix: quarantine reason now tells you exactly what went wrong: When a node is quarantined, the stored reason now distinguishes between a missing class ("class X unresolvable") and a bad field value ("archetype field deserialization failed: X"), so you know immediately whether to update your import paths or fix your stored data.
- Fix: Stale Redis cache after cascade quarantine causes dangling edge errors: After a node was quarantined and its connected edges were cascade-quarantined, pods that had previously cached the affected live nodes continued to serve stale entries with the orphaned edge IDs - even across restarts - causing
EdgeAnchor [<id>] is not a valid referenceon the next walker traversal. Redis is now correctly invalidated as part of the cascade.
Refactors#
- Refactor: split
JacScaleUserManager.create_userinto aUserManager-contract overload +create_user_with_identities: The baseUserManagerinterface expectscreate_user(username, password); jac-scale's identity-aware variant moves to a separatecreate_user_with_identities(identities, credential, profile)method, andcreate_user(username, password)is now a thin shim that delegates to it. Authenticate now mints the JWT inline so the result carries thetokenthe contract expects. - Refactor: read base path via
Jac.get_base_path_dir(): Migrated to the new accessor; the priorJac.base_path_dirclass attribute has been removed. - Refactor: request middleware uses token-based context push/reset: jfast_api's per-request context now uses
push_request_context+reset_request_context(token)with an explicitctx.close(), replacing the removedset_request_context/clear_request_contextfootgun pair.
jac-scale 0.2.19#
Bug Fixes#
- Fix: Redis authentication and RedisInsight dashboard connectivity in K8s: Refactored Redis configuration loading and ACL rule definitions, added username/password secrets to deployment tests, opened metrics endpoints for unauthenticated scraping, tuned liveness/readiness probe timeouts and failure thresholds, enabled gzip compression and improved HTML handling on the Redis Ingress, and configured RedisInsight to auto-accept the EULA with a provided encryption key so the dashboard connects out of the box.
- jac-scale: fix blocking event-loop call in request middleware:
request_context_middlewarewas callingctx.set_user_root()synchronously inside anasync defhandler, blocking the uvicorn event loop on every authenticated request. Switched toawait ctx.aset_user_root()so the user-root anchor load goes through the non-blocking async Redis/MongoDB path. - Fix: cascade-quarantine dangling edges on schema drift: When a
NodeAnchor's archetype becomes unresolvable (e.g. a node type is removed between deploys),MongoBackendnow also quarantines every connectedEdgeAnchorand strips those IDs from the source node'sdata.edges, preventing permanently corrupt traversal state. Recovery (recover-all) re-links edges back to their source node, fully restoring graph connectivity. - Fix:
_put_node_atomicno longer clobbers archetype scalars from concurrent walkers: Replaced the shallow$mergeObjectspipeline (which wholesale-replaceddata.archetypeon every commit) with per-fielddata.archetype.<field>dot-notation writes that only touch dirty fields. Concurrent walkers on separate pods can now safely write different scalar fields to the same node without reverting each other's changes. The atomic edge-merge guarantee from PR #5644 is fully preserved. - Fix: identity storage uses Jac-native
any:identity_storage.jacnow imports the Jacanykeyword instead of Python'styping.Any, clearing W1104 and cascading type errors across all storage methods.
jac-scale 0.2.16#
New Features#
- Configurable MongoDB PVC Storage Size: MongoDB persistent volume storage size is now configurable via
mongodb_storage_sizeinjac.toml(default:1Gi). Increasing the size on redeploy is supported and automatically patched onto the existing PVC without affecting stored data. Decreasing the size is blocked with an explicit error to prevent data loss. - Add: streaming sv-to-sv RPC:
def:pubgenerator returns now stream yields to the caller as SSE (text/event-stream+data: {json}+event: endterminator; errors viaevent: error). The consumer side gets a Python generator that yields parsed event dicts; httpx connection lifecycle follows the generator. Retry/circuit-breaker applies to connect failures; in-flight streams are not retried. Includes fixes to jaclang_finalize_call_response(isgenerator check was on the wrong field) and a missing SSE framing wrapper in jac-scale's serve. - Add: configurable gateway-to-service forward timeout:
[plugins.scale.microservices].http_forward_timeout(float seconds, default 30), with per-service override at[...services.NAME].http_forward_timeout. Controls aiohttp timeout inraw_forward+stream_forward. Distinct fromrpc_timeout(sv import httpx).jac setup microserviceemits a reference block. - Add: K-track v1 - Kubernetes deploy for microservice mode: New
KubernetesMicroserviceTarget(KubernetesTarget)fans one image out to one Deployment + ClusterIP Service + HPA + PDB persv import-discovered service, plus a gateway. Auto-selected by_scale_pre_hookwhen[plugins.scale.microservices].enabled=true+--scale. Pod-specJAC_SV_NAMEdifferentiates services from the gateway (__gateway__). Includes: - K8s DNS adapter: new
get_sv_registryhookimpl detects K8s-in-cluster viaKUBERNETES_SERVICE_HOSTand returnshttp://<svc>-service.<ns>.svc.cluster.local:<port>URLs; gateway works unchanged in both local and K8s modes. - Zero-downtime rolling deploys:
RollingUpdate{maxSurge:1, maxUnavailable:0}+/healthz/ready+/healthz/live(split so liveness doesn't trip on dependency degradation) +terminationGracePeriodSeconds = drain_timeout_seconds + 5+preStop sleep 5(bridges kube-proxy endpoint-propagation gap). Verified by the real-app e2e: zero non-2xx during gateway + service rolling restarts. - HPA + PDB per service:
autoscaling/v2 HPA(default min=1, max=3, cpu_target=70%) andpolicy/v1 PDB(defaultmaxUnavailable=1). Opt-out per-service withhpa.enabled=false/pdb.enabled=false. - Per-service config layering:
[plugins.scale.microservices.services.NAME](and__gateway__for the gateway) controlsreplicas,cpu_request/cpu_limit,memory_request/memory_limit,env,image_tag(canary),rpc_timeout,http_forward_timeout,hpa.*,pdb.*. - Optional Ingress:
[plugins.scale.microservices.ingress]withenabled,host,ingress_class_name,annotations. Single Ingress -> gateway Service; HTTP only (TLS via cert-manager/ACM is deployment-specific). Controller-agnostic. - Add: auto-build + auto-distribute:
jac start --scalenow builds + distributes the image automatically. New_cluster_detect.jacclassifies the active kubeconfig context (minikube / k3d / kind / remote / unknown);_image_build.jacresolves the right Dockerfile (user override<project>/Dockerfile.microservice> shipped<pkg>/scripts/Dockerfile.microservice> embedded fallback) and dispatches build/distribute per cluster type (minikube docker-env,k3d image import,kind load docker-image, ordocker pushfor remote). Activated only when_JAC_SCALE_AUTO_BUILD=1so existing tests bypass cluster-touching work. Builds the FE bundle (jac build <client.entry>) on the host before docker build so the gateway image contains.jac/client/dist/. Writes a.dockerignoreto the build context to avoid 2GB+ context transfers. - Add: stateful microservices out of the box: MongoDB + Redis auto-provisioned as StatefulSets (reusing the monolith K8s target's
_deploy_databases) andMONGODB_URI/REDIS_URLenv injected viavalueFrom: secretKeyRefon every pod. Wait-for-DB init containers prevent crash-loops on first deploy. Opt-out via[plugins.scale.kubernetes].mongodb_enabled=false/redis_enabled=false. - Add: gateway sticky sessions for WebSocket: gateway Service gets
sessionAffinity: ClientIP(3-hour timeout) so WS reconnects land on the same pod. Service pods stay round-robin. - Add: cross-service shared volumes (
[[plugins.scale.microservices.shared_volumes]]): per-volumeserviceslist of pods that should mount the volume atmount_path. PVC mode (size,access_mode,storage_class) for cloud; hostPath mode (host_path) for single-node dev clusters. Use case: services that intentionally share filesystem state. - Add: K8s Secrets injection (
[plugins.scale.secrets]): values are jaclang-core-interpolated (${VAR}expanded) and applied as a K8s Secret; pods get the secrets viaenvFrom: secretRef. - Add:
service_account_nameconfig: attach every pod to a pre-bound SA (apps that need cluster API access for sandbox-spawning / operator-style controllers). - Add: peer URL auto-injection: every pod gets
JAC_SV_<PEER>_URLenv vars pointing at sibling Service DNS, sosv importdispatch works without depending on the runtime hookimpl populating the registry first. - Add: real-app e2e (
jac-scale/scripts/k8s_microservice_real_e2e.sh): builds an actual image, deploys via the microservice K8s pipeline, waits for rollout, exercises gateway + per-service routing + optional Ingress, then runs a zero-downtime rolling-restart assertion (hammer at 10 req/s duringkubectl rollout restart, fail on non-2xx). - Fix: gateway
/healthzno longer fans out to backends: was in the builtin-passthrough exact-match set, returning 404 before any backend registered. Now direct-handled as a/healthalias (matches K8s convention). - Fix: K8s-mode registry pre-marked HEALTHY:
start_gateway_onlyskips the orchestrator (K8s owns lifecycle), so registry entries used to stay REGISTERED forever andhandle_proxy503'd every request. Now pre-flipped to HEALTHY; transport errors from not-yet-Ready pods bubble naturally (kube-proxy only routes to Ready pods). - Fix:
get_microservices_configreturns theingressblock: previously dropped silently soingress.enabled=truehad no effect. - UX: actionable errors on the three most-common K8s deploy failures: missing kubeconfig + no in-cluster SA (re-raise with minikube/eks/gcloud guidance), unreachable API server (early
list_namespaceprobe instead of failing mid-apply), empty routes (concrete[plugins.scale.microservices.routes]snippet instead of silent gateway-only deploy). - UX: clean exit on deploy fail: pre-hook used to
raiseand fall through to the local-mode dev server; now prints a red message and setscancel_return_code=1. - UX: fail loud on python_image fallback: microservice pods used to silently CrashLoopBackOff with "jac: command not found" when the deploy fell through to
python:3.12-slim. Now raises with concrete next-step guidance (opt-in via_JAC_SCALE_GUARD_FALLBACK_IMAGE=1). - Docs:
microservices/docs.mdK8s section,getting_started.md(5-min walkthrough), updated[plugins.scale.kubernetes]reference. - Add:
PATCH /user/meand stricter profile validation: NewPATCH /user/meendpoint merges supplied keys into the existing profile (preserving SSO data) and returnsUpdateProfileResponse. Profile validation now runs as a PydanticAfterValidator, soPOST /user/registerandPATCH /user/mereturn 422 on invalid input automatically.ssois reserved as a server-managed profile key, and the SSO callback defensively coercesprofile.ssoto{}when it isn't a dict, protecting users registered before reserved-key enforcement.GET /user/menow returns a typedMeResponse(withexclude_nonepreserving the original wire shape). - Add: kvstore distributed-lock primitives:
Db(returned bykvstore(db_type="redis")) gainsset_nx_with_ttl(key, value, ttl)for atomic acquire (RedisSET NX EX) anddelete_if_equals(key, expected_value)for fence-token release (Luaif GET == expected then DEL). Together these are the minimal building block for cross-pod mutexes, leader leases, and debounce windows, so apps no longer need to reach past the kvstore abstraction and pool their own redis-py clients to coordinate. MongoDB raisesNotImplementedError, matching the existing pattern forset_with_ttl/incr/expire. - Feat: Event-streaming broker: Adds an
EventStreamBrokerabstraction (jac_scale.events.broker) withpublish/@subscribe/consume/ack, retry with DLQ, and replayable offsets viastart_from. Ships withLocalEventStream(in-memory) andRedisEventStream(Redis Streams); selection is automatic based on whether a Redis URL resolves. Off by default; enable via[plugins.scale.events]injac.toml. - Feature: walker-flavored sv-to-sv RPCs: The
JacScalePluginoverrides the newsv_walker_callhook so cross-service walker spawns benefit from the same machinery asdef:pubcalls: Authorization passthrough,X-Trace-Idpropagation, exponential-backoff retry, per-servicerpc_timeout, and a per-provider circuit breaker. Walker calls share the breaker with function calls (both signal provider liveness), so a tripped breaker protects either RPC kind. - jac-scale: Native async drivers for MongoDB and Redis:
MongoBackendoverridesaget/acommitusing PyMongoAsyncMongoClient(PyMongo >= 4.9) andRedisBackendoverridesaget/aputusingredis.asyncio, eliminatingasyncio.to_threadoverhead for L2/L3 reads under concurrent load. Both clients are held as process-level singletons via_process_cache, matching the pattern established for the sync clients.ScaleTieredMemory.acommitcoordinates the async flush path. - Feat: MongoBackend / RedisBackend slice-pushdown instrumentation:
MongoBackendandRedisBackendnow exposefetch_count,put_count, andreset_counters()(mirroringSqliteMemory.l3_fetch_count) so the new edge-ref slice-pushdown runtime can be empirically verified end-to-end against the production stack. With the pushdown active,[-->][?:T][0:50]against a 2,000-neighbor graph drops from 4,400 Mongo fetches / 4,400 Redis cache promotions / 2,250 ms to 50 / 50 / 37 ms (60x) onScaleTieredMemory. Newtest_topology_slice_pushdown.jacintegration tests assert these bounds via testcontainers.
Bug Fixes#
- Fix: Desktop apps installed at read-only paths no longer crash on startup: The SQLite identity store now writes to the user's data directory, so apps installed system-wide (e.g. via
.deb/.rpmunder/usr/lib/) start cleanly. - Fix: declare
uvicorn[standard]so jac-scale's WebSocket endpoints actually work: jac-scale'sserve.jacregisters WebSocket routes (WebSocketConnectionManager,register_websocket_endpoints), but the package previously pinned bareuvicorn, which has no WebSocket implementation library bundled. Any WebSocket upgrade against the API server (jac-scale's own WS routes, browser dev tools probing, monitoring tooling, etc.) was rejected withUnsupported upgrade request. No supported WebSocket library detected.followed by HTTP 405. Switching the dep touvicorn[standard]>=0.38.0,<0.39.0pulls inwebsockets,httptools,uvloop,watchfiles, andpython-dotenv-- the conventional production install when a FastAPI app exposes WS routes -- so upgrades succeed and the warning is gone. - Fix: MongoDB process-level connection pool:
MongoBackendnow shares a singleMongoClientper worker process via_process_cache, eliminating per-request connection churn.is_available()only cachesTrueso a missingMONGODB_URIin one context no longer permanently blocks MongoDB in later contexts;close()drops the local reference only, keeping the shared client alive. - Fix: Redis process-level connection pool + MGET + TTL:
RedisBackendnow shares a single client per worker process via_process_cache(bounded byredis_max_connections, default 20);batch_get()uses a single MGET pipeline call instead of N individual GETs; defaultredis_default_ttlraised from 0 to 3600s to prevent unbounded key growth;is_available()only cachesTrueto avoid cross-context blocking. - Fix: ScaleTieredMemory.batch_get full L1→L2→L3 read-through:
batch_get()previously skipped the Redis L2 tier and always fetched L1 misses directly from MongoDB. Corrected order: L1 hit → Redis MGET for L1 misses → MongoDB$infor L2 misses, with L3 hits promoted to both L1 and L2. - Fix: JWT validation removes redundant user_exists() DB call:
validate_jwt_token()previously calleduser_exists()(a MongoDB round-trip) on every authenticated request after already verifying the JWT signature and expiry. Removed the extra call;jwt.decode()verification is sufficient. - Fix: Isolated ExecutionContext per scheduled job: Scheduled jobs now create their own
JScaleExecutionContext(pushed viapush_request_context, reset infinally) so concurrent jobs cannot share L1 memory state with each other or with in-flight HTTP requests. - Fix: RedisBackend.batch_put for bulk L2 cache writes: Added
batch_put(anchors)method toRedisBackendso callers can promote multiple anchors into L2 cache in a single logical operation without repeated per-anchor calls. - Fix: acommit race condition causing edge data loss under concurrent walker writes:
MongoBackend.acommitused a plainbulk_writewith_anchor_to_doc(last-writer-wins), bypassing the delta-merge_put_node_atomicpath insync(). Under concurrent load, concurrent walker commits could silently overwrite each other's edge writes. Fixed by routingScaleTieredMemory.acommitthroughasyncio.to_thread(self.commit)so the correct merge-awaresync()path (with$setUnion/$setDifferenceMongoDB pipeline) is always used. Also fixes the user registration format intest_async_io_blocking.jacandtest_persistence_race.jacto match the current identity-based auth API. - Fix: redundant MongoDB system root lookup on every request eliminated:
JScaleExecutionContext.init()constructed a fresh in-memory L1 cache on every request, causing the system root anchor lookup to fall through L1 → L2 (Redis) → L3 (MongoDB) unconditionally. The_process_cachedict now caches the system root anchor after the first resolve; subsequent requests inject it directly into L1 before the lookup, reducing per-request MongoDB round-trips to zero for this path. - Fix: eliminate redundant
MongoBackend.sync()pass per request (issue 1g): Added_committed: boolflag toScaleTieredMemory;acommit()sets the flag after a successful full commit and short-circuits on subsequent calls. The jfast middleware commit is changed from synchronousctx.mem.commit()toawait ctx.mem.acommit(), removing O(L1-size) hash computation from the event loop on every request while preserving the middleware as a safety net for error paths and non-walker routes. - Fix:
ScaleTieredMemory.acommit()now forwardsanchorargument tocommit(): Previously theanchorparameter was accepted but silently dropped.commit()always receivedNoneregardless of what the caller passed. The argument is now forwarded correctly viaasyncio.to_thread(self.commit, anchor), matching the contract of the baseMemory.acommit()interface.
jac-scale 0.2.15#
New Features#
- Add: Nested LLM Trace Tree in Admin Dashboard: The LLM Traces page now renders a fully nested, arbitrarily-deep call tree for
by llm()invocations, with parent-child relationships resolved via byllm'sparent_invocation_id. - Add: Streaming sv-to-sv RPC (generator returns): A
def:pubfunction returning an iterator now streams its yields to the caller as Server-Sent Events instead of being str-fallback-serialized. Wire format isContent-Type: text/event-streamwithdata: {json}\n\nframing and an explicitevent: endterminator; producer-side exceptions are emitted asevent: errorand re-raised asRuntimeErrorout of the consumer's iterator. The consumer side (sv-RPC stub in jaclang core + jac-scale's plugin override) detects SSE by Content-Type and hands back a Python generator that yields parsed event dicts; lifecycle of the underlying httpx connection follows the generator. Retry/circuit-breaker still applies to connect failures; in-flight streams are not retried (already-consumed events cannot be replayed). Pairs with a_finalize_call_responsefix in jaclang/runtimelib (the existing isgenerator check was onreports, notresult, so explicit generator returns silently fell into the str() fallback) and a missing SSE framing wrapper in jac-scale's serve.endpoints (the StreamingResponse path emitted dict reprs instead of valid SSE). - Add: Configurable gateway-to-service forward timeout:
[plugins.scale.microservices].http_forward_timeout(float seconds, default 30) controls the aiohttp timeout used byraw_forward(built-in passthrough fan-out) andstream_forward(path-routed proxy). Per-service overrides at[plugins.scale.microservices.services.NAME].http_forward_timeoutmirror the existingrpc_timeoutprecedence pattern - useful for LLM/long-running services that need minutes rather than the global default. Distinct fromrpc_timeout, which still controls inter-servicesv importcalls (httpx); these are two different code paths through two different HTTP clients.jac setup microserviceemits a commented reference block. - Feat: Custom Object Support in Walker/Function API Parameters: Walkers and
@restspecfunctions withhas/parameter fields typed as user-defined Jacobj(or nested/list/optional thereof) now generate proper nested Pydantic request bodies and OpenAPI schemas instead of collapsing tostr. Endpoint wrappers reconstruct typed archetype instances from validated JSON before dispatch, so walker handlers receive realUserBody(etc.) instances, not raw dicts. Recursive obj types (obj TreeNode { has children: list[TreeNode]; }) are handled via a placeholder-cached model registry inspired by PR #5387's ref-mode tracking. Implemented by resolving each parameter's actualtype_objviaget_type_hintsincreate_{walker,function}_parameters, carrying it throughAPIParameter.type_obj, and adding_resolve_type/_build_pydantic_model/_pydantic_to_jactoJFastApiServer. - Add: Email format validation on register/login: Identities with
type: emailare now validated as proper email addresses at the pydantic layer, returning422 Unprocessable Entitywith a clear error for malformed values.IdentityInputis now a discriminated union ofEmailIdentityInput(typed asEmailStr) andUsernameIdentityInput, and the OpenAPI schema at/docsmarks email identities withformat: email. - Feat: Partial Anchor Updates: Optimizes MongoDB writes by skipping full document replacement when only archetype fields change. Implements four-layer system with dirty-field tracking, selective serialization, and smart routing to targeted
$setoperations on changed fields, while preserving full rewrites for structural changes or first inserts. - Add: optional
profileon register,GET /user/me, and SSO profile population:POST /user/registeraccepts an optionalprofiledict (string/number/boolean values, bounded for safety). The newGET /user/mereturns the authenticated user's identities, role, and profile with credentials stripped. SSO providers (Google, GitHub, Apple) populateprofile.sso.<platform>(display_name,first_name,last_name,picture) and refresh it on every login. - FastAPI
/cl/__error__resolves React component stacks: The jac-scale client-error endpoint now logs source-mapped JS and React component-stack frames mapped onto the originating.jacfiles, matching the built-in server's behavior. - Scale context: initialize PermissionDenied diagnostics list:
JScaleExecutionContext.initnow seeds the newdiagnostics: list[PermissionDenied]field on the parentExecutionContext, so the scale subclass participates in the cross-user write-denial diagnostic plumbing introduced in #5788 instead ofAttributeError-ing on the first denial.
Bug Fixes#
- Fix: Authenticated requests now always run as the correct user: Previously, there was a brief window during request startup where a request could execute as the system root instead of the authenticated user, even with a valid JWT. This has been resolved by moving JWT validation into a dedicated middleware that runs before the request context is created. Your user's root node is set correctly from the very first operation in every request. Invalid, expired, or forged tokens are now rejected with
401 Unauthorizedimmediately at the middleware layer rather than silently falling through. - Fix: Concurrent walker edge loss: Concurrent walkers modifying the same node no longer silently lose edges. Edge changes are merged via per-request deltas instead of full replacement. MongoDB uses atomic aggregation pipelines (
$setUnion/$setDifference); SQLite usesBEGIN IMMEDIATEtransactions.MongoBackend.putis deferred tosync(), andScaleTieredMemory.commitroutes all writes throughsync()so nothing bypasses the merge-aware path. - Fix: Per-walker atomicity for MongoDB persistence:
MongoBackend.put()now defers all writes tosync(), which already routesNodeAnchorupdates through_put_node_atomicand other anchors through_write_to_db. This restores per-walker transactional boundaries matchingJac.commit()'s contract. - Fix:
pubendpoints no longer return 401 on invalid/expired bearer tokens: The JWT middleware was short circuiting all requests carrying an invalid or expiredAuthorization: Bearertoken with an immediate401response, before any endpoint handler could run. This causedpub(public) endpoints to reject requests from clients with stale tokens in browser storage. The middleware now ignores token validation failures and lets requests through; per-endpoint auth checks (requires_auth) still enforce401for protected walkers and functions.
Refactors#
- Refactor: Sandbox module removed: The sandbox module (local, docker, kubernetes providers, ingress providers, and related infrastructure) has been removed from jac-scale.
- Refactor: Share testcontainers across
test_memory_hierarchytests: Each test previously started and stopped its own MongoDB and Redis Docker containers, adding ~14 redundant container lifecycle operations and doubling suite runtime (5 min → 10 min). Containers are now started once per test session via lazy-init helpers (_get_mongo,_get_redis) and stopped viaatexit. State is reset between tests by droppingjac_dband callingredis.flushall()instead of restarting containers.
jac-scale 0.2.14#
- Identity-based auth system: Replaced flat username/password user model with a flexible identity + credential architecture. Users can register with multiple identities (username, email) and credentials (password), stored as arrays in MongoDB. Login accepts any identity type. SSO accounts are stored as identities (
type: sso,provider: google) within the user document instead of a separatesso_accountscollection. - JWT user_id claim: JWT tokens now use
user_id(UUID) instead ofusernameas the primary claim, enabling identity changes without token invalidation. - Feat: SV-to-SV Eager Auto-Spawn in
jac start:jac start consumer.jacnow brings up everysv import-ed provider (including transitive ones) automatically before serving the first request, so single-host multi-service deployments need exactly one terminal and zero env vars. - Fix: ScaleTieredMemory Initialization: Changed
ScaleTieredMemory.init(use_cache)topostinitlifecycle method withuse_cacheas a class field, fixing initialization order issues. - Fix: Windows Compatibility for Local Sandbox: Added platform guards for Unix-only APIs, cross-platform temp paths, Windows-compatible shell commands, --jac-cli sidecar support, and increased readiness timeout to 300s.
- Fix: Spurious "write access" warnings on system root during sync: Skip
check_write_access()for unchanged anchors in MongoDB sync, eliminating noisyCurrent root doesn't have write access to NodeAnchor Rootlog spam on every authenticated request. -
Persistence: MongoBackend gets Schema Drift + Quarantine + Aliases:
MongoBackendnow mirrorsSqliteMemory's schema-migration surface -- documents are stamped with archetype identity + fingerprint, undeserializable docs route to a<collection>_quarantinesidecar instead of being silently dropped, and DB-resident rescue aliases live in<collection>_aliases. The new jaclangjac db inspect / quarantine / alias / recovercommands work against Mongo deployments unchanged. See Persistence & Schema Migration. -
Optional Install Groups: Heavy dependencies (pymongo, redis, prometheus-client, apscheduler, kubernetes, docker) are no longer required by default. Install only what you need via extras:
pip install jac-scale[data](MongoDB + Redis),[monitoring](Prometheus),[scheduler](APScheduler),[deploy](Kubernetes + Docker), or[all]for everything. Groups are combinable:pip install jac-scale[data,monitoring]. Missing dependencies produce clear error messages with install instructions. Existing users should usepip install jac-scale[all]to keep current behavior. - Fix:
jac startcrashes withoutjac-scale[scheduler]: The scheduler setup injac startunconditionally initialized APScheduler, causing a'NoneType' object is not callableerror when APScheduler wasn't installed. The scheduler now gracefully degrades: static/interval/cron tasks still work via the core jaclang scheduler, and dynamic scheduling features are skipped with a clear log message when APScheduler is absent. - 1 small refactor/change.
jac-scale 0.2.13#
- jac-mcp included by default: Added to the default Kubernetes package set in jac-scale.
jac-scale 0.2.12#
- Pre-built Admin Dashboard: The admin dashboard UI is now pre-built during the release process and shipped as static assets in the package. Previously, navigating to
/admin/on first load triggered a full Vite build from source, causing significant lag. The server now copies bundled assets instantly, falling back to source build only in dev mode. - Dev Mode: Named endpoints in Swagger docs: Dev mode (
jac start --dev) now registers individual named endpoints (e.g./walker/read_todos) instead of generic catch-all routes (/walker/{walker_name}), so Swagger UI shows all walker/function names. HMR still works - routes are refreshed automatically on file changes. - API docs enabled by default:
/docs,/redoc, and/openapi.jsonare now available in all modes (not just dev). Disable withdocs_enabled = falsein[plugins.scale.server]. - 2 small refactors/changes.
jac-scale 0.2.11#
- Fix: Sandbox status returns stale RUNNING for dead pods:
KubernetesSandbox.status()was returning the cached registry state (oftenRUNNING) whenread_namespaced_pod_status()threw an exception (pod deleted or unreachable). This caused callers to believe the sandbox was still alive, preventing recovery. Now returnsSTOPPEDwhen the pod query fails so dead pods are detected immediately. - Fix: Admin portal build fails from PyPI install:
jac.tomlandstyles/*.csswere excluded from the wheel becausepyproject.tomlpackage-data only included*.jacfiles. The admin portal'sjac buildcommand needs these files to discover the project config and generate Tailwind CSS output.
jac-scale 0.2.10#
- Dev Mode: API Docs accessible from client URL: In dev mode (
jac start --dev), the FastAPI Swagger UI (/docs) and OpenAPI spec (/openapi.json) are now proxied through the Vite dev server, so you can browse your API docs at the same URL as your app without switching ports. - Configurable API docs:
/docs,/redoc, and/openapi.jsonare controlled by thedocs_enabledsetting in[plugins.scale.server](defaults totrue). Setdocs_enabled = falseto hide them in production. - Health check endpoint: Added
GET /healthzfor liveness checks. Returns{"status": "ok"}with no authentication required. Useful for Kubernetes probes and monitoring. - Warm Pool TTL: Added
warm_pool_ttlconfig to control warm pod lifetime independently from sandboxttl_seconds. Default0means warm pods live indefinitely until claimed, preventing the pool from emptying after the sandbox TTL expires.
jac-scale 0.2.9#
- Ingress Rate Limiting (DDoS Protection): Added configurable NGINX rate limiting to the Kubernetes ingress. Limits sustained requests per second, burst headroom, and concurrent connections per client IP using the leaky bucket algorithm. Returns
429 Too Many Requestswhen limits are exceeded. Configurable via[plugins.scale.kubernetes]injac.toml:ingress_limit_rps(default: 20),ingress_limit_burst_multiplier(default: 5),ingress_limit_connections(default: 20). - Cookie-Based Sticky Sessions (optional): Added opt-in session affinity via NGINX cookie (
route). When enabled, every user is pinned to the same pod regardless of IP changes (mobile, NAT, proxies). Cookie never expires in the browser. On pod failure NGINX automatically re-routes and rewrites the cookie. Enabled by default. Disable viaingress_session_affinity = falsein[plugins.scale.kubernetes]. - Performance: MongoBackend.batch_get(): New
batch_get(ids)usesfind({_id: {$in: [...]}})so edge traversals hit MongoDB with 2-3 queries instead of one per anchor. On cold starts with 100 edges this cuts 201 round-trips down to 3. - Extensible Deployment Targets and Image Registries:
DeploymentTargetFactoryandImageRegistryFactorynow support plugin-registered targets viaregister(name, factory). External packages can register custom deployment targets (e.g.DeploymentTargetFactory.register("enterprise-kubernetes", my_factory)) and image registries without modifying jac-scale. Custom targets load their config from[plugins.scale.<target-name>]injac.toml. - PWA/Web Target Integration Test: Added test to verify
jac start --client pwauses jac-scale's FastAPI server when installed (checks/docsendpoint availability). - Fix: HPA config ignored on redeployment:
create_hpasilently swallowed 409 Conflict errors when the HPA already existed, so updatedmin_replicas,max_replicas, andcpu_utilization_targetvalues injac.tomlwere never applied on subsequent deploys. Changed to a replace-first, create-on-404 pattern consistent with how Ingress and ConfigMap resources are managed, ensuring HPA configuration is always kept in sync withjac.toml. - Sandbox Security Hardening: Hardened K8s sandbox pods by dropping all Linux capabilities (
drop: ALL), enabling seccompRuntimeDefaultprofile (~44 dangerous syscalls blocked), disabling service account token automounting (prevents K8s API access from inside sandboxes), and adding a configurable/appemptyDir size limit (app_storage_limit, default 1Gi) to prevent node disk exhaustion. Applied consistently to both on-demand and warm pool pods. The sandbox base Dockerfile now creates a dedicated non-root user (jac, UID 1000) and installs Bun system-wide so it's accessible under the security context.
jac-scale 0.2.8#
- 1 small changes.
jac-scale 0.2.7#
- Apple & GitHub SSO Support: Added Apple Sign In and GitHub as SSO providers via
fastapi-sso. Unified the SSO callback into a single endpoint per platform (/sso/{platform}/callback) that auto-registers new users or logs in existing ones. Initiation endpoints remain separate (/sso/{platform}/login,/sso/{platform}/register). SSOhostconfig simplified to just the base URL (e.g.,http://localhost:8000). Configure via[plugins.scale.sso.apple]and[plugins.scale.sso.github]injac.toml. - Kubernetes Security Hardening: Added container-level security contexts (
allowPrivilegeEscalation: false,drop: ALL,readOnlyRootFilesystem,seccompProfile: RuntimeDefault), dedicatedServiceAccountper workload, component-specific NetworkPolicies enforcing proper isolation (databases only accept traffic from main app + dashboards, monitoring components only accept ingress from trusted internal sources), andpod-security.kubernetes.io/enforce: baselinenamespace labels. - Scheduler Code Quality Cleanup: Extracted shared
_authenticate_request()and_validate_trigger()helpers to remove duplicated auth/validation logic across/jobsendpoints. Fixedget_job()to query by ID directly instead of loading all jobs. Replaced deprecateddatetime.utcnow()withdatetime.now(timezone.utc). Persistedis_walkerin job data to avoid redundant introspector lookups. Replaced silent exception swallowing with debug logging. - Metrics Endpoint Fix & Prometheus Auth: Fixed
/metrics500 error (TransportResponseis a dataclass, not Pydantic - replaced.model_dump()withdataclasses.asdict()). Added HTTP Basic Auth support so Prometheus can scrape/metricsviabasic_authinprometheus.yml. - Hash-based dirty checking for MongoDB/Redis persistence: Replaced
is_updatedflag with hash-based change detection at sync time. Read-only requests no longer trigger any database writes. All mutation types, including in-place mutations (list.append(),dict[k]=v,set.add(), nested objects), are automatically detected and persisted. - Client-Side Error Reporting Endpoint: Added
POST /cl/__error__endpoint toJacAPIServerCorefor receiving client-side JavaScript errors. Errors are logged via thejaclang.client_errorslogger and printed to the dev console with stack traces for visibility. - Source-Mapped Error Stack Traces: Client error stack traces received at
/cl/__error__are now resolved from bundled JS locations to original.jacfile paths and exact line numbers via the centralizedSourceMapperwith two-layer resolution. - Client Error Rate Limiting: The
/cl/__error__endpoint now deduplicates identical error messages (10s window) and caps at 20 errors per minute to prevent log flooding from render loops or repeated failures. - Add: LLM Telemetry Admin Dashboard: Added a
TelemetryStorebackend that subscribes to byllm's agent callback and litellm's per-call logger, grouping all LLM calls within a single agent invocation into one trace (tokens, cost, latency, user prompt, agent response). Traces are served via four new admin REST endpoints (/admin/llm/telemetry/summary,/traces,/traces/{id},/filters) and visualized in the admin UI with a metrics overview page and a paginated, filterable trace detail view. - Fix: Nginx error when domain is set before
--enable-tls: Ingress now always deploys with a wildcard rule; the domainhostis only applied when--enable-tlsis run, fixing the app being unreachable via IP/NLB whendomainwas set injac.tomlbefore initial deployment. - Sandbox System: Isolated preview environments with Docker and Kubernetes backends, warm pod pool, routing proxy with WebSocket/HMR, and path-safe file operations. Configure via
[plugins.scale.sandbox]injac.toml. - Request-Scoped L1 Memory Cache: Made the L1 (in-memory) cache request-scoped using
ContextVar, ensuring each request gets an isolated cache that is automatically cleared after execution, preventing stale data, memory leaks, and cross-request interference while maintaining backward compatibility for CLI and tests.
jac-scale 0.2.6#
- Domain & TLS support (
--enable-tls): Added custom domain name routing and automatic HTTPS via cert-manager + Let's Encrypt. Setdomaininjac.toml, deploy normally, point your CNAME to the NLB, then runjac start app.jac --scale --enable-tlsto enable HTTPS without a full redeploy. cert-manager is installed automatically and certificates are renewed automatically. Configurable viadomainandcert_manager_emailin[plugins.scale.kubernetes].
jac-scale 0.2.5#
- Fix: Walker Route OpenAPI Parameter Naming: Fixed inconsistency where walker routes with node parameters used
{nd}in URL paths but declarednodein OpenAPI schema, causing FastAPI validation errors ("Field required"for parameternode). The OpenAPI schema now correctly usesndto match the actual path variable and function parameter. This fixes requests to/walker/{walker_name}/{node_id}endpoints. Note:nodeis a reserved Jac keyword, sondis used as the parameter name throughout. - Fix: K8s deployment time regression: NGINX Ingress controller now starts in parallel with databases/monitoring, restoring test runtimes.
- NGINX Ingress Controller: Replaced individual NodePort services with a single NGINX Ingress controller. All services are now ClusterIP, accessible via path-based routing through
ingress_node_port(default:30080):/app,/grafana,/cache-dashboard/,/db-dashboard. - Fix: Ingress routes now update correctly on re-deploy: Switched from
patchtoreplacefor Ingress resources so toggling monitoring or dashboards off actually removes the old routes instead of leaving them in place. - Security: RedisInsight always requires authentication: The
/cache-dashboardroute now always enforces HTTP basic-auth whenredis_dashboard = true. Credentials are hashed with bcrypt (replaces the previous SHA1 scheme). The auth Secret is also cleaned up automatically whenredis_dashboardis disabled. - Fix: Redis Insight dashboard 404 and nginx-auth ConfigMap not updating on re-deploy.
- Fix: Parser Strictness Compliance: Moved docstrings before signatures in
kubernetes_utils.impl.jacand converted nested function docstring to comment inapi.cl.jacto comply with the stricter RD parser. - [Internal] Refactor: Extract graph visualizer HTML into a standalone template file.
- User storage now supports both MongoDB and SQLite: User authentication and management automatically uses SQLite when MongoDB is not configured, maintaining full backward compatibility with existing installations.
- Fix: Include
redis.conf.templatein package distribution: FixedFileNotFoundErrorduring Redis deployment when jac-scale is installed via pip (non-editable install). Theredis.conf.templatefile is now correctly included in the wheel distribution viapackage-dataconfiguration inpyproject.toml.
jac-scale 0.2.4#
- Automatic Port Fallback: When starting the server with
jac start, if the specified port is already in use, the server now automatically finds and uses the next available port instead of crashing with "Address already in use". A warning message displays when using an alternative port. Supports up to 10 port retries with cross-platform compatibility (Linux and Windows). - [fix]Fix for internet facing aws load balancer
- 1 Minor refactor/change.
- Scheduling Support: Added static and dynamic task scheduling for walkers and functions via
@schedule(trigger=...). Static schedules (INTERVAL/CRON/DATE) start automatically at server startup; dynamic schedules (DYNAMIC) are managed via a new/jobsREST API (create, list, get, update, delete) with MongoDB persistence. Scheduled items are excluded from standard walker/function endpoints. A__system__user executes all scheduled tasks; configure via[plugins.scale.scheduler]injac.toml. - Fix: Fix for internet-facing AWS load balancer
- [Internal] Convert username and password for redis and mongodb to secret when injecting to pod deployment
- 3 Minor refactors/changes.
- update jac-scale plugin documentation with missing features
-
APP_NAME, K8s_NAMESPACE, DOCKER_USERNAME, DOCKER_PASSWORD are no longer read from environment variables and must be configured via `jac.toml.
-
Component-Level Destroy:
jac destroy app.jac --component <name>now supports removing individual Kubernetes components (application,database,cache,monitoring,dashboard) without tearing down the entire deployment. - Redis Cache Configuration with TTL Support: Added configurable eviction policies and TTL support for Kubernetes Redis deployments via
jac.toml(redis_max_memory,redis_eviction_policy,redis_eviction_samples,redis_default_ttl,redis_enable_keyspace_notifications); ConfigMap-based with automatic pod restart on change. Anchors stored in Redis L2 cache now respect theredis_default_ttlsetting and will automatically expire after the configured duration (default: 0 = no expiration). - 1 small refactor/change.
- Fix: Redis deployment annotation null guard: Fixed
'NoneType' object has no attribute 'get'crash duringjac start --scalewhen an existing Redis deployment has no annotations. Kubernetes returnsNonefor the annotations field when none exist, so the config-hash check now guards against this.
jac-scale 0.2.3#
- Admin API Endpoints: REST API for administrative operations at
/admin/*including user management, SSO provider listing, and configuration access. - Admin-Only Metrics Endpoint: The
/metricsPrometheus scrape endpoint now requires admin authentication. Unauthenticated requests receive a 403 Forbidden response. This prevents unauthorized access to server performance data. - Admin Metrics Dashboard: Added
/admin/metricsendpoint that returns parsed Prometheus metrics as structured JSON with summary statistics (total requests, average latency, error rate, active requests). The admin dashboard monitoring page now displays metrics in a visual dashboard with HTTP traffic breakdown, system stats (GC, memory, CPU time), and real-time counters. - Set default maximum memory limit of k8s pods from unlimited to 12Gb
- Automatically deploy Redis (RedisInsight) and MongoDB (MongoDB Dashboard) dashboards in Kubernetes when the redis_dashboard and mongodb_dashboard flags are enabled.
- Set default maximum memory limit for jaseci app pod to None (unlimited)
- 1 Minor refactor/change.
jac-scale 0.2.2#
- Data Persists Across Server Restarts: Graph nodes and edges created during a session now persist automatically in MongoDB. When you restart your
jac startserver, previously created data is restored and accessible - no manual save operations required. jac statusCommand: Newjac status app.jaccommand to check the live deployment status of all Kubernetes components (Jaseci App, Redis, MongoDB, Prometheus, Grafana). Displays a color-coded table with component health, pod readiness counts, and service URLs. Detects running, degraded, pending, restarting (crash-loop), and not-deployed states.- Resource Tagging: All Kubernetes resources created by jac-scale are now labeled with
managed: jac-scale, enabling easy auditing and identification viakubectl get all -l managed=jac-scale -A. - k8s metrics dashboard in prometheus and grafana
- Jac status command to check deployment status of each component of k8s
- Chore: Codebase Reformatted: All
.jacfiles reformatted with improvedjac format(better line-breaking, comment spacing, and ternary indentation). - Fix: Root-Level Font/Asset 404s: Added
.jac/client/dist/as a search candidate inserve_root_asset, fixing 404s for font files (.woff2,.ttf, etc.) bundled by Vite with root-relative@font-face url()paths.
jac-scale 0.2.1#
- Admin Portal: Added a built-in
/admindashboard for user management and administration. Features include user CRUD operations (list, create, edit, delete), role-based access control withadmin,moderator, anduserroles, force password reset, and SSO account management view. - Admin API Endpoints: REST API for administrative operations at
/admin/*including user management, SSO provider listing, and configuration access. - Admin Configuration: New
[plugins.scale.admin]section injac.tomlto configure admin portal settings. Environment variablesADMIN_USERNAME,ADMIN_EMAIL, andADMIN_DEFAULT_PASSWORDsupported. - Refactor:
JacSerializerremoved, useSerializer(api_mode=True):JacSerializerhas been removed fromjaclang.runtimelib.server. API serialization is now handled directly bySerializer.serialize(obj, api_mode=True)fromjaclang.runtimelib.serializer. Storage backends are unaffected; continue usingSerializer.serialize(obj, include_type=True)for round-trip persistence. Addedsocial_graph.jacfixture demonstrating native persistence withdb.find_nodes()for querying the_anchorscollection using MongoDB filters. - Internal: refactor jac-scale k8s loadbalancer/service to support other vendors
- Before deploying to the local Kubernetes cluster, check whether the required NodePorts are already in use in any namespace; if they are, throw an error.
- jac destroy command deletes non default namespace
- Fix: Code-sync pod stuck in ContainerCreating: Added preferred
podAffinityto the code-sync pod spec so it prefers scheduling on the same node as the code-server pod. Fixes RWO (ReadWriteOnce) PVC mount failures when Kubernetes schedules the two pods on different nodes. - 1 Minor refactor
- Internal: check whether redis,mongodb,grafana and prometheus are also restarted when checking deployment status
jac-scale 0.2.0#
- SSO Frontend Callback Redirect: SSO callback endpoints now support automatic redirection to frontend applications. Configure
client_auth_callback_urlinjac.tomlto redirect with token/error parameters instead of returning JSON, enabling seamless browser-based OAuth flows. - Graph Visualization Tests: Added tests for
/graphand/graph/dataendpoints.
jac-scale 0.1.11#
- Graph Visualization Endpoint (
/graph): Added a built-in/graphendpoint that serves an interactive graph visualization UI in the browser.
jac-scale 0.1.10#
- support horizontal scaling: based on average cpu usage k8s pods are horizontally scaled
- Client Build Error Diagnostics: Build errors now display formatted diagnostic output with error codes, source snippets, and quick fix suggestions instead of raw Vite/Rollup output. Uses the
jac-clientdiagnostic engine for consistent error formatting acrossjac startandjac build.
jac-scale 0.1.9#
- Refactor: Modular JacAPIServer Architecture: Split the monolithic
serve.impl.jacinto three focused impl files using mixin composition: serve.core.impl.jac: Auth, user management, JWT, API keys, server start/postinitserve.endpoints.impl.jac: Walker, function, webhook, WebSocket endpoint registrationserve.static.impl.jac: Static files, pages, client JS, graph visualization- Fix:
@restspecPath Parameters: Resolved a critical bug where using@restspecwith URL path parameters (e.g.path="/items/{item_id}") caused the server to crash on startup withCannot use 'Query' for path param 'id'. Both functions and walkers with@restspecpath templates now correctly annotate matching parameters asPath()instead ofQuery(). Mixed usage (path params alongside query params or body params) works correctly across GET and POST methods. Starlette converter syntax (e.g.{file_path:path}) is also handled. - Remove Authorization header input from Swagger UI: The
Authorizationheader is no longer exposed as a visible text input field in Swagger UI for walker, function, and API key endpoints. Authentication tokens are now read transparently from the standardAuthorizationrequest header (accessible via the lock icon), consistent with theupdate_usernameandupdate_passwordendpoints. - 1 Minor refactors/changes.
jac-scale 0.1.8#
- Internal: K8s integration tests now install jac plugins from fork PRs instead of always using main
- .jac folder is excluded when creating the zip folder that is uploaded into jaseci deployment pods.Fasten up deployment
- Fix:
jac startStartup Banner: Server now displays the startup banner (URLs, network IPs, mode info) correctly viaon_readycallback, consistent with stdlib server behavior. - Various refactors
- PWA Build Detection: Server startup now detects existing PWA builds (via
manifest.json) and skips redundant client bundling. The/static/client.jsendpoint serves Vite-hashed files (client.*.js) in PWA mode. - Prometheus Metrics Integration: Added
/metricsendpoint with HTTP request metrics, configurable via[plugins.scale.metrics]injac.toml. - Update jaseci scale k8s pipeline to support parellel test cases.
- early exit from k8s deployment if container restarted
- Direct Database Access (
kvstore): Addedkvstore()function for direct MongoDB and Redis operations without graph layer. Supports database-specific methods (MongoDB:find_one,insert_one,update_one; Redis:set_with_ttl,incr,scan_keys) with common methods (get,set,delete,exists) working across both. Import fromjac_scale.libwith URI-based connection pooling and configuration fallback (explicit URI → env vars → jac.toml). - Code refactors: Backtick escape, etc.
- Persistent Webhook API Keys: Webhook API key metadata is now stored in MongoDB (
webhook_api_keyscollection) instead of in-memory dictionaries. API keys now survive server restarts. - Native Kubernetes Secret support: New
[plugins.scale.secrets]config section. Declare secrets with${ENV_VAR}syntax, auto-resolved at deploy time into a K8s Secret withenvFrom.secretRef. - Minor Internal Refactor in Tests: Minor internal refactoring in test_direct.py to improve test structure
- fix: Return 401 instead of 500 for deleted users with valid JWT tokens.
- Docs update: return type
any->JsxElement - 1 Small Refactors
- promethius and grafana deployment: Jac-scale automatically deploys promethius and grafana and connect with metrics endpoint.
jac-scale 0.1.7#
- KWESC_NAME syntax changed from
<>to backtick: Updated keyword-escaped names from<>prefix to backtick prefix to match the jaclang grammar change. - Update syntax for TYPE_OP removal: Replaced backtick type operator syntax (
`root) withRootand filter syntax ((`?Type)) with[?:Type]across all docs, tests, examples, and README.
jac-scale 0.1.6#
-
WebSocket Support: Added WebSocket transport for walkers via
@restspec(protocol=APIProtocol.WEBSOCKET)with persistent bidirectional connections atws://host/ws/{walker_name}. TheAPIProtocolenum (HTTP,WEBHOOK,WEBSOCKET) replaces the previouswebhook=Trueflag-migrate by changing@restspec(webhook=True)to@restspec(protocol=APIProtocol.WEBHOOK). -
fix: Exclude
jac.local.tomlduring K8s code sync: The local dev override file (jac.local.toml) is now excluded when syncing application code to the Kubernetes PVC. Previously, this file could override deployment settings such as the serve port, causing health check failures.
jac-scale 0.1.5#
- JsxElement Return Types: Updated all JSX component return types from
anytoJsxElementfor compile-time type safety. - Client bundle error help message: When the client bundle build fails during
jac start, the server now prints a troubleshooting suggestion to runjac clean --alland a link to the Discord community for support.
jac-scale 0.1.4#
- Console infrastructure: Replaced bare
print()calls withconsoleabstraction for consistent output formatting. - Hot fix: call state: Normal spawn calls inside API spawn calls supported.
--no_clientflag support: Server startup now honors the--no_clientflag, skipping eager client bundling when the client bundle is built separately, adn we need server only.- PyJWT version pinned: Pinned
pyjwtto>=2.10.1,<2.11.0and updated default JWT secret to meet minimum key length requirements.
jac-scale 0.1.3#
-
GET Method Support: Added full support for HTTP GET requests for both walkers and functions, including correct mapping of query parameters, support for both dynamic (HMR) and static endpoints, and customization via
@restspec(method=HTTPMethod.GET). -
Streaming Response Support: Streaming responses are supported with walker spawn calls and function calls.
-
Webhook Support: Added webhook transport for walkers with HMAC-SHA256 signature verification. Walkers can be configured with
@restspec(webhook=True)to receive webhook requests at/webhook/{walker_name}endpoints with API key authentication and signature verification. -
Storage Abstraction: Introduced a pluggable storage abstraction layer for file operations.
- Abstract
Storageinterface with standard operations:upload,download,delete,list,copy,move,get_metadata - Default
LocalStorageimplementation injaclang.runtimelib.storage - Hookable
store(base_path, create_dirs)builtin that returns a configuredStorageinstance -
Configure via
jac.toml [storage]section orJAC_STORAGE_PATH/JAC_STORAGE_CREATE_DIRSenvironment variables -
jac destroy command wait till fully removal of resources
-
SPA Catch-All for BrowserRouter Support: The FastAPI server's
serve_root_assetendpoint now falls back to rendering SPA HTML for extensionless paths whenbase_route_appis configured. API prefix paths (cl/,walker/,function/,user/,static/) are excluded from the catch-all. This matches the built-in HTTP server's behavior for BrowserRouter support. -
Internal: Explicitly declared all postinit fields across the codebase.
PyPI Installation by Default#
Kubernetes deployments now install Jaseci packages from PyPI by default instead of cloning the entire repository. This provides faster startup times and more reproducible deployments.
Default behavior (PyPI installation):
Experimental mode (repo clone - previous behavior):
New CLI Flag: --experimental#
Added --experimental (-e) flag to jac start --scale command. When enabled, falls back to the previous behavior of cloning the Jaseci repository and installing packages in editable mode. Useful for testing unreleased changes.
Version Pinning via plugin_versions Configuration#
Added plugin_versions configuration in jac.toml to pin specific package versions:
[plugins.scale.kubernetes.plugin_versions]
jaclang = "0.1.5" # or "latest"
jac_scale = "0.1.1" # or "latest"
jac_client = "0.1.0" # or "latest"
jac_byllm = "none" # use "none" to skip installation (will install relevant byllm version)
When not specified, defaults to "latest" for all packages.
Enhanced restspec Decorator#
The @restspec decorator now supports custom HTTP methods and custom endpoint paths for both walkers and functions.
- Custom Methods: Use
method=HTTPMethod.GET,method=HTTPMethod.PUT, etc. - Custom Paths: Use
path="/my/custom/path"to override the default routing.
jac-scale 0.1.1#
jac-scale 0.1.0#
Initial Release#
First release of Jac-Scale - a scalable runtime framework for distributed Jac applications.
Key Features#
- Conversion of walker to fastapi endpoints
- Multi memory hierachy implementation
- Support for Mongodb (persistance storage) and Redis (cache storage) in k8s
- Deployment of app code directly to k8s cluster
- k8s support for local deployment and aws k8s deployment
-
SSO support for google
-
Custom Response Headers: Configure custom HTTP response headers via
[environments.response.headers]injac.toml. Useful for security headers like COOP/COEP (required forSharedArrayBuffersupport in libraries like monaco-editor).