Skip to content

test(0.34.x): KEDA suite (#8940) plus the seal-time venv fix (#8944), stacked for wake-latency measurement - #8946

Draft
kugesan1105 wants to merge 13 commits into
jaseci-labs:release/jaclang-0.34.18from
kugesan1105:test/keda-suite-plus-sealvenv-0.34.18
Draft

test(0.34.x): KEDA suite (#8940) plus the seal-time venv fix (#8944), stacked for wake-latency measurement#8946
kugesan1105 wants to merge 13 commits into
jaseci-labs:release/jaclang-0.34.18from
kugesan1105:test/keda-suite-plus-sealvenv-0.34.18

Conversation

@kugesan1105

Copy link
Copy Markdown
Collaborator

Measurement branch, not meant to merge on its own: #8940 (the merged KEDA suite backport) plus the seal-time venv fix from #8944, stacked so jacBuilder#1642's wake-latency rig can measure the seal fix against the #8940 baseline on the same 0.34.x binary generation, same cluster, same day.

Contents

Validation with the published jac 0.34.18 binary

231 passed, 4 skipped across scale/tests/deploy/, test_fat_bundle (including bundle-digest determinism, now covering the sealed venv), and the new test_seal_venv suite; the 5 seal-venv tests include two real pack_jab e2e runs. Pre-existing failures on this line (test_drain, test_fleet_pod_optout, test_rate_limit) are unchanged.

How this gets measured

jaseci/jaclang:experimental-<this PR> via the experimental-image workflow dispatched from release/jaclang-0.34.18 (its Dockerfile matches this pre-#7969 closure), then jacBuilder#1642's deploy-pr.yml with experimental_jaseci_pr=<this PR> and measure_wake_latency=true. Expected: the jac-bootstrap init phase collapses from the ~21s-class install measured on the #8940 baseline run to a few seconds of extra bundle untar plus a ~1s marker check, identical on cold and warm legs.

Developer-Linus and others added 12 commits September 4, 2026 04:44
…ght diagnostics (jaseci-labs#7620)

## Summary

Closes jaseci-labs#7404.

`KEDAAutoscaler.preflight()` only ever verified that the core
`keda.sh/v1alpha1 ScaledObject` API existed on a cluster. That check is
not sufficient for HTTP activated workloads, which also depend on the
KEDA HTTP Add-on's `InterceptorRoute` API, two Kubernetes Services (the
external scaler and the interceptor proxy), and a compatible Add-on
version. Without these checks, `apply_http_activation` could create a
valid `ScaledObject` that never actually activates from HTTP traffic,
with no clear diagnostic explaining why.

This PR introduces structured capability discovery for the KEDA HTTP
Add-on and wires it into `apply_http_activation`, so a broken or
partially installed cluster fails with an actionable error instead of
silently deploying a nonfunctional workload.

## Changes

| File | Changes |
|---|---|
| `jac/jaclang/scale/deploy/autoscale/keda_capabilities.jac` (new file)
| Adds the `KEDACapabilities` data object, matching the shape specified
in the issue: `core_available`, `http_addon_available`,
`interceptor_route_api_version`, `external_scaler_address`,
`interceptor_service_address`, `warnings`, and `errors`. Also adds
`SUPPORTED_HTTP_INTERCEPTOR_API_VERSIONS`. |
| `jac/jaclang/scale/deploy/autoscale/keda_autoscaler.jac` | Imports
`KEDACapabilities`.<br>Adds a new `interceptor_service_address` field,
defaulted the same way `http_scaler_address` already is.<br>Replaces the
old bool only `_http_preflight_cache` with `_capabilities_cache`, which
holds a full `KEDACapabilities` object per cluster.<br>Declares
`discover_capabilities`, `invalidate_capabilities`,
`_check_service_address`, `format_capabilities`, and
`_capabilities_cache_key`. |
| `jac/jaclang/scale/deploy/autoscale/keda_autoscaler.impl.jac` |
`discover_capabilities` checks the core `ScaledObject` API and the HTTP
Add-on's `InterceptorRoute` API independently, so a missing core install
is distinguished from a missing Add-on. A 403 on either check is
reported as a distinct permission error rather than being folded into
"not installed."<br>If the `v1beta1 InterceptorRoute` API is absent (not
RBAC denied), it additionally probes the legacy `v1alpha1
HTTPScaledObject` API. If that is present, the result is a distinct
"upgrade" warning with the exact `helm upgrade` command, rather than
being reported as "Add-on not installed." A 403 on this legacy probe is
reported as its own permission error rather than being folded into "not
installed."<br>`_check_service_address` verifies the external scaler and
interceptor proxy Services actually resolve, and reports which one is
missing.<br>Results are cached per cluster identity via a new
`_capabilities_cache_key` helper, which combines `_get_cluster_key()`
with this instance's configured `http_scaler_address` and
`interceptor_service_address`. This keeps two instances that target the
same cluster but are configured with different service addresses from
reusing an incompatible cached result.
`discover_capabilities(refresh=True)` and `invalidate_capabilities()`
remain the explicit refresh paths.<br>`format_capabilities` produces a
single concise, human readable line for CLI output.<br>`preflight()` now
also catches a 403 and returns a distinct permission error instead of
letting the `ApiException` propagate unhandled, which is what happened
previously.<br>`apply_http_activation` now calls
`discover_capabilities()` instead of its own inline, duplicate check. If
discovery reports any errors (missing service, RBAC denied, or similar),
it raises `ValueError` with the details. If the core KEDA API or the
HTTP Add-on is simply absent (no errors, just not installed), it keeps
the existing behavior of logging a warning and returning `False` rather
than raising. A partial install, where the Add-on and its services are
healthy but core KEDA itself is missing, now also falls into this gate
instead of proceeding to create resources that would fail against a
nonexistent API. |
| `jac/jaclang/scale/tests/deploy/test_keda_http_activation.jac` | Adds
a new capability discovery test section covering: core only clusters,
full installs, missing external scaler service, missing interceptor
service, legacy Add-on detection, RBAC denied on the core check, the
HTTP Add-on check, and the legacy probe, cache and refresh behavior,
cache isolation between instances with different service addresses,
explicit invalidation, the raise on broken capabilities and skip on
missing core behavior in `apply_http_activation`, `format_capabilities`
output, and the `preflight()` 403 fix. Test setup uses small composable
mock builders (`_custom_api_for`, `_core_api_for`) rather than one mock
per scenario, and one table driven test covers the distinct diagnostic
cases to avoid duplicating near identical assertions. |

## Out of scope

As noted in the issue: no auto install or upgrade of KEDA, no ingress
creation, no general health checking of every scaler type. This PR also
does not verify that a workload's traffic is actually routed through the
interceptor at runtime, only that the interceptor's own API and Service
objects exist and resolve. That distinction is called out explicitly in
the implementation plan for this issue.

## Test plan

- [x] Core only clusters report core available and HTTP unavailable.
- [x] Fully installed clusters report HTTP ready with no errors.
- [x] Missing external scaler service, missing interceptor service,
legacy Add-on version, and RBAC denied cases (core check, HTTP Add-on
check, and legacy probe) each produce a distinct diagnostic.
- [x] Results are cached per cluster identity, including the configured
service addresses, and refresh via `discover_capabilities(refresh=True)`
and `invalidate_capabilities()`.
- [x] `apply_http_activation` raises when capabilities are broken,
returns `False` gracefully when the Add-on or core KEDA is simply
absent, and does not proceed to create resources on a partial install.
- [x] Documentation update showing the supported Helm install and
upgrade flow.
- [x] Full deploy test suite (169 tests) passes with no regressions.

---------

Co-authored-by: Kugesan Sivasothynathan <kugesan.sivasothynathan@jaseci.org>
(cherry picked from commit 5234c81)
… policy (jaseci-labs#7899)

Closes jaseci-labs#7764.

`jac-scale`'s generated HPA `behavior` block (added in jaseci-labs#7445) has always
shipped one hardcoded scale-down policy — `{"type": "Percent", "value":
50, "periodSeconds": 60}`, no `selectPolicy` — with no way to override
it per service. Because the deploy path re-applies the full generated
manifest on every `jac start --scale`, any out-of-band `kubectl patch`
on the HPA gets wiped on the next deploy, forcing a
re-patch-after-every-deploy loop in CI.

This PR adds a `hpa.behavior` fragment to the per-service config table,
deep-merged over the generated default. It's the first slice of jaseci-labs#7764 —
connection-heavy services (WebSockets, SSE) can now ask for a gentle
scale-down (e.g. at most 2 pods every 5 minutes) without imposing that
rate on every other service through the app-global
`autoscaler_cooldown`, and the override survives every subsequent
deploy.

```toml
[scale.microservices.services.chat_sv.hpa.behavior.scaleDown]
stabilizationWindowSeconds = 600
selectPolicy = "Min"
policies = [{ type = "Pods", value = 2, periodSeconds = 300 }]
```

- **`AutoscalerSpec`** (`autoscaler.jac`) gains a `behavior_overlay:
dict[str, any] = {}` field.
- **`Autoscaler._build_behavior`** deep-merges `behavior_overlay` over
the hardcoded default via the existing `merge_manifest_overlay` (same
semantics already used for `deployment_overlay`: maps merge recursively;
the `policies` list carries no `name` key, so it's replaced wholesale
rather than merged entry-by-entry, matching how you'd actually want to
specify a policy set). Both the HPA and KEDA engines pick this up for
free — they already route through the same `_build_behavior` call, so
this is a single-point change.
- **`ManifestBuilder._get_autoscaler_config`** (`manifest_builder.jac`)
resolves the fragment from the per-service `hpa.behavior` table.
- **`KubernetesTarget`**'s deploy loop (`target.jac`) threads it into
`AutoscalerSpec(...)` as `behavior_overlay=cfg.get("behavior", {})`.
- **Config schema** (`plugin_config.jac`) and the **K8s reference
table** (`runtime/docs.md`) document the new key.

`_build_behavior` resolves `merge_manifest_overlay` via a
function-scoped import rather than a top-level one.
`manifest_builder.jac` already imports constants from `autoscaler.jac`
at module level, so a top-level import in the other direction would be a
circular import; a deferred import inside the function (evaluated well
after both modules are fully loaded) is the same pattern already used
elsewhere in this codebase (e.g. `manifest_builder.jac`'s own
`_pod_entrypoint`) for exactly this situation.

- `service_overlay` / first-class `Service` `annotations` + gateway
`sessionAffinity` knobs
- PDB `min_available` and percentage (`"50%"`) support

Written test-first: 11 new tests across the files that already exercise
the touched code paths (`test_deployment_overlay.jac`,
`test_factories.jac`, `test_keda_autoscaler.jac`,
`test_memory_trigger_guard.jac`), confirmed red against the
pre-implementation tree, then green after:

- `_build_behavior` merge semantics in isolation: no-overlay default,
empty-overlay no-op, `scaleDown` override, `scaleUp` override, wholesale
(not per-item) `policies` replacement.
- Both engines apply the overlay: HPA's `_build_manifests`
(`spec.behavior`) and KEDA's `_build_manifests`
(`advanced.horizontalPodAutoscalerConfig.behavior`).
- Config plumbing: `_get_autoscaler_config` surfaces `hpa.behavior`,
defaults to `{}` when unset, doesn't leak across services, and
`hpa.enabled = false` still short-circuits before behavior is
considered.

Also verified against a real cluster, not just unit tests. Deployed the
`jac-shop` 3-service microservice fixture
(`jaclang/scale/tests/fixtures/k8s_e2e`) to a local `kind` cluster with
a `hpa.behavior.scaleDown` override configured on `products_app`, via
`jac start main.jac --scale`:

- **First deploy**: all 4 services (gateway + 3) reached Ready, and the
live `products-app-hpa` object's `spec.behavior.scaleDown` matched the
configured override exactly (`stabilizationWindowSeconds: 600`,
`selectPolicy: Min`, `policies: [{type: Pods, value: 2, periodSeconds:
300}]`), with `scaleUp` untouched at the generated default.
- **Redeploy**: re-ran `jac start main.jac --scale` against the same
cluster and diffed `products-app-hpa`'s `spec.behavior.scaleDown` before
and after — byte-identical. Confirms the actual pain point from the
issue: the override survives repeated applies instead of being reset to
the old hardcoded `{Percent, 50, 60s}` shape.

- [x] A per-service `hpa.behavior` fragment in `jac.toml` survives
repeated `jac start --scale` applies with no external patching.
- [x] Global `autoscaler_*` keys remain the defaults; per-service
fragment wins on merge.
- [x] Works for both `autoscaler_engine = "hpa"` and `"keda"`.
- [x] Config schema (`plugin_config.jac`) and docs updated;
overlay-merge unit tests added alongside the existing
`test_deployment_overlay.jac` patterns.

---------

Co-authored-by: Kugesan Sivasothynathan <153247429+kugesan1105@users.noreply.github.com>
Co-authored-by: Kugesan Sivasothynathan <kugesan.sivasothynathan@jaseci.org>
(cherry picked from commit 91c7adc)
…ewed YAML matches what a deploy applies (jaseci-labs#8008)

`--show-yaml` printed pvcs, deployments, services, pdbs and ingress but
dropped autoscalers, while the totals line counted them and the service
view rendered HPA details from them. The reviewed YAML was not the
applied
artifact, for exactly the resources most likely to cause a production
surprise.

The bundle's `autoscalers` key holds CONFIGS, not manifests; the real
HPA/ScaledObject is synthesized only inside the apply loop. Adding the
key
to the flatten list would therefore have dumped config dicts. Instead:

- `deploy/target/kubernetes/target.jac`: the AutoscalerSpec construction
is
extracted out of the apply loop into `_autoscaler_spec` (single source),
and a new `render_autoscaler_manifests(bundle)` builds the same objects
  the apply path would, via each engine's `_build_manifests`. Pure - no
  Kubernetes API calls.
- `runtime/cli/plan.jac`: `Plan.from_target` stashes the rendered
manifests
  into the bundle; `_flatten_manifests` emits them into --show-yaml.

- New test_show_yaml_autoscalers (3): real KubernetesTarget with the HPA
and KEDA engines; asserts kind, scaleTargetRef, min/max mapping, and
that
  KEDA's ScaledObject body (not its name/body wrapper) is rendered.
- test_plan extended (+2): autoscaler manifests reach the YAML dump,
plus a
  sentinel test asserting every manifest-bearing bundle key reaches
  --show-yaml (the issue's drift-proofing suggestion).
- jac-ec2 rig, linked-source dev binary on this branch: all passing -
test_show_yaml_autoscalers (3, new), test_plan (19, includes the 2 new),
test_factories (28), test_keda_autoscaler (34),
test_memory_trigger_guard
  (8), test_dry_run_purity (12).
- NOT run: a live cluster apply of HPA/KEDA objects (the render path is
pure; the apply path is unchanged except for the spec extraction). Full
  repo suite not run locally.

- KEDA TriggerAuthentication objects are applied but still not rendered
into --show-yaml (built inline in `_apply_trigger_auth`; needs the same
  build/apply split).
- Normalize the `_build_manifests` contract: KEDA returns a {name, body}
  wrapper, HPA returns the bare manifest.

Closes jaseci-labs#7915. Part of jaseci-labs#7900.

---------

Co-authored-by: Thamirawaran Sathiyalogeswaran <107134124+Thamirawaran@users.noreply.github.com>
(cherry picked from commit 9d2cace)
…aml (jaseci-labs#8424)

Closes jaseci-labs#8409.

`jac start --scale --show-yaml` (via `render_autoscaler_manifests`)
prints
the autoscaler objects a deploy would apply, but for KEDA it only ever
printed the `ScaledObject`. A trigger with `auth.secret_refs` also
causes
`apply()` to create a `TriggerAuthentication`, and that half of the
footprint was invisible to the dry-run preview. This PR unifies the
`_build_manifests` contract across both autoscaler engines so the render
path renders the same objects `apply()` creates, with nothing built as a
side effect of a live API call.

`_build_manifests` for KEDA only ever constructed the `ScaledObject`
body.
The `TriggerAuthentication` for an authenticated trigger was built and
written to the cluster exclusively inside `KEDAAutoscaler.apply` via
`_apply_trigger_auth`, a function that conflated *building* the manifest
with *reconciling* it against the cluster (get → patch-or-create) in one
step. Because the manifest never existed as a standalone, returnable
value,
`render_autoscaler_manifests` (the `--show-yaml` path) had no way to see
it.

A secondary wart compounded this: `_build_manifests` returned a
different
shape per engine (HPA a bare manifest dict, KEDA a `{"name", "body"}`
wrapper), so the render path added defensive shape-sniffing
(`built["body"] if "body" in built else built`) instead of a real
contract.
That sniff is what let the KEDA gap go unnoticed - it happily returned
"a manifest" without anyone checking whether it was the *only* manifest
KEDA needed to emit.

Net effect: a KEDA autoscaler config with an auth-bearing trigger
dry-ran
clean (N ScaledObjects, 0 TriggerAuthentications) and then `apply()`
silently created N ScaledObjects **and** M TriggerAuthentications the
preview never showed - exactly the class of surprise `--show-yaml`
exists
to prevent, and the same shape of gap jaseci-labs#8008 already fixed once for
autoscalers generally.

`_build_manifests` now returns one `list[dict]` contract for both
engines:

- `HPAAutoscaler._build_manifests` returns `[hpa]`.
- `KEDAAutoscaler._build_manifests` returns
  `[scaled_object, *trigger_authentications]`.

To make that possible without duplicating logic between the render and
apply paths:

- The `TriggerAuthentication` body construction is extracted out of the
old
  apply-only `_apply_trigger_auth` into a pure builder,
  `_build_trigger_auth_manifest`, which returns the manifest (or `None`
  when a trigger has no auth) and makes no API calls.
- A shared `_apply_custom_object` reconciler (get → patch-or-create)
  replaces the two nearly-identical get/patch/create blocks that
  previously existed separately for `TriggerAuthentication` and
  `ScaledObject` writes in `KEDAAutoscaler.apply`.
- `render_autoscaler_manifests` (`target.jac`) now does
`out.extend(autoscaler._build_manifests(...))` instead of shape-sniffing
a single object out of the return value, so it renders every manifest an
  engine produces, not just the first one.

Apply-time ordering is unchanged: `TriggerAuthentication` objects are
still
written before the `ScaledObject` that references them via
`authenticationRef`, since KEDA's admission webhook needs the referenced
auth resource to exist first.

| File | Change |
|---|---|
| `jac/jaclang/scale/deploy/autoscale/keda_autoscaler.jac` | Interface
updates: `_build_manifests` returns `list[dict[str, any]]`; new
`_build_trigger_auth_manifest` and `_apply_custom_object` declarations
replace `_apply_trigger_auth`. |
| `jac/jaclang/scale/deploy/autoscale/keda_autoscaler.impl.jac` |
`_build_manifests` assembles `[scaled_object, *trigger_auths]`;
trigger-auth body construction moved into pure
`_build_trigger_auth_manifest`; new `_apply_custom_object` reconciler;
`apply()` applies trigger-auth manifests before the ScaledObject via the
shared reconciler. |
| `jac/jaclang/scale/deploy/autoscale/hpa_autoscaler.jac` |
`_build_manifests` interface updated to `list[dict[str, any]]`. |
| `jac/jaclang/scale/deploy/autoscale/hpa_autoscaler.impl.jac` |
`_build_manifests` wraps its single manifest in a list; `apply()`
unwraps `[0]`. |
| `jac/jaclang/scale/deploy/target/kubernetes/target.jac` |
`render_autoscaler_manifests` flattens the manifest list from each
engine instead of shape-sniffing a single object. |
| `jac/jaclang/scale/tests/deploy/test_factories.jac` | HPA
`_build_manifests` assertions updated to the list contract. |
| `jac/jaclang/scale/tests/deploy/test_keda_autoscaler.jac` | KEDA
`_build_manifests` assertions updated to the list contract (ScaledObject
at index 0, no more `name`/`body` wrapper). |
| `jac/jaclang/scale/tests/deploy/test_show_yaml_autoscalers.jac` | New
regression test: an authenticated KEDA trigger renders both the
`ScaledObject` and its matching `TriggerAuthentication` through
`render_autoscaler_manifests`. |

Ran the full `deploy` test group locally with `jac test` (one process
per
file, matching how CI's `test-scale (deploy)` matrix leg runs it): all
27
files, 293 tests pass, both before-vs-after on the three files this PR
touches and as a full-group run afterward to catch cross-file fallout.

- `test_show_yaml_autoscalers` (4, 1 new) - the regression test for this
  issue. Builds a bundle with one KEDA-engine service carrying an
  authenticated `redis` trigger (`auth.secret_refs`) and asserts
  `render_autoscaler_manifests` returns exactly two manifests: the
`ScaledObject` at index 0 and a `TriggerAuthentication` at index 1 whose
`metadata.name` matches the `authenticationRef.name` KEDA embedded in
the
ScaledObject's trigger, with the `secretTargetRef` mapped correctly from
config. Confirmed this test fails against the pre-fix code (dropping the
  `_build_trigger_auth_manifest` loop reproduces the original bug -
  `len(manifests) == 1`) and passes with the fix. The other two existing
  tests in this file (HPA render, empty-bundle render) are unchanged and
  still pass, guarding against a regression in the ordinary case.
- `test_keda_autoscaler` (34) - full suite for the engine this PR
changes
most: `_build_manifests` shape and field mapping (9 tests, now indexing
  `[0]` into the list), trigger-authentication naming/collision rules,
  `preflight`, and - the part most likely to break silently on a
  build/apply split - `apply()`'s behavior end to end against a mocked
  `CustomObjectsApi`: that a `TriggerAuthentication` is still created
  before its `ScaledObject`, that the name written to the cluster still
  matches the `authenticationRef` the ScaledObject carries, and that a
KEDA-CRDs-not-installed cluster still short-circuits before either
write.
  All pass unmodified in assertion logic (only the `_build_manifests`
  indexing changed), which is the signal that `apply()`'s observable
  behavior toward the cluster is unchanged by the refactor.
- `test_factories` (32) - HPA `_build_manifests` output (exact-dict
  equality against the expected manifest, and the `behavior_overlay`
  merge) re-verified against the new `[hpa]` list contract.

Also validated end to end against a real cluster, not just mocks:

- **Cluster**: local `kind` (v0.32.0) with KEDA core installed via the
standard Helm chart (`kedacore/keda`, namespace `keda`);
`scaledobjects.keda.sh`
  and `triggerauthentications.keda.sh` CRDs confirmed present. A static
`hostPath` PV under a `no-provisioner` StorageClass (`jac-rwx`) stands
in
  for the bundle PVC's `ReadWriteMany` requirement, the same technique
  `jac/jaclang/scale/scripts/e2e_lib.sh`'s `provision_kind_rwx_storage`
uses for the kind-based CI e2e leg (kind's `local-path` StorageClass is
  RWO-only; a static hostPath PV is safe on a single-node cluster).
- **Project**: a minimal scratch app (`main.jac`, one walker, no client)
with
`[dev] jaclang_source` pointing at this checkout, so both the CLI and
the
  deployed pods run the fixed source, not a published release binary.
  `[scale.kubernetes] autoscaler_engine = "keda"` plus one
  `[[scale.microservices.services.<name>.triggers]]` entry with
`type = "redis"` and `auth.secret_refs` - the exact shape jaseci-labs#8409
described.
- **Dry-run** (`jac start --scale --dry-run --show-yaml`, no cluster
contact): the rendered YAML stream includes both the `ScaledObject` and
a
  `TriggerAuthentication` object, with the `ScaledObject`'s
  `authenticationRef.name` matching the `TriggerAuthentication`'s
`metadata.name` exactly (`kedaval-7dcd81a6-ta`) - this is the bug fixed,
  observed through the real CLI rather than only through the test suite.
- **Live apply** (`jac start --scale`): the same `TriggerAuthentication`
(`kedaval-7dcd81a6-ta`, `SECRET: redis-secret`) is present on the
cluster
  via `kubectl get triggerauthentications`, and the `ScaledObject`'s
  `AUTHENTICATIONS` column names it - dry-run and `apply()` now agree,
  which is the whole point of the fix. `kedaval-scaledobject` reports
`READY=False`/`ACTIVE=Unknown` only because no real Redis exists at the
  configured address; that's expected and irrelevant to what's being
validated here (the auth object's existence and wiring, not live
scaling).
- **App health**: both `gateway-deployment` and `kedaval-deployment`
reached
`1/1 Running`, the fleet rollout reported `2/2 deployments serving`, and
`/health` on the gateway reported both services `"status": "healthy"` -
  confirming the fix doesn't regress the ordinary deploy path.

Configure a KEDA autoscaler with an auth-bearing trigger and run
`jac start --scale --show-yaml`: both the `ScaledObject` and its
`TriggerAuthentication` now render, matching what `apply()` creates.

---------

Co-authored-by: Kugesan Sivasothynathan <kugesan.sivasothynathan@jaseci.org>
Co-authored-by: Kugesan Sivasothynathan <153247429+kugesan1105@users.noreply.github.com>
(cherry picked from commit b908596)
…bel (part 2 - final - of issue jaseci-labs#6535) (jaseci-labs#6908)

## Problem

`KEDAAutoscaler` orphaned `TriggerAuthentication` resources in two
distinct ways:

1. **Redeploy with a changed trigger set.** TA names are derived from
the trigger identity (`app + trigger-key`, hashed). When a redeploy
renames, removes, or reorders triggers, `apply()` creates new TAs under
new names and re-points the ScaledObject at them, but never deletes the
TAs from the previous config. `apply()` had no prune step, so every such
redeploy left a stale TA behind, unreferenced by the ScaledObject.

2. **destroy().** `destroy()` deleted only the ScaledObject, leaving
every TA its `apply()` created behind.

The earlier approach tried to fix (2) by reading
`authenticationRef.name` off the live ScaledObject. That could not fix
(1) at all (the stale TA is no longer referenced by the ScaledObject, so
it is never found), and it left a hole in (2): if the ScaledObject was
already gone, the GET returned 404 and `destroy()` returned early,
stranding any TAs permanently.

## Root cause

There was no ownership-based reconciliation. TAs are children whose
lifecycle should be bound to the autoscaler, but the code bound them
only by name derivation plus an imperative "read the ScaledObject,
delete what it currently points at". Nothing ever reconciled the actual
set of managed TAs in the namespace against the desired set. Compounding
this, TAs were stamped only with `managed=jac-scale` and no owner
scoping, so they could not even be listed per-autoscaler.

## Fix

Reconcile TAs by an ownership label instead of by parsing the
ScaledObject.

- `_build_trigger_auth_manifest()` stamps every managed TA with
`jac-scale/owner=<ScaledObject name>` (plus `app` and `managed`). The
owner value is the ScaledObject name, hashed if it exceeds the 63-char
label limit, via a shared `_owner_label_value()` helper so the apply and
destroy selectors always agree. Because the labels live in the manifest
builder, `--show-yaml` renders exactly what `apply()` writes.
- `apply()` collects the desired TA names, then
`_prune_orphan_trigger_auths()` lists this owner's managed TAs by label
and deletes any not in the freshly-applied set. This closes orphan
pathway (1).
- `destroy()` lists TAs by `jac-scale/owner=<name>,managed=jac-scale`
and deletes them (404/422-safe), then deletes the ScaledObject. It no
longer reads the ScaledObject, so cleanup works even when the
ScaledObject was already deleted out of band. This closes orphan pathway
(2) with no early-return hole.
- `destroy()` (base `Autoscaler`, `HPAAutoscaler`, `KEDAAutoscaler`) now
takes `app_name` instead of a raw resource name and resolves the
concrete name internally via `resource_name_for()`, matching how the
owner label selector is computed. A single `_scaled_object_name(spec,
app_name)` helper is the one definition of the ScaledObject name, used
by `apply()` and `_build_manifests()`; `destroy()`'s formula is exactly
its fallback branch, so the two cannot disagree.

```
apply(spec):
  build manifests (validation runs inside the builder)
  for each manifest in _APPLY_ORDER: create/patch (TAs labelled owner=<SO name>, then the ScaledObject)
  prune: list TA by owner label -> delete any name not in desired set

destroy(app_name, namespace):
  scaled_object_name = resource_name_for(app_name)
  list TA by owner label=scaled_object_name -> delete each (404-safe)
  delete ScaledObject named scaled_object_name (404-safe)
```

## Migration for TAs that predate the owner label

There is deliberately no legacy name-pattern sweep (an earlier revision
had one; review showed its truncated-prefix match could cross-delete
another app's TAs in a shared namespace, so it was removed rather than
narrowed). Instead:

- A pre-label TA whose trigger still exists is adopted implicitly:
`apply()` patches it by name and the merge patch adds the owner label.
From then on it is fully reconciled.
- A pre-label TA whose trigger was already removed cannot be named from
`spec.triggers`, so nothing touches it; the namespace-wide
`destroy_collection()` sweep (`managed=jac-scale`) remains the catch-all
for those.

## Tests

`test_keda_autoscaler.jac`, all mocked, 42 tests including:

- TriggerAuthentication manifest carries app and owner labels for
reconciliation
- apply prunes a TriggerAuthentication the redeployed trigger set no
longer references, and keeps one still referenced
- destroy resolves the ScaledObject name from app_name and deletes it
- destroy sweeps TriggerAuthentications owned by this ScaledObject
before deleting it (works with the ScaledObject already gone, the case
the earlier patch could not handle)
- destroy tolerates an already-deleted TA (404) and an absent auth CRD
(404 on list)
- duplicate trigger identities are rejected by the manifest builder, so
`--show-yaml` refuses what `apply()` refuses

`test_show_yaml_autoscalers.jac` covers the rendered TA manifests
carrying the same labels `apply()` writes. `test_factories.jac` green.

## Live cluster validation

Verified against a real cluster with real KEDA CRDs, driving this
branch's `KEDAAutoscaler.apply()`/`destroy()` directly:

| Step | Action | Result |
| --- | --- | --- |
| apply | trigger `cache` + auth | TA created with `jac-scale/owner`,
`app`, `managed` labels |
| apply prune | rename trigger `cache` to `queue` | old TA pruned, only
the new TA remains |
| destroy | destroy with ScaledObject present | 0 TAs, 0 ScaledObjects |
| destroy (SO gone) | delete ScaledObject out of band, then destroy | TA
still swept by label |
| migration | pre-label TA, trigger still live | adopted by apply's
patch, swept by destroy |

Both orphan pathways are provably closed on a real cluster.

## Notes

Rebased on the merged jaseci-labs#8424: trigger validation lives in
`_build_manifests`, so the render and apply paths accept and refuse
identical specs, and the manifest list is the single desired-state
description that both paths share. This PR supersedes the earlier
ScaledObject-parsing approach and resolves the partial-apply /
already-gone-ScaledObject limitation tracked in jaseci-labs#6909 directly: cleanup
is now independent of the ScaledObject.

Closes jaseci-labs#6535.
Closes jaseci-labs#6909.

---------

Co-authored-by: Kugesan Sivasothynathan <kugesan.sivasothynathan@jaseci.org>
Co-authored-by: Kugesan Sivasothynathan <153247429+kugesan1105@users.noreply.github.com>
(cherry picked from commit e092aaa)
…jac.toml config (jaseci-labs#7709)

Closes jaseci-labs#7475

PR jaseci-labs#7421 added a programmatic API for KEDA HTTP Add-on scale-to-zero
activation (`HTTPActivationSpec`, `KEDAAutoscaler.apply_http_activation`
/ `destroy_http_activation`), but left it reachable only by writing a
Jac driver script. Every other autoscaler knob in jac-scale is
configured declaratively through `jac.toml`, so this PR adds that same
entry point for HTTP activation, for both the monolith deploy path and
per-service microservice deploys.

The programmatic API from jaseci-labs#7421 is unchanged and remains the right tool
for callers with a dynamic, create/destroy-on-demand lifecycle (for
example, an IDE preview orchestrator). The two entry points are
additive, not a replacement of one by the other: `jac.toml` covers a
known, standing service; the programmatic API covers a workload created
and torn down at runtime.

**One mutually-exclusive scaling decision per service, resolved at build
time.** `manifest_builder` resolves each service (the gateway included)
to exactly one scaling mode - `none`, `metric` (HPA/KEDA), or
`http_activation` - computed once and carried through the bundle as a
single `scaling` map, replacing two independent resolvers (autoscaler
config and http_activation config) that could both fire on the same
Deployment. Three consequences, each of which was a real apply-side
defect before:

- **Correct scale target.** The `ScaledObject`'s `scaleTargetRef`, and
the `InterceptorRoute`/`ScaledObject` resource names derived from it,
come from the built Deployment manifest (`<name>-deployment`), never
re-derived from the bare service name. A bare-name target made
`apply_http_activation`'s existence check 404 on a real cluster, killing
the deploy.
- **No dual scalers.** A service gets a metric autoscaler or an HTTP
`ScaledObject`, never both; the modes are mutually exclusive by
construction, so KEDA's admission webhook can no longer reject a second
`ScaledObject` on an HPA-managed target.
- **The gateway never scales to zero.** It is the ingress entry point
and must stay warm, so it is structurally excluded from HTTP activation
and never inherits a shared `enabled = true`; it falls through to a
normal HPA.

**Teardown sweeps KEDA resources regardless of the base engine.**
`destroy()`'s KEDA sweep previously went through
`AutoscalerFactory.create(self.k8s_config.autoscaler_engine, ...)`, so
when `autoscaler_engine = "hpa"` it resolved to
`HPAAutoscaler.destroy_collection`, which does not know about
`InterceptorRoute` or HTTP-activation `ScaledObject` resources,
orphaning them on teardown. `destroy()` now also runs a
`KEDAAutoscaler(...).destroy_collection(...)` sweep whenever
`autoscaler_engine != "keda"`, since HTTP activation needs the KEDA HTTP
Add-on regardless of the base engine.

**Teardown tolerates the HTTP Add-on's separate RBAC surface, without
masking other failures.** The HTTP Add-on's CRD group (`http.keda.sh`,
covering `InterceptorRoute`) installs and is permissioned separately
from core KEDA's `keda.sh` group (`ScaledObject`,
`TriggerAuthentication`), which the base autoscaler already needs. A
cluster's RBAC can easily cover one without the other. Both
`destroy_http_activation` and `destroy_collection` were changed so a 403
on the `http.keda.sh` side never aborts cleanup of the `keda.sh` side
(or vice versa for `destroy_collection`, which still requires 404/422
there), both resources are always attempted regardless of the other's
outcome, and if one side hits a genuinely unexpected status (500, for
example) that is what propagates, not a tolerable 403 that happened to
be checked second. This closes a class of gaps found in review: a single
unhandled 403 previously could abort the rest of monolith or
microservice teardown (Deployment, Service, PVC, ingress, monitoring,
database cleanup) before it ran.

**Per-service config inherits from the shared default, except the switch
itself and the gateway.** A per-service
`[scale.microservices.services.NAME.http_activation]` block merges over
`[scale.kubernetes.http_activation]` as a base, so a service that only
sets `target_port` still picks up a shared `concurrency_target` from the
top level, and a service with no local block at all still inherits a
top-level `enabled = true`. An explicit per-service `enabled = false`
still overrides that default, so a shared "on" switch can be selectively
opted out of per service. The gateway is the one service that never
inherits the shared switch (see the scaling-decision point above).

**`jac plan` surfaces HTTP-activated services.** The dry-run plan reads
the same `scaling` map, so an http_activation service renders its `HTTP
activation: 0 -> N` line and appears in the Totals, instead of being
invisible in the plan.

**A redeploy no longer resets a scaled-to-zero target back to its
baseline.** The Deployment manifest's `spec.replicas` is always the
configured static default, and every apply PATCHes it onto the live
Deployment - with no exclusion for HTTP-activation-mode services, that
undid KEDA's own scale-to-zero on every redeploy. `_apply_or_replace`
now takes an optional `patch_body` distinct from the manifest used for a
fresh create, and the deployment-apply loop strips `replicas` from the
PATCH body (not the CREATE body) for `HTTP_ACTIVATION`-mode services, so
KEDA's current value is left alone.

**No PodDisruptionBudget for an HTTP-activated service, and `jac plan`
stops flagging it.** `_build_pdb_manifest` used to compute a replica
floor from `hpa.min` regardless of mode, so an HTTP-activated service
could still get a PDB with `min_available` set - a budget that's
permanently unsatisfiable once the target is actually at zero replicas.
It now takes the resolved mode and skips PDB generation entirely for
`HTTP_ACTIVATION` services (warning if the user had `pdb` settings
configured). `PlanValidator._check_hpa`/`_check_pdb` had the same blind
spot for `jac plan`'s diagnostics and now skip both checks for
`HTTP_ACTIVATION` services too.

**Two services can't silently collide on the same HTTP-activation
traffic.** `build_http_activation_spec` now rejects an empty `rules`
list at build time - the docs already said an empty list means no
traffic ever matches, so a service left at the default was silently
deploying unreachable. Separately, `generate_manifests` now runs
`_validate_http_activation_rules`, which rejects two services that
resolve to identical `InterceptorRoute` match criteria (for example, two
services that both inherit the shared top-level rules unchanged) - the
KEDA HTTP Add-on interceptor has no way to tell which target a matching
request belongs to, so this is now a build-time error naming the
colliding services, not a runtime routing ambiguity.

**Mode transitions reap the old resource before applying the new one.**
`apply_manifests` used to apply the new mode's scaling resource (metric
autoscaler, or the HTTP `ScaledObject`) before calling
`reap_stale_scaling_resources_for_bundle`, which tears down the previous
mode's leftover resource. A service switching modes on a given redeploy
collided with its own stale resource - KEDA's admission webhook rejects
a second `ScaledObject` on an already-managed target - and only got
cleaned up on the next deploy. The reap call now runs before both
applies.

**A deploy bundle that's entirely HTTP-activation-scaled no longer
crashes.** `_wait_for_fleet_rollout` called `self._load_kube_config()`,
a method that doesn't exist anywhere in this codebase - the real method
is `_load_cluster_config()`. This crashed with an `AttributeError` in
exactly the case the surrounding code path exists to handle: a fleet
with no metric-scaled services to wait on.

**Traffic routing to the interceptor is not wired yet (documented
limitation).** jac-scale creates the `InterceptorRoute` and
`ScaledObject`, but does not yet rewire the gateway or Ingress backend
to the KEDA HTTP interceptor proxy. With `min_replicas = 0`, inbound
traffic that reaches the app Service directly (instead of the
interceptor) will not wake the pod. The reference doc carries a warning
to route through the interceptor yourself for now; the routing-plane
rewire is tracked separately as jaseci-labs#7959.

**Namespace-wide sweeps still lack application-identity scoping.** Every
resource-kind sweep in `destroy()` (Deployments, Services, PDBs,
Ingresses, ConfigMaps, and the KEDA resources above) uses a
namespace-wide `managed=jac-scale` label selector with no
application-identity component, so two jac-scale applications sharing
one namespace would delete each other's resources on teardown. This is a
pre-existing characteristic of the whole method, not something this PR
introduces; fixing it properly requires an app-identity label added at
manifest-build time across several files. Tracked separately as jaseci-labs#7710
rather than patched partially here.

- `test_http_activation_config.jac` (12 tests): config-to-spec
translation (disabled/absent, minimal config defaults, one full-config
test covering every optional field, and the four jac.toml-relative
validation errors - `target_port`/`target_port_name` mutual exclusivity,
a missing `concurrency_target`/`request_rate_target`, empty `rules`, and
a rule header missing `name`), the injectable apply/destroy wiring
helpers, and that only a 403 from `destroy_http_activation` is caught (a
500 still propagates). Uses a shared `_assert_config_error(cfg,
*substrings)` helper instead of hand-rolled raise/catch/assert blocks
per test.
- `test_http_activation_microservices.jac` (17 tests): the per-service
config pass-through and top-level inheritance (including `enabled`
itself, and an explicit per-service opt-out); the single scaling
decision in `_resolve_scaling` - that an http_activation service
resolves to `HTTP_ACTIVATION` mode carrying the `<name>-deployment`
scale target and emitting no metric autoscaler, that the gateway is
never HTTP-activated even under a shared default (exercised with a real
logger so a wrong log-method name is caught without a live deploy), and
that a plain service resolves to `METRIC`; the per-service apply loop
(only HTTP_ACTIVATION-mode services are applied, using the carried
scale-target and service names); the mode-transition reap tests (a
METRIC service has its stale HTTP activation resources reaped and its
autoscaler left alone, and vice versa, a NONE service has both reaped,
and an empty bundle is a no-op); and the cross-service rule-collision
validation (identical rules raise, distinct/empty/lone-peer rules don't,
and `generate_manifests` raises when two services inherit the shared
catch-all rule unchanged).
- `test_keda_http_activation.jac` (46 tests): imports the shared
fixtures instead of defining them locally, plus the
`destroy_http_activation`/`destroy_collection` error-handling coverage -
that `destroy_http_activation` still attempts the `ScaledObject` delete
after a 403 on the `InterceptorRoute` delete; that a 500 on one delete
is not masked by a 403 on the other; that `destroy_collection` tolerates
a 403 listing `InterceptorRoute`s without aborting the
`ScaledObject`/`TriggerAuthentication` sweep; and that a 403 listing
`ScaledObject`s still raises.
- `test_plan.jac` (23 tests): includes a case asserting an
http_activation service renders its scale-to-zero line and counts in
Totals, with no HPA line, and that the metric-autoscaler smoke test
reads the new `scaling` bundle key.
- `test_pdb_budget.jac` (11 tests): the existing PDB floor/emission
cases, plus that an HTTP-activated service gets no PDB even with
`pdb.min_available` set, and that the plan validator skips both the HPA
and PDB checks for an `HTTP_ACTIVATION` service.
- `test_show_yaml_autoscalers.jac` (5 tests): `jac plan --show-yaml`
renders the HPA/`ScaledObject` body the apply path would build, that the
scale target comes from the resolved plan rather than being re-derived
from the service name, that an http_activation-scaled service renders no
autoscaler manifest, and that a bundle with no scaling renders nothing.
- `http_activation_test_support.jac`: shared fixtures
(`default_http_activation_spec`, `default_http_activation_config`,
`mocked_keda_autoscaler`) extracted from `test_keda_http_activation.jac`
so both files use one source of truth instead of duplicating mock setup.

The HTTP activation, KEDA autoscaler, plan, memory-trigger-guard,
deployment-overlay, dry-run-purity, ms-config-isolation, and
superseded-deployment-reap suites all pass under `jac test`.

The microservices `destroy()` engine-gap wiring itself has no dedicated
test: like the rest of that method, it requires a live cluster
(`_load_cluster_config`, real client construction) with no existing
unit-test precedent in this codebase. The error-handling logic it
depends on (`destroy_collection`'s group-aware tolerance) is unit-tested
directly, as listed above. It was, however, exercised end to end on a
real cluster (see Notes to Reviewer).

| File | What Changed |
|---|---|
| `jac/jaclang/scale/deploy/autoscale/http_activation_config.jac` | New.
`build_http_activation_spec` translates a `[*.http_activation]` config
dict into an `HTTPActivationSpec`, with jac.toml-relative validation
errors, including an empty-`rules` check and a friendly error (instead
of a bare `KeyError`) for a rule header missing its required `name` key.
`apply_http_activation_for_target` /
`destroy_http_activation_for_target` wrap the `KEDAAutoscaler` calls
behind an injectable interface, shared by both deploy paths below.
`destroy_http_activation_for_target` is unconditional (no config gate,
so a resource created while enabled stays cleanable even if disabled
later) and swallows only a 403 from `destroy_http_activation` (via
null-safe `e?.status`, since the checker can't prove `ApiException`
isn't the `_optdeps` fallback `Exception`), re-raising anything else. |
| `jac/jaclang/scale/deploy/autoscale/keda_autoscaler.impl.jac` |
`destroy_http_activation` now always attempts both the
`InterceptorRoute` and `ScaledObject` deletes regardless of the other's
outcome, and prefers surfacing a non-403 error over a 403 if both fail
differently. `destroy_collection` now tolerates 403 specifically for the
`http.keda.sh` group, leaving `keda.sh` groups at the existing strict
404/422 tolerance. |
| `jac/jaclang/scale/deploy/autoscale/keda_autoscaler.jac` |
`destroy_collection`'s interface gains a `best_effort: bool = False`
parameter for the group-aware 403 tolerance above. |
| `jac/jaclang/scale/deploy/autoscale/http_activation.jac` | Renames
`HTTPRequestRateMetric.window` to `rate_window` to match the config
schema and generated YAML field name. |
| `jac/jaclang/scale/config/plugin_config.jac` | New
`[scale.kubernetes.http_activation]` schema block (master switch,
replica bounds, target port, concurrency/request-rate metrics, routing
rules, cold start, timeouts, custom scale target). Documents the
per-service `[scale.microservices.services.NAME.http_activation]`
override, which falls back to the top-level block. The ~20-field nested
schema is now built once by `_http_activation_nested_schema()` and
shared by both call sites instead of being duplicated verbatim (the two
copies had already drifted: the `rules` description claimed "empty
matches all traffic," the opposite of the now-enforced behavior). |
| `jac/jaclang/scale/deploy/target/kubernetes/kubernetes_config.jac` |
New `http_activation: dict[str, any]` field on `KubernetesConfig`, wired
into `to_dict()` / `from_dict()`. |
| `jac/jaclang/scale/deploy/target/kubernetes/kubernetes_target.jac` |
Monolith teardown (`_destroy_application`) calls
`destroy_http_activation_for_target` next to the base-autoscaler
destroy, with the engine-gap sweep described above. Also removes
`KubernetesTargetBase.destroy`, `_destroy_component`,
`_destroy_application`, `_destroy_databases`/`_destroy_database`, and
`_wait_for_deletion` (about 360 lines): all were shadowed by
`KubernetesTarget`'s own overrides for every real deploy and unreachable
in practice. Note for future readers: `_destroy_component` was the
reference implementation jaseci-labs#8403 and jaseci-labs#7968 point at for full-teardown
component dispatch. After this merges, addressing either issue means
writing that dispatch logic fresh in `target.jac` rather than reusing
this block. |
| `jac/jaclang/scale/deploy/target/kubernetes/manifest_builder.jac` |
New `ScalingMode` enum and `_resolve_scaling(svc_name, deployment,
service)`: the single scaling decision, reading the scale-target name
off the built Deployment manifest, enforcing the gateway exclusion and
metric-vs-activation exclusivity. `_get_http_activation_config` merges
`[scale.kubernetes.http_activation]` as a base with the per-service
block. The build loop emits one `bundle["scaling"]` map (replacing the
separate `autoscalers`/`http_activations` keys). `generate_manifests`
now also calls `_validate_http_activation_rules` to reject cross-service
rule collisions, and `_build_pdb_manifest` takes the resolved mode and
skips PDB generation for `HTTP_ACTIVATION` services. |
| `jac/jaclang/scale/deploy/target/kubernetes/target.jac` |
`apply_http_activations_for_bundle`, the autoscaler apply loop, and the
post-deploy reachability-skip set all read `bundle["scaling"]` by mode,
using the carried scale-target/service names with no name re-derivation.
`destroy()` engine-gap fix (see Notable design points).
`apply_manifests` now reaps stale scaling resources before applying the
new mode's resource, `_apply_or_replace` takes an optional `patch_body`
so a redeploy's PATCH strips `replicas` for `HTTP_ACTIVATION`-mode
services instead of resetting KEDA's scale-to-zero, and
`_wait_for_fleet_rollout`'s crash on an all-activated fleet is fixed
(`_load_cluster_config`, not the nonexistent `_load_kube_config`). |
| `jac/jaclang/scale/runtime/cli/plan.jac` | Reads the `scaling` bundle
key; new `HTTPActivationView` rendered beside `HPAView`, and Totals
counts autoscalers and HTTP activations by mode.
`PlanValidator._check_hpa`/`_check_pdb` now read the resolved mode and
skip both checks for `HTTP_ACTIVATION` services. |
| `jac/jaclang/scale/tests/deploy/http_activation_test_support.jac` |
New. Shared fixtures (`default_http_activation_spec`,
`default_http_activation_config`, `mocked_keda_autoscaler`) extracted
from `test_keda_http_activation.jac`. |
| `jac/jaclang/scale/tests/deploy/test_http_activation_config.jac` |
Config-to-spec translation tests plus the injectable apply/destroy
wiring-helper tests (12 tests), using a shared `_assert_config_error`
helper. |
|
`jac/jaclang/scale/tests/deploy/test_http_activation_microservices.jac`
| New. Per-service config inheritance, the `_resolve_scaling` decision
(scale-target name, gateway exclusion, metric-vs-activation
exclusivity), the mode-transition reap tests, the apply-loop tests, and
the cross-service rule-collision validation (17 tests). |
| `jac/jaclang/scale/tests/deploy/test_keda_http_activation.jac` |
Imports shared fixtures instead of defining them locally, plus the
`destroy_http_activation`/`destroy_collection` error-handling tests (46
tests). |
| `jac/jaclang/scale/tests/microservices/test_plan.jac` | Reads the new
`scaling` bundle key; adds an http_activation render/Totals test (23
tests). |
| `jac/jaclang/scale/tests/deploy/test_pdb_budget.jac` | Adds coverage
that an `HTTP_ACTIVATION` service gets no PDB even with
`pdb.min_available` set, and that the plan validator skips both the HPA
and PDB checks for it (11 tests). |
| `jac/jaclang/scale/tests/deploy/test_show_yaml_autoscalers.jac` | Adds
coverage that `jac plan --show-yaml`'s scale target comes from the
resolved plan (not re-derived from the service name) and that an
http_activation-scaled service renders no autoscaler manifest (5 tests).
|
| `jac/jaclang/scale/runtime/cli/diagnostics.jac` |
`_check_hpa`/`_check_pdb` read the resolved scaling mode and skip both
checks for `HTTP_ACTIVATION` services. |
| `jac/jaclang/cli/docs/reference/plugins/jac-scale-kubernetes.md` |
Restores the "HTTP Add-on Activation" reference section with the new
config keys, both deploy-path examples, a note on the still-available
programmatic API, and a warning that inbound traffic must be routed
through the interceptor for now (jaseci-labs#7959). |
| `jac/jaclang/cli/docs/tutorials/production/kubernetes.md` | New "Scale
to zero on an HTTP request" subsection alongside the existing
autoscaling tutorial content, with a minimal `jac.toml` example and a
link to the full reference section. |
| `jac/jaclang/scale/tests/fixtures/keda_http_activation_e2e/` and
`jac/jaclang/scale/tests/deploy/keda_http_activation_real_e2e.sh` |
Real-cluster e2e fixture and driver, deployed through the `jac.toml`
wiring (replaces the old `fixture.yaml`-driven setup). The driver
resolves resource names from the unified deploy path's
`<app>-deployment` scale target. `keda_http_activation_verify.jac`, the
old standalone verify script, is removed - superseded by the
`jac.toml`-driven driver. |

The full flow was validated end to end on a local `kind` cluster (not
just unit tests):

- **Runtime channel.** The fixture `jac.toml` carried a `[dev]` stanza
(dev binary channel) with `[dev] jaclang_source` pointing at this
checkout, so `jac start --scale` ran this branch's deploy code host-side
(all manifest generation, the `kubectl apply` calls, and the
`InterceptorRoute`/`ScaledObject` reconcile happen in that process). The
confirming `jac dev mode - using compiler source at ...` banner appeared
on every deploy.
- **Cluster.** A single-node `kind` cluster with KEDA core and the KEDA
HTTP Add-on installed via helm. Leader election was disabled on the
control-plane components for the single-node dev cluster so the pod's
first-boot compile could not starve them.
- **Result.** `keda_http_activation_real_e2e.sh` PASSED: deploy,
idempotent redeploy (get-then-patch, no duplication), both
`InterceptorRoute` and `ScaledObject` reconciled to Ready, a request
through the interceptor cold-started the target from `0 -> 1` and
returned HTTP 200 with the walker's response, then the target scaled
back to `0` after the cooldown.
- **The three design fixes confirmed on the live cluster:** the
`ScaledObject`'s `scaleTargetRef` was `echo-deployment` (not the bare
name), the activated service had no metric HPA, and the gateway had a
normal HPA rather than a `ScaledObject`.

(cherry picked from commit 6ded655)
…a local stdlib copy during backport testing)
…ip jac install (jaseci-labs#8695)

A woken pod spent 21s of its 44s wake rebuilding /app/.jac/venv from the
bundle's own vendored wheels, identically every time, because /app is an
emptyDir and the .jab never carried the built venv. The dependency
closure is fully determined at seal time, so build it there instead:

- pack_jab now runs the seal binary's own 'jac install' inside the
  staging tree against the freshly vendored wheels (the exact install
  pods run today) and seals lib/*/site-packages plus a .jac-deps-hash
  marker into the .jab under .jac/venv/. The existing pvc-bootstrap
  untar populates the pod venv with zero pod-side script changes.
- install_specs returns early when the marker matches the resolved
  specs, before ensure_venv can classify the interpreter-less sealed
  venv as corrupted and delete it. Only pack_jab ever writes the
  marker, so local 'jac install' behavior is unchanged.
- The payload stays deterministic (guarded by the existing digest
  determinism test): bin/ shebangs, pyvenv.cfg, __pycache__/*.pyc and
  ensurepip's seeded pip never ship, and RECORD lines pointing outside
  site-packages are dropped.
- Staging failures (cross-arch hosts, thin bundles) log and fall back
  to today's boot-time install; JAC_SEAL_SKIP_VENV=1 opts out.
- Drive-by fix the e2e surfaced: vendor_wheels resolved the project
  config via discovery, which 'jac run' roots at the script's own tree,
  so any dev-source seal vendored the jac checkout's closure instead of
  the app's. It now loads the staged jac.toml directly.

(cherry picked from commit fd35eb8)
The 0.34 standalone install routes through PIP_PREFIX (as the pod
bootstrap does), so the stage exports it plus PIP_IGNORE_INSTALLED so a
seal host whose runtime site already satisfies specs cannot produce a
silently empty venv. Also removes a duplicate venv-stage hook the
cherry-pick auto-applied with main's argument names, and ports the e2e
tests to this line's _pod_binary()/pack_jab signature.
… the fleet wait

Measurement scaffolding standing in for jaseci-labs#8723: with a KEDA min-0
service whose trigger is idle at deploy time, the rollout gate waited
1200s for a deployment the autoscaler had deliberately parked at 0 and
failed the deploy (jacBuilder run 33836166656). jaseci-labs#8723's status model is
the real fix on main; this branch only needs the gate to pass so the
wake can be measured.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants