Skip to content

fix(plane-enterprise): align integration-event-consumer with webhook-consumer - #375

Open
Saurabhkmr98 wants to merge 43 commits into
feat-integration_event_publisherfrom
fix/integration-event-consumer-secrets
Open

Saurabhkmr98 wants to merge 43 commits into
feat-integration_event_publisherfrom
fix/integration-event-consumer-secrets

Conversation

@Saurabhkmr98

Copy link
Copy Markdown
Member

Stacked on #302. The consumer isn't on master yet, so this targets #302's branch. It brings the same fix as makeplane/helm-charts-private#762 to this chart.

Reviewing this PR

The diff is large because it includes a merge of master: #302 was 41 commits behind, and the helpers this fix relies on exist only on master. Review commit 22064b6, which is the actual fix. The merge commit resolves three conflicts:

  • Chart.yaml: master's appVersion, version 3.12.0
  • questions.yml: keeps both the consumer and mcp_server blocks
  • app-env.yaml: master's WEBHOOK_ALLOWED_HOSTS and token budget defaults, plus the INTEGRATION_EVENT_* keys

Problem

#302's template was written against an older master, and webhook-consumer has since moved on. As it stands, the consumer misses:

  • external_secrets.app_keys_existingSecret and silo_connectors_existingSecret
  • The runner key Secret
  • plane.infraCredsEnv, which delivers externally managed Postgres, RabbitMQ, Redis, OpenSearch and storage credentials
  • serviceAccount.name and the service account pod labels
  • plane.podAnnotations, because it still stamps a timestamp, which defeats global.deterministicRender
  • plane.rollingUpdateStrategy, plane.podSpread and PDB eligibility

Changes

  • Regenerated the template from master's webhook-consumer, so they differ only by service name
  • Registered the consumer in plane.pdbEligible
  • Added topologySpreadConstraints and podDisruptionBudget to its values

Testing

Rendered with alpine/helm:3.14.0, with global.deterministicRender on:

  • External Secrets: [SILO-1523] feat(plane-enterprise): add integration event stream env vars + integration-event-consumer deployment #302's head mounts app, doc-store, opensearch and silo. This branch adds runner, app-keys and silo-connectors, and its envFrom matches webhook-consumer apart from each service's own vars ConfigMap.
  • New keys: a custom serviceAccount.name, a PDB and a spread constraint all render on the consumer.
  • Consumer off, compared with master: the only differences are the new INTEGRATION_EVENT_* keys in app-vars and silo-vars, the helm.sh/chart label, config checksum annotations and hashed Job names.
  • helm lint passes.

Not deployed to a cluster.

Before releasing

🤖 Generated with Claude Code

pratapalakshmi and others added 30 commits August 31, 2026 13:24
…rom class name (#255)

* fix(plane-enterprise): decouple ingress controller type from class name

The ingress templates used `ingress.ingressClass` to decide both the
resource kind (standard Ingress vs Traefik IngressRoute) and the class
name string. As a result `ingress.yaml` rendered only for the exact
value `nginx` and `ingress-traefik.yaml` only for values prefixed
`traefik` — any other class name (a Traefik alias, or `nginx-new`)
matched neither template and the ingress was silently dropped.

Introduce `ingress.controller` to select the resource kind, decoupled
from the free-form class name. Only `traefik` selects the IngressRoute;
any other value selects a standard Ingress. Empty falls back to the
previous auto-detection from `ingressClass`, so existing installs are
unaffected.

Also fix a pre-existing latent bug in ingress.yaml where `len` of a nil
`ingress_annotations` threw a hard render error; switched to `with`.

Bumps chart 2.6.2 -> 2.6.3.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(plane-enterprise): drop Traefik-alias-on-standard-Ingress guidance

The ingress.controller feature stays (it implements the F5/nginx-new
request). Remove only the README/values examples about serving a
Traefik-named class alias via a standard Ingress, which is no longer a
needed use case.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(plane-enterprise): align Middleware gate + expose controller in questions.yml

- traefik-middleware.yaml: gate on plane.ingressController (matching
  ingress-traefik.yaml, licenseDomain condition included) instead of
  hasPrefix on ingressClass. Prevents a missing Middleware (and route
  admission failure) when controller=traefik with a non-traefik
  ingressClass, and avoids an orphan Middleware in the reverse case or
  when licenseDomain is unset.
- questions.yml: add ingress.controller so the Rancher form can reach the
  new value, describe what ingressClass now does, and default
  ingress.ingressClass to "traefik" to match values.yaml (the
  authoritative chart default).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(plane-enterprise): make the widened Ingress fallback opt-in

An upgrade-safety review of 3.5.2 -> 3.5.5 found the one case where this
branch was not inert. With ingress.controller empty, the widened fallback
in ingress.yaml rendered a <release>-ingress for any class outside
{traefik*, nginx, openshift} -- alb, contour, istio, gce, nginx-new,
openshift-default, a custom IngressClass name, an empty string. Those
operators get no ingress from 3.5.2 and therefore run one of their own, so
on upgrade they would get a second Ingress for the same host (a second ALB
on the AWS LB controller), or a failed upgrade if theirs is already named
<release>-ingress and Helm refuses to adopt it.

The fallback is now reached only when an operator sets ingress.controller.
With it empty the helper reproduces the pre-3.5.5 selection exactly,
including its case sensitivity, so every existing values file renders
byte-for-byte what it did before. The F5/nginx-new case the ticket asked
for still works via ingress.controller.

Also reverts the questions.yml ingress.ingressClass default to "nginx".
Defaulting the Rancher form to "traefik" would fail a new install on any
cluster without the Traefik CRDs; upgrades keep their stored answer either
way, so the change bought consistency with values.yaml at the cost of new
form installs.

Verified: rendered output is identical to 3.5.2 across 22 ingressClass
values plus ingress.enabled=false, an empty licenseDomain, and
examples/values-openshift.yaml.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs(plane-enterprise): add a values snippet for every ingress configuration

Ten copy-pasteable `ingress` blocks, split by whether they need the new
ingress.controller value:

- Already supported, controller omitted: Traefik IngressRoute (the default),
  standard Ingress on ingress-nginx, OpenShift Routes, and ingress.enabled
  false for bring-your-own.
- Newly possible with controller set: a class name that is not "nginx"
  (F5 NGINX, a second ingress-nginx install), AWS ALB, Contour/HAProxy/
  Kong/Istio/Cilium/GCE, Traefik behind a non-traefik class name, OpenShift
  Routes behind a non-openshift class name, and OpenShift's
  ingress-to-route conversion.

Each snippet states what it renders and carries the annotations that
controller actually needs. All ten were rendered with `helm template` and
the output checked against the documented result, annotations included.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* refactor(plane-enterprise): rename ingress.yaml to ingress-nginx.yaml

Names the standard-Ingress template after the controller it is for, matching
ingress-traefik.yaml and ingress-openshift.yaml, and scopes the docs to the
three supported configurations: nginx, Traefik and OpenShift.

- templates/ingress.yaml -> templates/ingress-nginx.yaml. Template filenames
  do not affect rendering; verified the output is unchanged.
- README: drop the AWS ALB and Contour/HAProxy/Kong/Istio/Cilium/GCE
  snippets and renumber the rest, so only nginx/Traefik/OpenShift are
  documented. The remaining eight snippets were re-extracted from the
  committed README and re-rendered.
- README/values.yaml/questions.yml: stop listing third-party controllers as
  ingress.controller options. The "any other value" behaviour is still
  documented, because that is what serves a non-"nginx" class name such as
  nginx-new, but it is no longer presented as a supported controller list.
- The unrecognised-class warning is kept and reworded to "any class other
  than nginx, openshift or traefik*" rather than naming specific
  controllers, so the silent no-op stays documented.

Verified: rendered output identical to 3.5.2 across 22 ingressClass values
plus ingress.enabled=false, an empty licenseDomain and
examples/values-openshift.yaml; resource ordering unchanged by the rename.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix formatting

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…305)

* feat(plane-enterprise): make silo init container image configurable

The silo deployment's two init containers (`wait-for-rabbitmq` and the
S3-CA-gated `prepare-ca-bundle`) hardcoded `busybox`, forcing an
unauthenticated Docker Hub pull. That blocks air-gapped installs and
private-registry mirroring, and is exposed to Docker Hub rate limits.

Add `services.silo.init_image`, following the `services.minio.init_image`
convention from #298, and document it in the README table and
questions.yml. Defaults to `busybox`, so rendered output is unchanged
unless overridden.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs(plane-enterprise): note command requirements for silo init_image

Address CodeRabbit review on #305: a custom init image must provide the
utilities the init scripts invoke (/bin/sh, grep, nslookup, sleep for
wait-for-rabbitmq; /bin/sh, ls, cat, touch for prepare-ca-bundle),
otherwise the affected init container fails and silo never starts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…306)

* feat(plane-enterprise): make live init container image configurable

The live deployment's `prepare-ca-bundle` init container hardcoded
`busybox`, forcing an unauthenticated Docker Hub pull. This container
only renders when a custom S3 CA is configured (`airgapped.enabled`
with an S3 CA secret) -- so the one init container specific to
air-gapped installs was itself unmirrorable.

Add `services.live.init_image`, following the `services.minio.init_image`
convention from #298, and document it in the README table and
questions.yml. Defaults to `busybox`, so rendered output is unchanged
unless overridden.

`services.live_exporter` needs no equivalent -- it has no init
containers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs(plane-enterprise): note command requirements for live init_image

Address CodeRabbit review on #306: a custom init image must provide the
utilities the prepare-ca-bundle script invokes (/bin/sh, ls, cat, touch),
otherwise the init container fails and live never starts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
fix(plane-enterprise): bump iframely v1.2.0 -> v2.5.4
…h zero-downtime rotation (#278)

* feat(plane-enterprise): consume credentials from external Secrets, with keyless cloud identity (3.6.2)

Rebased onto master, which has since gained opt-in OpenTelemetry (#248) and the v3.1.1 release.
The two touch the same regions of every workload — master adds OTel env, this branch adds
credential env — so the resolution keeps both: one `env:` per container, one guard carrying both
conditions, and OTel's envFrom entry back in the envFrom position where it belongs.

Verified rather than eyeballed. Every workload template parses; the default, all-services, OTel and
externalized-credentials renders all succeed; and both features coexist — the api container comes out
with OTEL_SERVICE_NAME=api, POSTGRES_PASSWORD from the operator's Secret, and otel-vars alongside the
credential Secrets in envFrom. Resolved environments are identical to pre-rebase for all 22
containers except APP_VERSION, which moves 3.1.0 -> 3.1.1 because that is master's release.
hack/assert-secrets.py --no-dsn still passes.

What this branch does, in the order it was built:

  - Credentials come from Secrets the operator owns, as discrete parts rather than a DSN, so a
    rotated password can actually reach the app. Postgres, RabbitMQ, Redis, OpenSearch and storage,
    plus whole-Secret and key-group hooks for the rest.
  - The same contract extended to silo, live and Plane AI, which each read a different subset.
  - live's AMQP_URL guarded so the RabbitMQ mirror is not silently inert for live.
  - live-exporter's ServiceAccount (it was the only workload hardcoding the release-scoped name),
    and a NOTES warning that the MQ mirror does not reach live's export queue.
  - Keyless S3: the chart omits AWS_ACCESS_KEY_ID rather than rendering it empty, because an empty
    credential is found first in boto3's chain and shadows the pod's identity. That Secret now
    renders base64 `data`, so a key the chart stops rendering is a deletion Helm can express —
    without which switching an existing release from MinIO to S3 fails as InvalidClientTokenId while
    the configuration looks correct.
  - Bedrock credentials, keyed (AWS_BEARER_TOKEN_BEDROCK) or keyless via the pod's identity, with the
    profile ARN and region outside the provider-key suppression group because they are identifiers.

The chart version stays at 3.6.2, above master's 3.3.0; appVersion takes master's 3.1.1.

Rebasing replayed as a single commit: master's OTel change collides with all eight of the original
commits in the same few regions, and resolving the same conflict eight times invites exactly the
silent mangling this diff is meant to avoid — two of the intermediate resolutions had already
produced duplicate `env:` keys before being caught. The original commit messages are preserved in
the PR history.

* feat(plane-enterprise): give live and live-exporter the broker parts (3.6.3)

The chart already blanked live's AMQP_URL so the RabbitMQ mirror's parts would take over — but it
never injected any parts into live, and live-exporter had no credential helper at all. So the mirror
reached every service except the two that own the export queue, and it failed in the quietest way
available: live boots, serves collaborative editing, and only exports are dead.

live now gets plane.rabbitmqCredsEnv alongside the Redis parts it already had. live-exporter gets
both — it is the export worker, so the broker credential is the entire point for it, and Redis comes
too because it shares live's RedisManager for job state.

Paired with the app change that teaches apps/live to compose AMQP_URL from
RABBITMQ_HOST/PORT/USER/PASSWORD/VHOST/SSL (makeplane/plane-ee#8747). Verified against the
ext-secret-test values: both workloads resolve AMQP_URL empty with the five parts present, the
credentials arriving by secretKeyRef from the operator's Secret, RABBITMQ_SSL=1 and port 5671 for
Amazon MQ.

The NOTES warning is reworded rather than deleted. It was stated as a permanent gap in live; it is
actually a version constraint, and an operator on an older planeVersion still needs to hear it —
with the specific note that only exports fail, because that is what makes it easy to miss.

Additive as usual: with no external_secrets set, all 18 containers resolve an identical environment
to 3.6.2.

* feat(plane-enterprise): extraObjects, so an env's ESO wiring can live with its release (3.6.4)

Everything a Plane environment needs in order to STOP holding credentials — an ESO SecretStore, the
ExternalSecrets that produce the Secrets this chart reads by name — is not part of this chart and
should not be. But it also has nowhere to live when a GitOps tool treats one directory as one unit:
Fleet's dependsOn orders one bundle against another, so expressing "Secrets before workloads" forced
a second bundle to exist purely to be depended on. extraObjects removes that.

Rendered with toYaml and NOT tpl, deliberately. What people put here is usually an ExternalSecret
whose target.template contains ESO's own {{ }} placeholders; tpl would try to evaluate those as Helm
expressions and either fail or silently resolve them to empty. Helm does not template values, so
this passes them through untouched — verified by rendering an ExternalSecret whose template
references {{ .CLOUDFLARE_KEY }} and confirming it survives.

Ordering is the caller's to choose with the standard Helm hook annotations, and the template's
comment shows the shape, because it is the whole point: this chart's Secret references are
optional: false, so a pod that starts before its Secret exists sits in CreateContainerConfigError
until it appears. A pre-install/pre-upgrade hook with a negative weight is applied before the
workloads.

Inert when unset — extraObjects defaults to [] and the default render is unchanged.

* fix(plane-enterprise): address the remaining CodeRabbit review on the secret contract

Three findings from the review on #278, plus one declined.

NOTES.txt — the SemVer probe was a prefix match, so "3.2.0.1" (a realistic
four-part vendor version) was treated as parsable, reached semverCompare and
aborted the render with "Invalid Semantic Version" instead of falling through to
the compatibility warning it exists to print. Anchored, with optional prerelease
and build metadata so real SemVer still compares.

app-env.yaml — externalized credentials describe a remote backend, but nothing
stopped them being paired with a bundled one. The chart would then hand the app
the operator's credential and the in-cluster endpoint, which the bundled service
never authenticates; for Redis it also blanks REDIS_URL, leaving an older image
with no route at all. Now a render-time failure for postgres/redis/rabbitmq,
matching what plane.externalOpensearch already did for its own backend.

app-env.yaml — the read-replica guard proved credentials existed but not an
endpoint, so replica-enabled plus a database Secret and no pgdb_read_replica_host
rendered POSTGRES_READ_REPLICA_HOST="". The API deliberately does not fall back
to the primary there, because that would send every "replica" read to the writer
with no error, so an empty host is a broken replica rather than a slow one. The
contract is now enforced and documented in values.yaml.

Declined: removing plane.siloConnectorsSecretRef from the migrator. The migrator
already mounts <release>-silo-secrets on master, and silo.yaml suppresses the
connector keys from it when silo_connectors_existingSecret is set — so dropping
the ref would give the migrator strictly fewer variables on the externalized path
than on the default one, which is the divergence this PR exists to avoid.

* chore(plane-enterprise): ship the whole secret contract as one 3.6.0 bump

The branch had walked 3.6.1 through 3.6.5 as it grew. Nothing above 3.5.2 was
ever published, so collapse it: one version for one PR, and the base64 `data`
note in README/NOTES now points at the release that actually carries it.

* fix(plane-enterprise): roll workloads when the OTEL config changes

plane.configChecksum hashed every config-secret template except otel.yaml, so
retuning observability.otel.* rewrote <release>-otel-vars / -otel-secrets but
left the checksum untouched. envFrom is read once at container start, so the
running pods kept the old exporter config until something else rolled them.

Only reachable since this branch replaced the unconditional `timestamp` pod
annotation with a content checksum -- on 3.5.2 every upgrade rolled everything,
which hid it. Folded into the same 3.6.0 bump; nothing is released yet.

docker-registry and cert-issuers stay out of the hash: neither is pod env, so
hashing them would roll every workload for a change no container can observe.

Also moves the extraEnv HTTP_PROXY example back under extraEnv, where it sat
before extraObjects was inserted above it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore: drop the hack/ verification scripts from the chart repo

assert-secrets.py and resolve-env.py were written to check this branch's work --
that no rendered Secret still carries a credential, and that each container
resolves the env it did before. Useful while building it, but they are not chart
deliverables and they sat at the repo root of a published charts repo.

Nothing references them: no README, NOTES.txt, or the rotation runbook. Both
were added on this branch, so removing them leaves no trace in master's history.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore(plane-enterprise): do not enforce the Reloader annotation on email

Back to annotations: {}, so nothing is imposed on the workload. Operators who
want the restart set reloader.enabled, or add the annotation themselves.

Note this differs from master, where the annotation was a literal in
email.deployment.yaml and so applied to every deployment. That literal sat above
the plane.labelsAndAnnotations include, which emits its own annotations: key --
so any operator who set services.email_service.annotations turned it into a
duplicate YAML key and silently lost the Reloader one. Routing everything
through the helper fixes that: annotations now compose instead of clobbering.

The trade-off is stated in values.yaml rather than left implicit -- this service
mounts a cert-manager TLS Secret, so without the annotation it will not pick up
a renewed certificate until something else restarts it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(plane-enterprise): let the key-group Secrets win in envFrom

The same ordering bug review found in plane-ce, which this chart shares: the
key-group includes were FIRST in envFrom on every workload that uses them.
envFrom is later-source-wins, so an operator setting app_keys_existingSecret
alongside app_env_existingSecret -- or silo_connectors_existingSecret alongside
silo_env_existingSecret -- would have the whole-Secret hook override the key the
group hook owns.

That defeats the group hook's purpose. SECRET_KEY and LIVE_SERVER_SECRET_KEY are
rendered into both the app and live Secrets and must agree; an override landing on
the api but not on live leaves the two with different signing keys and live-server
auth fails with nothing logged. SECRET_KEY also derives the key encrypting the
instance-configuration rows.

NOTES.txt already warned about this interaction at install time. A warning is
weaker than correct ordering: it fires only if the operator reads it, and says
nothing about which of the two Secrets wins.

17 envFrom blocks across 17 workloads. Two needed more than a straight move --
api carries both appKeysSecretRef and siloConnectorsSecretRef stacked together,
and silo has two envFrom blocks with the include leading the second.

Verified by rendering with both the group and whole-Secret hooks set and checking
every container: 10 containers receive a group Secret and in all 10 it now sorts
after every other source. The default render is unchanged, which it must be --
with no hook set the includes emit nothing, so order cannot matter.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* refactor(plane-enterprise): drop the reloader flag, keep the annotation route

This chart is public, and a Reloader annotation is a deployment decision rather
than something a chart should offer a switch for -- which workloads must restart
when a credential rotates depends on the install, and the datastores generally
should not.

services.<name>.annotations already reaches the WORKLOAD resource, which is where
Reloader looks, so nothing is lost: an operator sets

  services:
    api:
      annotations:
        reloader.stakater.com/auto: "true"

and gets exactly what the flag did, for the workloads they choose.

Removing the values key means NOTES.txt could no longer dereference
.Values.reloader.enabled -- that would have been a nil-pointer failure on every
install, not a silent no-op, so the warning is rewritten. It still fires whenever
an external-Secret hook is set, and now names the annotation to add instead of a
flag to flip. README, the ESO examples and the rotation runbook follow.

email.deployment.yaml keeps taking its annotations from the helper. The literal it
used to carry is still gone, which is what was asked for earlier: the chart imposes
nothing.

No default-render change -- the flag defaulted false, so 0 lines differ. Renders
44 documents, NOTES.txt renders under --dry-run with and without a hook set, a
per-workload annotation lands on the workload and not the pod template, and lint
is clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…PII scanner service (#314)

* [INFRA-881] feat(plane-enterprise): add the Argus sensitive-data & PII scanner service (3.6.0)

Argus (apps/argus in plane-ee) was deployable through the plane-cloud chart
and, since INFRA-877 (plane-ee#9446), through every commercial self-hosted
method except this chart. This ports it, opt-in and mirroring that contract:

- templates/workloads/argus.deployment.yaml: headless Service + Deployment on
  port 8100. Pinned to replicas: 1 with strategy Recreate and no `replicas`
  value: export CSVs are served from the pod's own filesystem and the startup
  reapers are unscoped, so a second pod breaks in-flight work. Probes hit
  <base_path>/healthz (the only health route, mounted under the prefix).
  terminationGracePeriodSeconds 60 because Argus drains scan workers before
  its 10s HTTP drain. An emptyDir is mounted at ARGUS_DATA_DIR so exports stay
  writable under securityContext.enabled or an OpenShift-assigned UID.
- templates/workloads/argus-migrator.job.yaml: one-shot `argus migrate`
  (args only; the image ENTRYPOINT is the binary). `argus start` deliberately
  does not migrate.
- templates/config-secrets/argus-env.yaml: Secret (ARGUS_DATABASE_URL derived
  from services.postgres / env.pgdb_* like silo, ARGUS_CONTENT_DATABASE_URL
  from the read replica, ARGUS_FINGERPRINT_SECRET, ARGUS_INTERNAL_SECRET) with
  external_secrets.argus_env_existingSecret, plus the ConfigMap. Rendering
  fails with the reason when the fingerprint secret is under 32 characters
  and no existing Secret is named, instead of shipping a crash-looping pod.
- /argus/ route (from env.argus_envs.base_path) on the nginx, Traefik and
  OpenShift ingress templates, forwarded without rewrite.
- values.yaml (services.argus, env.argus_envs, argus_env_existingSecret),
  questions.yml ("Argus Setup" group, image, existing secret), README
  (Argus Deployment section, External Secrets and Custom Ingress Routes
  tables), Chart.yaml 3.5.8 -> 3.6.0.

Off by default; no rendered output changes unless services.argus.enabled.

Co-Authored-By: WOZCODE <contact@withwoz.com>
Claude-Session: https://claude.ai/code/session_01Wu7g4mh2TaFo4Dcp9fYeKc

* [INFRA-881] chore(plane-enterprise): bump Plane-EE to v3.2.0 and default the Argus internal secret

- appVersion 3.1.4 -> 3.2.0; planeVersion v3.1.4 -> v3.2.0 in values, questions and README
- env.argus_envs.internal_secret now ships a placeholder default, matching the other
  placeholder secrets in the chart (rotate per deployment)

Co-Authored-By: WOZCODE <contact@withwoz.com>
Claude-Session: https://claude.ai/code/session_013yaAavA8NMZ5jTziKPjM48

* Bump version from 3.6.0 to 3.7.0

---------

Co-authored-by: WOZCODE <contact@withwoz.com>
…ker Hub org (#315)

MinIO removed the `minio` organisation from Docker Hub. Every deployment
that pulls `minio/minio` or `minio/mc` now fails with:

    pull access denied for minio/minio, repository does not exist
    or may require 'docker login'

The registry returns 401 (not 404) to anonymous clients, so the error
reads like a credentials problem when the repository is simply gone.

quay.io is MinIO's own second registry and still serves both images
publicly (multi-arch: amd64, arm64, ppc64le). Repoint every default at
quay.io, and pin an explicit RELEASE tag instead of `:latest` so a
future upstream change to a floating tag cannot break deployments the
same way:

  minio -> quay.io/minio/minio:RELEASE.2025-09-07T16-13-09Z
  mc    -> quay.io/minio/mc:RELEASE.2025-08-13T08-35-41Z

These are the digests `:latest` currently resolves to on quay.

Note: the community MinIO build is effectively frozen upstream (last
community push was 2025-09-07), so this unblocks deployments but does
not change the longer-term need to steer self-hosted users toward
external S3, with bundled MinIO as a dev/eval convenience.


Claude-Session: https://claude.ai/code/session_01TB2aRDJ5BMx6m5c7jDzkqM

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…316)

Version bump for the MinIO registry fix merged in #315, which changed the
shipped image defaults in both charts:

  minio -> quay.io/minio/minio:RELEASE.2025-09-07T16-13-09Z
  mc    -> quay.io/minio/mc:RELEASE.2025-08-13T08-35-41Z

Patch bumps, matching how comparable image-default changes were versioned
previously (iframely 3.5.7 -> 3.5.8, busybox/mc 1.6.2 -> 1.6.3).

appVersion is unchanged in both charts - no application release is involved.


Claude-Session: https://claude.ai/code/session_01TB2aRDJ5BMx6m5c7jDzkqM

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…FIPS (UBI) images (#317)

Enabling a custom CA bundle on a FIPS deployment stopped the pods from
starting at all. plane.s3CAInitScript ran `update-ca-certificates`, which
only exists on the standard Alpine/Debian images; the FIPS images are UBI
and ship `update-ca-trust`. The script is embedded in a container command
that runs under `set -e`, so the missing binary exited 127 and the
entrypoint was never reached -- api, external-api, worker,
worker-importers and the pi-* workloads all went down. Measured on
registry.access.redhat.com/ubi10/ubi-minimal: old script exit 127, new
script exit 0.

Detect the trust tool at runtime instead of assuming one, and use the
matching anchor directory and bundle path (/etc/pki/ca-trust/source/anchors
and /etc/pki/tls/certs/ca-bundle.crt on UBI, /usr/local/share/ca-certificates
and /etc/ssl/certs/ca-certificates.crt on Alpine/Debian). Verified the
custom CA lands in the trust store on both: 146 -> 147 certs on UBI,
119 -> 120 on Alpine, and Python's default SSL context picks it up.

The correct bundle path is not knowable when the chart renders, so the
SSL_* variables are now exported by the script -- an export survives the
exec that follows -- overriding the Alpine/Debian defaults that
plane.s3CAEnvVars still puts in the pod spec.

Nothing in the script can fail the container any more. When the install
does not happen -- no trust tool, a non-root container that cannot write
the anchor directory, or an empty secret -- it warns and unsets those
variables rather than leaving SSL_CERT_FILE pointing at a bundle the image
does not have, which would break every outbound TLS call instead of just
the private-CA ones. All three paths verified to leave the container
running.

The Node path (live, silo) already worked: it concatenates into an
emptyDir from a busybox init container and sets NODE_EXTRA_CA_CERTS, with
no distro-specific tooling. Unchanged.

With the CA feature disabled -- the default -- the rendered output is
byte-identical to 3.7.1 apart from the chart-version label and the
checksum/config annotation that any version bump moves.

Also document in values.yaml that these certificates are not S3-only, and
that LDAP is configured separately via extraEnv against the verbatim mount
at /s3-custom-ca/<key>, because OpenLDAP does not read the system store.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: WOZCODE <contact@withwoz.com>
Co-authored-by: WOZCODE <contact@withwoz.com>
…dConstraints (#325)

* feat(plane-enterprise): native PodDisruptionBudgets and topologySpreadConstraints

Two gaps the HA docs have been apologising for in a "Known chart gaps" table:
budgets had to be applied as hand-written manifests, and AZ spreading had to be
expressed as podAntiAffinity because the chart rendered no
topologySpreadConstraints. Both are now values.

  podDisruptionBudget:
    enabled: true            # every eligible workload with 2+ replicas
    maxUnavailable: 1
    unhealthyPodEvictionPolicy: AlwaysAllow

  services:
    api:
      replicas: 3
      podDisruptionBudget: { maxUnavailable: 2 }   # per-workload override
      topologySpreadConstraints:
        - topologyKey: kubernetes.io/hostname
          whenUnsatisfiable: DoNotSchedule
        - topologyKey: topology.kubernetes.io/zone

plane-enterprise only. plane-ce is untouched deliberately -- it ships six scalable
workloads against nineteen here, and the community edition's deployments are
overwhelmingly single-node, where a budget protects nothing and a spread constraint
has nowhere to spread. It can follow later if there is demand.

ONLY THE SCALABLE WORKLOADS GET A BUDGET, and the exclusions are enforced rather
than documented: requesting one for a singleton (beatworker, pi_beat_worker,
monitor, argus, the migration Jobs) or an in-chart stateful service fails the
render with an explanation. With no second copy a budget cannot protect anything
-- all it can do is refuse the eviction until the drain gives up or the node
disappears under it, turning a routine rotation into a stuck one. An eligible
workload still at replicas: 1 is skipped SILENTLY instead, so
podDisruptionBudget.enabled stays a safe one-line change; the explicit request is
the mistake, not the default.

maxUnavailable is the documented default over minAvailable because replica counts
move: at one replica maxUnavailable: 1 is a no-op, where minAvailable: 1 wedges
every future drain of that node. Setting both fails the render -- the API accepts
only one.

THE labelSelector IS FILLED IN by plane.podSpread from the workload's own app.name
label. This is the part worth reviewing: a topologySpreadConstraint whose selector
matches nothing is satisfied by every possible placement, so a hand-written one
fails open and silently. The values keys do not match the labels either
(email_service renders email-app, pi renders pi-api), so the mapping lives in
plane.pdbEligible and is consumed by both the PDB template and the validations.

The migrator Jobs deliberately do NOT get spread constraints even though they share
services.api / services.pi with the api and pi deployments -- a one-shot Job has
nothing to spread against.

3.7.4 -> 3.8.0. The chart also gains a templates/validations.yaml, which it did not
have: it renders no resource and holds the refusals.

Additive: rendering with default values is byte-identical to before, verified by
diffing a full render against master. Feature paths verified by asserting every
rendered PDB selector matches exactly one Deployment at replicas >= 2, that no
multi-replica Deployment is left uncovered, and that each refusal path refuses.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(plane-enterprise): zero-safe PDB fields, required topologyKey

Three CodeRabbit findings on #325, all reproduced before fixing.

1. A constraint with no topologyKey rendered `topologyKey: null` and was rejected
   by the API at apply time. `required` turns it into a Helm error naming the
   field.

2. minAvailable/maxUnavailable were resolved by truthiness, and 0 is falsey. Both
   values are legal and meaningful -- maxUnavailable: 0 forbids all voluntary
   eviction, minAvailable: 0 permits it all -- and both were silently rewritten to
   `maxUnavailable: 1`, which INVERTS the intent of maxUnavailable: 0. CodeRabbit
   caught the validation half of this; the renderer was the worse half.

   Both now key off hasKey, and the precedence is per LEVEL rather than per key:
   if the workload sets either field it decides alone, otherwise the chart-wide
   block does. Resolving key by key let the chart-wide maxUnavailable survive
   alongside a workload's minAvailable and emit both, which the API rejects.

3. values.yaml shipped `maxUnavailable: 1` as a concrete default, so a user who
   set only `podDisruptionBudget.minAvailable` merged into both fields and had
   their render refused -- the chart contradicting its own documented override.
   The default now lives only in the renderer's final else arm.

The both-set refusals still fire, including the case truthiness could never catch:
minAvailable: 0 AND maxUnavailable: 0 together.

Defaults still render byte-identical to master; the feature suite (selector
correctness, no uncovered multi-replica Deployment, per-service override wins)
still passes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(plane-enterprise): default WEBHOOK_ALLOWED_HOSTS to the license domain

When env.webhook_allowed_hosts is empty, derive it from license.licenseDomain
with the scheme chosen by the plane.tlsEnabled check, the same way WEB_URL
is built. Document the default in values.yaml, questions.yml and the README,
and bump the chart to 3.8.1.

Co-Authored-By: WOZCODE <contact@withwoz.com>

* release(plane-enterprise): update Plane-EE to version v3.2.3

Co-Authored-By: WOZCODE <contact@withwoz.com>

* fix(plane-enterprise): add webhook allowed hosts URL example

Co-authored-by: akshat5302 <55329820+akshat5302@users.noreply.github.com>

---------

Co-authored-by: WOZCODE <contact@withwoz.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
… optional in-chart service (#320)

* feat(plane-enterprise): add the Plane MCP server as an optional in-chart service

Restores the MCP-in-enterprise work from the 2026-08-27 session (stash on
release/plane-enterprise-v3.1.2): services.mcp_server values, ConfigMap/Secret,
Service+Deployment, and /mcp path routing for nginx, traefik and OpenShift
ingress. Disabled by default; path prefix defaults to /mcp.

Co-Authored-By: WOZCODE <contact@withwoz.com>

* chore(plane-enterprise): default the MCP server to v0.3.2 and hand it REDIS_URL

v0.3.2 accepts REDIS_URL for its OAuth token store, so the chart now emits the
same URL live uses (bundled or env.remote_redis_url) instead of parsing the URL
into REDIS_HOST/PORT/PASSWORD/SSL. With external_secrets.redis the URL stays
unset and the discrete REDIS_* parts come from plane.redisCredsEnv.

Co-Authored-By: WOZCODE <contact@withwoz.com>

* fix(plane-enterprise): give the MCP ingress route a trailing slash like the other routes

Matches /api/, /silo/, /pi/ etc. on nginx, Traefik and OpenShift; the prefix is
trimmed first so a value like /agent/ does not render as /agent//.

Co-Authored-By: WOZCODE <contact@withwoz.com>

* fix(plane-enterprise): route the MCP server's OAuth discovery paths to it

MCP clients resolve the resource metadata URL the server advertises in
WWW-Authenticate (/.well-known/oauth-protected-resource<prefix>/http/mcp) and
the path-inserted authorization-server metadata
(/.well-known/oauth-authorization-server<prefix>/http). Both sit at the host
root, outside the <prefix>/ route, so they were landing on the web app. Plane
serves nothing under /.well-known, so the two prefixes go to the MCP server on
nginx, Traefik and OpenShift.

Co-Authored-By: WOZCODE <contact@withwoz.com>

* chore(plane-enterprise): address review on the MCP server service

- carry the tag in services.mcp_server.image (makeplane/plane-mcp-server:v0.3.2)
  like iframely/postgres; drop services.mcp_server.tag from values, questions.yml
  and the README
- give the MCP rows their own "MCP Server Deployment" README section instead of
  extending the Iframely table
- document the /mcp and OAuth discovery routes in the custom-ingress table

Co-Authored-By: WOZCODE <contact@withwoz.com>

* fix(plane-enterprise): make the MCP server's Plane OAuth application mandatory

CodeRabbit flagged that the README said the MCP server does not start without
plane_oauth.client_id while the values table called OAuth optional. The README
was right: plane-mcp-server's default http transport (the image CMD) builds its
PlaneOAuthProvider unconditionally, and that provider raises on an empty client
id or secret, so plane_oauth.enabled=false shipped a crash-looping pod.

- drop services.mcp_server.plane_oauth.enabled from values, questions and README;
  client_id / client_secret are required whenever services.mcp_server.enabled is
  true, and the env template fails at render time with the reason (same pattern
  as argus) unless mcp_server_env_existingSecret supplies them
- derive PLANE_OAUTH_PROVIDER_BASE_URL from the license domain and TLS setting
  (the server appends <path_prefix>/http itself) and move it to the ConfigMap,
  so an external Secret no longer has to carry a non-secret value
- README: state the OAuth callback URIs in terms of services.mcp_server.path_prefix
  and mark /mcp in the custom-ingress table as the default prefix
- Chart.yaml 3.8.0 -> 3.9.0: master took 3.8.0 with the PodDisruptionBudget PR,
  so this branch's version now collides and the release workflow would skip it

Co-Authored-By: WOZCODE <contact@withwoz.com>

* chore(plane-enterprise): bump version to 3.9.0

---------

Co-authored-by: WOZCODE <contact@withwoz.com>
…closed to anonymous pulls (#330)

On 2026-09-24 MinIO closed anonymous pulls on quay.io/minio/minio and
quay.io/minio/mc for every tag (the anonymous token grants no actions and
manifests answer 401), two weeks after #315 moved here from the deleted Docker
Hub org. Every install using the bundled MinIO fails to pull it.

services.minio.image  -> docker.io/pgsty/minio:RELEASE.2026-08-04T00-00-00Z
services.minio.image_mc -> docker.io/pgsty/mc:RELEASE.2026-09-16T00-00-00Z

Pigsty's community builds of MinIO's AGPL source: same entrypoint and root user
as upstream, read data written by RELEASE.2025-09-07T16-13-09Z, amd64 + arm64,
/usr/bin/mc where the bucket Job expects it. The Job's exact commands (alias
set, mb, anonymous set download) verified against the pair. questions.yml and
README defaults follow.

plane-ce 1.8.1 -> 1.8.2, plane-enterprise 3.9.1 -> 3.9.2.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…n-chart draw.io (off by default) (#245)

* feat: add DRAWIO_EMBED_URL env to plane-enterprise chart

Adds the DRAWIO_EMBED_URL config var (default empty) to the
plane-enterprise app-vars ConfigMap and values.yaml so operators can
point the Wiki editor's diagram extension at a self-hosted drawio
endpoint. Empty preserves the diagrams.net cloud default.

Co-Authored-By: WOZCODE <contact@withwoz.com>

* chore: document DRAWIO_EMBED_URL in questions/README and bump chart to 2.5.2

* feat(plane-enterprise): add optional in-chart draw.io, off by default

Ports the commercial-deployments draw-io kustomize component
(makeplane/commercial-deployments@f63056d) to the chart:

- services.drawio (default enabled: false): makeplane/drawio:v2.5.0
  Deployment + Service on 8080, same resources/probe as the component
- /drawio subpath on Traefik (stripPrefix + trailing-slash redirect
  middlewares), nginx (dedicated rewrite-target + redirect Ingresses)
  and OpenShift (Route with rewrite-target)
- DRAWIO_EMBED_URL defaults to <scheme>://<licenseDomain>/drawio when
  drawio is enabled and env.drawio_embed_url is empty
- drawio is PDB-eligible; values, questions.yml and README documented
- chart version 3.10.0

Co-Authored-By: WOZCODE <contact@withwoz.com>

* refactor(plane-enterprise): gate drawio on local_setup like other bundled services

Align draw.io with the postgres/redis/rabbitmq/opensearch/minio pattern:

- services.drawio.enabled -> services.drawio.local_setup (default false)
- env.drawio_embed_url -> env.drawio_remote_url, mirroring
  pgdb_remote_url / opensearch_remote_url
- DRAWIO_EMBED_URL precedence follows DATABASE_URL: local_setup
  (in-chart /drawio URL) -> drawio_remote_url -> ""
- pdb.yaml also skips a service whose local_setup is false, so no
  drawio PDB renders when drawio isn't deployed
- questions.yml "Install Draw.io" with the remote URL shown only when
  local_setup is off; README updated

Co-Authored-By: WOZCODE <contact@withwoz.com>

* feat(plane-enterprise): default DRAWIO_EMBED_URL to the public diagrams.net cloud

env.drawio_remote_url now defaults to https://embed.diagrams.net (the
editor's own DEFAULT_DRAWIO_EMBED_HOST) instead of "", and the template
falls back to it if the value is blanked, so DRAWIO_EMBED_URL is never
empty. embed.diagrams.net also serves /js/viewer-static.min.js, so the
read-only viewer keeps working with the single host. local_setup: true
still points it at the in-chart /drawio.

Co-Authored-By: WOZCODE <contact@withwoz.com>

* revert: keep DRAWIO_EMBED_URL empty by default

The frontend already falls back to the diagrams.net cloud when the value
is empty, so the chart doesn't need to hard-code embed.diagrams.net.
Reverts 3a3b7a5.

Co-Authored-By: WOZCODE <contact@withwoz.com>

* refactor(plane-enterprise): route drawio on /drawio/ like the other services

Match the /silo/, /pi/ style: the drawio ingress path prefix is now
/drawio/ (with trailing slash) on Traefik, nginx and OpenShift, and the
bare-/drawio -> /drawio/ redirect is dropped (Traefik redirect-slash
middleware and the nginx redirect Ingress). Plane only ever requests
<DRAWIO_EMBED_URL>/?embed=1... and <DRAWIO_EMBED_URL>/js/..., both under
/drawio/. The /drawio prefix is still stripped since the image serves at /.

Co-Authored-By: WOZCODE <contact@withwoz.com>

---------

Co-authored-by: WOZCODE <contact@withwoz.com>
…235)

* Plane-EE: route /static to API service for Swagger UI sidecar assets

Add a /static route to both the nginx Ingress and Traefik IngressRoute
so DRF-Spectacular Swagger UI sidecar assets (served from
/static/drf_spectacular_sidecar/) reach the API service instead of
falling through to the web frontend. Bump chart version to 2.4.4.

Fixes makeplane/plane#9104

Co-Authored-By: WOZCODE <contact@withwoz.com>

* Plane-EE: default nginx proxy-buffer-size to 8k

Ship nginx.ingress.kubernetes.io/proxy-buffer-size: 8k as a default
entry in ingress.ingress_annotations so large upstream response headers
(e.g. the Swagger UI / OpenAPI schema endpoints) are not truncated by
the nginx ingress controller. Picked up by the existing annotations
range in the Ingress template; documented in README.

Co-Authored-By: WOZCODE <contact@withwoz.com>

---------

Co-authored-by: WOZCODE <contact@withwoz.com>
#335)

Swagger UI loads its assets from /static/drf_spectacular_sidecar/, which
the plane-ce ingress sent to the web frontend, so the page stayed blank.
Route /static/ to the api service on both the nginx Ingress and the
Traefik IngressRoute, matching the Community proxy's Caddyfile.

Also expose env.enable_drf_spectacular (default false) as
ENABLE_DRF_SPECTACULAR so Swagger can be turned on without extraEnv.
Bump the chart to 1.8.3.

Co-authored-by: WOZCODE <contact@withwoz.com>
Update appVersion, the planeVersion default, the questions.yml default
and the README to v1.4.2, and bump the chart to 1.8.4.

Co-authored-by: WOZCODE <contact@withwoz.com>
…PDB validation (#338)

validations.yaml read .podDisruptionBudget off every services.* value, so any scalar
there (e.g. services.storage_class, as plane-cloud has) crashed rendering with
"can't evaluate field podDisruptionBudget in type string". Mirrors
helm-charts-private#727. Bump 3.10.1 -> 3.10.2.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: WOZCODE <contact@withwoz.com>
…her silo limits (#337)

* [INFRA-1040] fix(plane-enterprise): raise MinIO idle timeout and silo limits by default

MinIO resets a connection whose client stops reading for ~30-60s. Silo reads
a Confluence export's entities.xml through one ranged GET and pauses it while
writing each batch, so a large import failed with ECONNRESET "aborted".

- services.minio.idle_timeout (default 1h) renders MINIO_IDLE_TIMEOUT on the
  bundled MinIO; empty keeps MinIO's default.
- Silo default limits 500m/1000Mi -> 2000m/2Gi: parsing an import is
  CPU-bound and the throttled pauses are what outlast the idle timeout.
- Chart 3.10.1 -> 3.10.2.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [INFRA-1040] fix(plane-enterprise): make the MinIO idle timeout opt-in

MINIO_IDLE_TIMEOUT is both MinIO's keep-alive idle timeout and the per-connection
read/write deadline, so a 1h default lets any client hold a stalled or idle
connection for an hour. Default to empty (MinIO's own 30s) and document when to
raise it; silo resuming a dropped read (makeplane/plane-ee#9952) is the real fix.

Addresses the CodeRabbit review on #337.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…am endpoints (#342)

* fix(plane-enterprise): add traefik routes for SSE stream endpoints

Route the run activity stream (/api/workspaces/*/runs/*/activities/stream/)
to the API without the body-limit middleware, with priority 1000 so it wins
over the /api/ route. Add an explicit route for the pi chat stream-answer
endpoint, also without buffering. Chart 3.10.5 -> 3.10.6.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(plane-enterprise): anchor run activity stream route regex

End the PathRegexp with $ so only the stream endpoint itself skips the
body-limit middleware, not paths with trailing segments.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ent self-managed Garage (#331)

* [INFRA-1030] fix(charts): move the bundled store to pgsty/silo RELEASE.2026-09-16

pgsty/minio is frozen: RELEASE.2026-08-04 is the last tag ever published under
that name, and everything after it ships as pgsty/silo. We were three releases
and six weeks behind, across a security release that lands in Plane's own path.

RELEASE.2026-09-16 (SN-2026-011..014) rejects unsigned x-amz-* operation
headers, verifies header-bound presigned payloads and aligns signature-age and
payload-hash conditions with authenticated values. Presigned URLs are Plane's
entire upload and download mechanism, so this is not a hygiene bump. The same
release also removes exponential wildcard matching from policy evaluation,
which the bucket's anonymous-download policy puts on the request path.

image_mc was already on the 09-16 tag, so this also ends the client/server skew.

Verified the rename is behaviour-neutral for how the chart uses the image:
  - the entrypoint still dispatches `server` (args: [server, /data] runs
    `silo server /data`), so the StatefulSet needs no change
  - /usr/bin/mc still exists in pgsty/mc, so the bucket init Job is unaffected
  - mb + anonymous set download + PUT/GET round trip all pass
  - unauthenticated GET, 404 on a missing key and the ListBucketResult body are
    byte-identical between the old and new image
  - rendered diff against master is one image: line per chart

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* [INFRA-970] docs(plane-enterprise): self-managed Garage on Kubernetes, with a MinIO-to-Garage copy Job

Plane's charts keep bundling MinIO. examples/garage/ is for operators who want to run Garage
instead; they own it, and Plane uses it through the existing external-storage values, so no
chart change is needed.

- README.md: install Garage from its own chart, bring the cluster up (layout), create Plane's
  key and bucket, copy existing files, switch Plane with helm upgrade, check, roll back, and
  operate it (redundancy, snapshots, scrub, capacity, upgrades, key rotation, region).
- garage-values.yaml: values for Garage's chart: 3 nodes at replication factor 3, the
  multi-arch image (the chart default is amd64-only), region us-east-1, no public ingress.
- copy-minio-to-garage.yaml: a standalone Secret + Job (pgsty/mc) that mirrors Plane's MinIO
  bucket into Garage and verifies names, sizes, object count and optionally SHA-256. It only
  reads MinIO, reruns copy only what is missing, and runs non-root with a read-only root fs.
- plane-values.yaml: services.minio.local_setup=false, the in-cluster Garage endpoint, and
  env.use_storage_proxy=true so Plane's API carries file traffic and Garage stays private.
  plane-ce has no storage proxy; the README covers the public-endpoint + CORS path for it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
… and ESO examples (#349)

* fix(plane-enterprise): external secrets fixes and corrected guidance

- live-exporter now mounts app_keys_existingSecret. It runs the live image,
  whose env check exits without LIVE_SERVER_SECRET_KEY, and with app_keys set
  the live Secret no longer carries that key, so a fresh install's exporter
  could not start.
- NOTES, README, values.yaml and the appKeysSecretRef comment described
  app_keys as mounted first (older groups win). Every workload mounts it last,
  so it wins; the duplicate warning now says what actually happens.
- New NOTES warnings, no behaviour change: silo_env_existingSecret decides the
  API workloads' DATABASE_URL/REDIS_URL/AMQP_URL/AES_SECRET_KEY (mounted after
  app_env), and pgdb/rabbitmq_existingSecret only set the bundled servers'
  credentials while the apps still use values.yaml.
- values.yaml key lists: AWS_BEARER_TOKEN_BEDROCK (ai_providers);
  SLACK_SIGNING_SECRET, GITHUB_WEBHOOK_SECRET, Bitbucket and HubSpot keys
  (silo_connectors).
- examples/external-secrets: the AWS, Azure and GCP SecretStores were rejected
  by ESO's admission webhook (cross-namespace serviceAccountRef on a namespaced
  store); AWS now uses the controller's credentials, Azure/GCP a ServiceAccount
  in Plane's namespace. template.engine -> engineVersion (unknown field in v1).
  Redis parts need v3.2.0, not v3.1.0; the silo DSN templates are only for
  releases before v3.2.0; the rotation runbook compares a hash instead of
  printing a password.
- external-secrets-setup.md: v1beta1 -> v1, namespace on ClusterSecretStore
  secret refs, pointer to the full guide.

Chart 3.10.7 -> 3.10.8.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(plane-enterprise): address review on external secrets PR

- Azure example: annotate the ServiceAccount with azure.workload.identity/tenant-id;
  without a tenant ESO fails with "missing tenantID" before syncing.
- silo_env warning: AES_SECRET_KEY comes from silo_env only when
  app_keys_existingSecret doesn't carry it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…a bare hostname (#350)

The default rendered "http(s)://<licenseDomain>", but the API matches
WEBHOOK_ALLOWED_HOSTS entries as bare hostnames, so the scheme kept the
default from ever matching. Integrations then failed to register their
webhook with "URL domain or its subdomain is not allowed" until an
operator set a bare hostname by hand.

Render the license domain without a scheme, which works on every app
version that reads the setting, and correct the docs that suggested
full URLs.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: WOZCODE <contact@withwoz.com>
…need (#328)

god-mode's System checks page runs inside the api container, so the api has to
know where the other containers are and which queues they read. It did not: of
everything the checks look at, app-env carried only LIVE_BASE_URL. On Kubernetes
the space, iframely, argus, export-worker, Plane AI, automation, agent and
webhook checks therefore skipped forever -- correct behaviour, no information.

Adds seven variables to app-env, none of which any request path uses. The api
never calls space, argus or iframely, and never consumes those queues.

The queue names are taken from the same values the consumers are handed a few
files over, rather than re-defaulted here. A deployment that renames a queue
would otherwise get a check watching a queue nothing uses, which reports "not
deployed" for a consumer that is running -- wrong, and in the direction nobody
investigates.

That already bites AGENT_QUEUE_NAME. This chart defaults it to "plane.agent";
the api's own default, in run_agent_consumer.py, is "plane.agents". Passing the
chart's value through settles it for the check, but the two defaults genuinely
disagree and that is worth fixing separately.

ARGUS_BASE_URL and IFRAMELY_URL are emitted only when their service is enabled,
so a default render is unchanged for them. ARGUS_BASE_URL includes the
configurable base path, because argus mounts every route under it and a URL
without it 404s.

Verified with helm template: all seven render with the expected values, argus
and iframely are absent when disabled, and the only other change to a default
render is the app-env checksum annotation -- which is the chart correctly
deciding to roll the pods that consume this ConfigMap.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
pratapalakshmi and others added 13 commits October 19, 2026 14:58
…ivergence warning, drop dead value (#354)

* [INFRA-809] feat(plane-enterprise): the two addresses #328 did not cover

#328 gave the api its neighbours' addresses and the queue names they read,
which covers most of what god-mode > System checks needs. Two things it does
not reach, both added here on top of it:

ARGUS_INTERNAL_SECRET. #328 supplies ARGUS_BASE_URL, and an address alone is
not enough to read Argus's /admin-health: Argus mounts that route only when it
has a secret of its own, because its router is browser-facing and otherwise
unauthenticated below /healthz. Without the matching value on the api the row
skips. It is also what lets Argus report its own 5xx responses back to the api,
which the api already warns about.

INTAKE_EMAIL_SMTP_HOST and _PORT. The traffic between the api and the email
service only ever goes one way -- the service holds EMAIL_SAVE_ENDPOINT and
POSTs parsed mail to the api -- so the api has never known where it lives and
had nothing to probe. That left the one state an operator most needs invisible:
the service speaks only SMTP, so "enabled but not working" looked exactly like
"not deployed here". The check now opens a connection and reads the SMTP
banner, and a server must greet with 220 before it will accept mail, so a
listener that is up but wedged is caught too.

Both are gated on their service being enabled, so an install without them is
unchanged and the checks skip rather than fail. Templated rather than added to
values.yaml: the chart already knows both names, and an address nobody types is
an address nobody gets wrong.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(plane-enterprise): warn on web_url divergence and drop dead instance_admin_email

Folded in from #321, which was opened against chart 3.7.2 and had drifted into
conflict against master. Both of its premises still hold on current master, so
the change is re-applied rather than rebased: instance_admin_email is still
read by exactly zero templates, and NOTES.txt still collects warnings the same
way.

The marketplace OAuth apps build their redirect URIs from
license.licenseDomain, not from env.web_url. When the two disagree, the
callback every integration advertises is not the host the browser is on, so the
OAuth round-trip fails the redirect check -- and nothing in the logs names the
cause. The install warning now says so, naming both values.

instance_admin_email was never read by any template, so setting it did nothing
while looking like it configured the first admin. That admin is created through
the /god-mode/ setup page. Replaced with a comment rather than deleted
outright, so an operator who had it set finds out why it went.

Version note corrected from 3.7.3 to the version this actually ships in.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…d mode (#252)

Adds a top-level customCA section that installs private CA certificates on
connected installs, without airgapped.enabled (which also switches licensing
to offline). The airgapped.s3* keys remain a fallback.

Rebased onto master (chart 3.10.12) by Pratapa Lakshmi.

Co-authored-by: Pratapa Lakshmi <pratapagoutham@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
… requested (#360)

* fix(security): encrypt the MCP server's Redis token store when TLS is requested

The MCP server applies REDIS_SSL only to its startup PING on the host/port
path; the token store itself connects in plaintext, sending REDIS_PASSWORD in
the clear. Both charts relied on REDIS_SSL for TLS:

- plane-mcp-server: services.valkey.remote_ssl
- plane-enterprise: external_secrets.redis + env.redis_ssl (the documented
  ElastiCache setup)

With TLS requested, both now give the server a password-free rediss:// REDIS_URL,
which every image since v0.3.2 honours end to end; REDIS_PASSWORD fills in the
password, so it needs no URL encoding and rotation keeps working.
plane-enterprise composes the URL from $(REDIS_HOST):$(REDIS_PORT) so a
hostKey/portKey from the Secret still applies.

plane-mcp-server's default image moves v0.3.0 -> v0.3.5 (v0.3.0 has no
REDIS_URL), and rendering fails if remote_ssl is set with an older semver tag
instead of silently falling back to an in-memory store.

Charts: plane-mcp-server 1.0.1, plane-enterprise 3.10.13.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(security): guard both MCP image paths against pre-v0.3.2 images

Review follow-up on the rediss:// REDIS_URL change:

- plane-mcp-server: an explicitly empty services.api.tag skipped the version
  check while the deployment fell back to v0.3.0. Both now share one fallback,
  raised to v0.3.5 to match values.yaml, so an empty tag deploys a working
  image and the check judges the image that actually runs.
- plane-enterprise: env.redis_ssl with external_secrets.redis now fails the
  render when services.mcp_server.image names a semver tag below v0.3.2, which
  would ignore REDIS_URL and keep the plaintext token store. Images without a
  semver tag (stable, digests) pass through, as in the MCP chart.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…'s SSE (3.11.0 / 1.0.2) (#361)

* fix(plane-enterprise, plane-mcp-server): do not buffer the MCP server's SSE

The `<release>-body-limit` Middleware is Traefik `buffering`, which holds
the whole response until the upstream closes. The MCP server answers over
SSE: <prefix>/sse stays open for the session and <prefix>/http/mcp
replies as text/event-stream. Behind that middleware the SSE transport
never delivers its endpoint event, and every streamed reply arrives only
once complete.

plane-enterprise (3.10.14): drop the middleware from the MCP route, as
/pi/ and the agent-run activity stream already do, and note in the README
which routes maxRequestBodyBytes does not cover.

plane-mcp-server (1.0.2): every route is MCP, so drop the Middleware and
the now-unused ingress.traefik.maxRequestBodyBytes (values, questions.yml,
README). Traefik has no request-body cap that does not also buffer the
response.

nginx and OpenShift renders are unchanged: ingress-nginx defaults to
proxy-buffering off, and the OpenShift Routes already carry a 300s timeout
for streaming.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(plane-enterprise): version 3.11.0 instead of 3.10.14

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…drop monitor service (#359)

* chore(plane-enterprise): route feature flags via api, drop monitor service

- FEATURE_FLAG_SERVER_BASE_URL now points at the in-cluster api service
  (:8000) with FEATURE_FLAG_SERVER_PATH=/services/feature-flags/ in
  app-env, silo, pi-api-env and argus-env.
- PAYMENT_SERVER_BASE_URL is hardcoded to https://prime.plane.so in the
  same ConfigMaps.
- Remove the monitor StatefulSet, its ConfigMap, values, Rancher
  questions and README section.
- Add AI_USAGE_CREDITS_PER_USD (env.pi_envs.ai_usage_credits_per_usd,
  default 100) to the pi ConfigMap.
- Bump chart to 3.10.13.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(plane-enterprise): target Plane v4.0.0

Co-Authored-By: WOZCODE <contact@withwoz.com>

* chore(plane-enterprise): default AI usage credits per USD to 10000000

Co-Authored-By: WOZCODE <contact@withwoz.com>

* chore(plane-enterprise): default AI agent token budget to 10000000, restore credits per USD to 100

Co-Authored-By: WOZCODE <contact@withwoz.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: WOZCODE <contact@withwoz.com>
Bump planeVersion default, appVersion, questions.yml and README to v4.0.1.

Co-authored-by: WOZCODE <contact@withwoz.com>
Bump planeVersion default, appVersion, questions.yml and README to v1.4.3.

Co-authored-by: WOZCODE <contact@withwoz.com>
…LS, /dav/ route, /drawio redirect, unbuffered live SSE, search-index Job (3.11.3) (#362)

* [INFRA-1261] feat(plane-enterprise): configurable email Service and TLS, /dav/ route, /drawio redirect, unbuffered live SSE

Gaps found while installing the chart on EKS behind Traefik with cert-manager
(makeplane/commercial-deployments#64, aws/terraform-aws-plane).

Email service:
- New services.email_service.service block (type, annotations,
  loadBalancerClass, loadBalancerSourceRanges, externalTrafficPolicy,
  nodePorts). Defaults render the same LoadBalancer Service as before; type
  ClusterIP runs the email service without a load balancer.
- The keys volume mounts a Secret only when there is one: the chart's
  Certificate secret (smtp_domain set) or services.email_service.tls_secret_name.
  Otherwise it is an emptyDir and the app self-signs at startup
  (plane-ee apps/email/pkg/utils/certs.go, GenerateCerts from main.go), as the
  kustomize deployment already does. Previously the pod could not start
  without smtp_domain, because it waited on a Secret nothing created.
- services.email_service.generateCert (default true) skips the Certificate
  while keeping smtp_domain for SMTP_DOMAIN / INTAKE_EMAIL_DOMAIN.
- services.email_service.certIssuerRef (name, kind) points the Certificate at an
  existing Issuer/ClusterIssuer; the chart then stops creating
  <release>-cert-issuer for the email service.

Ingress:
- Route /dav/ (WebDAV for Office attachment editing, plane/urls.py) to the api
  on Traefik, nginx and OpenShift, like /api/.
- With drawio local_setup, redirect exactly /drawio to /drawio/ on Traefik
  (redirectRegex Middleware, relative Location) and nginx (Exact-path Ingress
  with permanent-redirect). It used to fall through to web.
- Give live's SSE responses (POST /live/api/ai/write, GET
  /live/export/<jobId>/sse; apps/live controllers/ai-write.controller.ts and
  export.controller.ts) a Traefik route without the buffering middleware, which
  held each stream back until it closed. Websockets stay on /live/. nginx
  (proxy-buffering off by default) and OpenShift need no change.

PI_BASE_URL is left as is, with a comment: the api has no "off" value for it
(PI_URL is "/pi" when it is empty), and the Plane AI OAuth app's redirect URI is
built from it once, at first-admin creation, so blanking it would break Plane AI
sign-in if pi is enabled later.

Chart 3.11.3 (3.11.2 was released from master as Plane v4.0.1).

* [INFRA-1261] feat(plane-enterprise): create missing OpenSearch indexes in a post-install/upgrade hook

Plane never creates its search indexes, and a missing one makes workspace
search return HTTP 500. `manage_search_index index create` cannot run on every
upgrade: django-opensearch-dsl 0.7.0 exits 1 on the first index that already
exists.

New hook Job <release>-api-search-index (post-install,post-upgrade, weight 5,
delete policy before-hook-creation,hook-succeeded, backoffLimit 3,
activeDeadlineSeconds services.api.searchIndexJob.activeDeadlineSeconds, 3600):
- api image and the migrator's env (envFrom, infra creds, extraEnv), custom CA
  like the api, Plane service account (SigV4 for Amazon OpenSearch).
- wait_for_db, wait_for_migrations (the migrator is a plain Job, not a hook),
  then `manage.py shell -c`: create each registry index that does not exist,
  then plane_docs.sync_index("create") for docs_semantic. Connection errors and
  502/503/504 are retried for ~10 minutes (bundled cluster still starting);
  anything else fails the Job and the release.
- Rendered when services.api.searchIndexJob.enabled (missing key = enabled,
  explicit false honoured) and OpenSearch can be on: services.opensearch.
  local_setup, env.opensearch_remote_url or external_secrets.
  opensearch_existingSecret (the Job exits 0 if that Secret has
  OPENSEARCH_ENABLED != 1).

Defaults (OpenSearch off) render nothing new. Mapping updates and rebuilds stay
manual (README).

* [INFRA-1261] chore(plane-enterprise): drop the PI_BASE_URL comment from app-env

* [INFRA-1261] refactor(plane-enterprise): search-index Job uses Plane's management commands only

No Python in the Job spec. The command is now plain shell:
- exit 0 when OPENSEARCH_ENABLED is not 1;
- wait_for_db, wait_for_migrations;
- retry `manage_search_index list` (exists() on every registered index, raises on
  any error) up to 60 x 10s while OpenSearch comes up;
- `manage_search_index index create --force --ignore-error`: skips indexes that
  already exist, then creates docs_semantic (sync_index("create"));
- `manage_search_index list` again, failing unless every index shows [X], since
  --ignore-error also hides real errors.
Output goes to shell variables, not files, so a read-only root filesystem is fine.

* [INFRA-1261] fix(plane-enterprise): review follow-ups for the email Service and search-index Job

- Document that spec.loadBalancerClass is immutable on an existing LoadBalancer
  Service, so changing services.email_service.service.loadBalancerClass fails
  `helm upgrade`; values.yaml and README describe the ClusterIP round-trip.
  Comment only.
- Also render the search-index Job when the global extraEnv sets
  OPENSEARCH_ENABLED to "1"/"true" (any case) or through valueFrom, which the
  chart cannot read (new helper plane.extraEnvOpensearchEnabled). A false
  positive is harmless: the Job exits 0 unless the pod's OPENSEARCH_ENABLED is
  exactly "1".

* refactor(plane-enterprise): rename emailNeedsChartIssuer to emailCertUsesReleaseIssuer

* [INFRA-1261] refactor(plane-enterprise): rename the email issuer helper to emailCertIssuerEnabled
… god-mode; remove env.skip_env_var from values (3.11.4) (#368)

* [INFRA-1332] feat(plane-enterprise): env.sync_config_from_env, and correct the skip_env_var docs (3.11.4)

The chart documented env.skip_env_var: '0' as making the API read instance
settings (SMTP, OAuth/OIDC/SAML/LDAP, LLM key, login toggles) from the
environment. The commercial API entrypoint forces SKIP_ENV_VAR=1, so the API
never sees it; only worker, beat and the consumers do, and the two disagree.

Add env.sync_config_from_env (default false). It renders
PLANE_SYNC_CONFIG_FROM_ENV=1, which makes the API copy every setting the
environment sets into the database on start (plane-ee #10852). It is rendered
only when on, so existing releases render byte-identical apart from the
version label.

Correct values.yaml, the README section and parameter table, the rotation
runbook and the install notes. Add a notes warning when skip_env_var is not
'1', and a Rancher question for the new value.

* [INFRA-1332] docs(plane-enterprise): list the 73 settings sync_config_from_env applies

Add a table of every instance setting the API reads from the database, by
area, marking the 10 stored encrypted, with the formats that are not plain
toggles (seconds, days, comma-separated, JSON). Checked against the registry in
plane-ee (core + extended).

Correct "seeded from the environment on first startup": the OIDC, SAML and LDAP
settings never are, so on a fresh install they could only be set in god-mode.

* [INFRA-1332] docs(plane-enterprise): deprecate env.skip_env_var in favour of sync_config_from_env

Two switches with overlapping names left it unclear which one to use. The API
never honoured skip_env_var, and with sync_config_from_env its only remaining
effect is to point worker, beat and the consumers at the environment while the
API reads the database.

Drop it from values.yaml so sync_config_from_env is the one documented switch,
and mark it deprecated in the README, parameter table, runbook and install
notes, with a two-step migration (enable the sync, then remove skip_env_var).
The template keeps rendering it with the same default, so every existing set of
values renders byte-identical.

* [INFRA-1332] docs(plane-enterprise): instance settings are managed in god-mode; remove env.skip_env_var from values

Instance settings (sign-in, SSO providers, SMTP, LLM key) are managed in the
god-mode admin UI by design, not through environment variables. The chart
documented env.skip_env_var: '0' as making the API read them from the
environment, which the commercial API never did.

Drop sync_config_from_env (its app side, plane-ee #10852, was closed) and remove
env.skip_env_var from values.yaml. The README, rotation runbook and install
notes now say to manage and rotate these settings in god-mode. The template
keeps rendering SKIP_ENV_VAR with default "1", so every existing release renders
byte-identical, and the install notes warn anyone who still sets '0' to save
their settings in god-mode and then remove it.
…from the README (3.11.5) (#372)

- New Runner Deployment section (services.runner.*, env.runner_envs.*).
- Rows for license.licenseServer, the Postgres read replica,
  OpenSearch/email runAsUser, live exporter pullPolicy/labels/annotations,
  silo aes_salt/cors, Plane AI sync/usage/OAuth settings, Bedrock,
  env.cors_allowed_origins, env.export_download_base_url,
  serviceAccount.cloudIdentity, external_secrets.storage,
  silo_connectors_existingSecret and extraObjects.
…itOps (3.11.6) (#369)

* [INFRA-1333] feat(plane-enterprise): global.deterministicRender for GitOps (3.11.4)

Every render differed, so ArgoCD showed releases as permanently out of sync:
the migrator, Plane AI migrator, Argus migrator and MinIO bucket Jobs are
named after the render time, and Argus stamps a `timestamp` pod annotation
outside the forceRedeploy gate every other workload uses.

Add global.deterministicRender (default false). When on, each of those Jobs is
named after a hash of its own rendered spec. Identical values give an identical
name, and any spec change (image, config checksum, chart version) gives a new
Job, which Job immutability requires anyway. Argus moves onto the shared
plane.podAnnotations. forceRedeploy still renames the Jobs on every render.

Each Job's spec moves into a named template so its name can hash it. With the
flag off, renders are byte-identical to before apart from the clock.

* [INFRA-1333] fix(plane-enterprise): rerun the Argus and MinIO bucket Jobs when their own config changes (3.11.6)

With global.deterministicRender on, two Jobs could keep their name when their
input changed:

- Argus: argus-env is not in plane.configChecksum, and the per-render timestamp
  is gone in this mode, so an Argus-only setting changed nothing in the Argus
  pod template or the migrator spec. argusPodAnnotations now adds
  checksum/argus-config (a hash of argus-env) in deterministic mode only.
  Adding argus-env to configChecksum instead would roll every workload.
- MinIO bucket Job: the bucket name and credentials come from the doc-store
  Secret, not the Job spec. Its run suffix now hashes that Secret and the chart
  version, so the README's "reruns on image, config or chart version" holds.

Default-mode renders are unchanged (diffed against master with the clock
normalised). Version 3.11.6: master took 3.11.5 in #372.
Conflicts: Chart.yaml takes master's appVersion and moves to 3.12.0.
questions.yml keeps both the consumer and mcp_server blocks. app-env.yaml keeps
master's WEBHOOK_ALLOWED_HOSTS and token budget defaults plus the
INTEGRATION_EVENT_* keys.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…consumer

The template was written against an older master. It skipped the group Secrets
(app_keys, silo_connectors), the runner key and plane.infraCredsEnv, so a release
using externally managed Secrets would not reach this workload. It also ignored
serviceAccount.name, and stamped a timestamp that defeats
global.deterministicRender.

Regenerate it from master's webhook-consumer, register it in plane.pdbEligible,
and add its topologySpreadConstraints and podDisruptionBudget keys to values.yaml.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

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

Run configuration

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

Use the checkbox below for a quick retry:

  • Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Tip

🎯 Customize your risk assessments

Guide how CodeRabbit assesses merge risk and security architecture risk. Highlight sensitive changes and explain your security boundaries using Merge Risk Instructions and Security Risk Instructions in your review settings. Security architecture risk assessments require the Advanced plan or above.

✅ Automatic approvals, with your rules

Let CodeRabbit approve pull requests once required threads are resolved, pre-merge checks pass, and your risk limits are met. Enable Automatic approvals in review settings, with overrides for teams or file paths.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

This branch has not been deployed

No deployments
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.

5 participants