This runbook applies to releases that include the target-aware promptBridge
attempt ledger or the provider-free clientNotifications readiness contract.
The SDK is fail-closed during an upgrade: an old writer or an old mcp-host must
not run beside the new policy/ledger contract.
- Keep
PLUGIN_WORKLOAD_SDK_ENABLED=falsewhile the schema and binaries are upgraded. - Back up the database and record
schema_migrations, the SDK grant inventory, and the target policy hashes. Do not copy secret values into the backup report. - Apply the compatible CRDs and deploy the new Control API migration image.
- Stop old SDK writers before allowing the new Control API to accept writes.
pre-gate-syncfences bothworkflow-recipesandcontrol-apiat zero replicas, and the Control API Deployment usesstrategy: Recreate; this is an enforced no-overlap invariant, not a runbook-only coordination hint. - Run the migrations, including the no-op legacy repair and the forward-only
policy-review-provenance migration. A complete-looking JSON row is not
provenance: every active promptBridge row without a durable operator review
is re-fenced as
legacy_unreviewedand remains unusable. - Start the new Control API and verify the read-only
/api/v1/admin/plugin-workload-sdk/legacy-inventoryendpoint. A recipe can be enabled only when its inventory isactivationReady=trueand every declared policy has review provenance. - Deploy the matching gateway, Control UI, WRC, and mcp-host images. The capabilities probe must report readiness independently for promptBridge and clientNotifications.
- Have an operator review every
legacy_unreviewedrow in Control UI and explicitly save its ordered targets. The UI never activates a legacy row on page load, and no migration infers provider, model, slot, default, or order. - Enable the feature for a canary recipe, reconcile it, and verify:
- the eager host is Ready with a stable pod UID;
- every declared family has an active grant;
- a pre-provider credential/configuration failure closes both logical and
physical attempts as
failedand can revive the same idempotency key; - an ambiguous provider outcome remains
provider_unavailableand is not revivable; - a real client notification reaches its authorized target.
Disable the SDK feature and stop new reconciles. Do not roll back the database or deploy old binaries that cannot understand the attempt ledger. Keep the new Control API and repair policy rows forward; after the inventory is clean, re-enable a canary and repeat the readiness and ledger checks.
The SDK enforces platform-level per-minute rate limits configured through the
control-api-config ConfigMap. The per-run grant quotas maxRequestsPerRun
and maxNotificationsPerRun are deprecated: they are still accepted in grant
payloads for backward compatibility but are ignored by the authorizer. Each
per-minute limit is enforced over a trailing (rolling) 60-second window
computed from the invocation audit trail — there is no fixed reset tick.
Denied attempts are recorded and count toward the window, so traffic that
stays above the limit remains throttled while it keeps arriving; once the
caller slows below the limit the trailing window drains within ~60 seconds.
Unlike the deprecated per-run counters, a stepless recipe is never
permanently trapped.
| ENV variable | Default | Limits |
|---|---|---|
CONTROL_API_PLUGIN_SDK_NOTIFICATIONS_PER_MIN |
150 |
clientNotifications invocations per minute |
CONTROL_API_PLUGIN_SDK_PROMPTBRIDGE_PER_MIN |
120 |
promptBridge invocations per minute |
CONTROL_API_PLUGIN_SDK_REQUEST_BUCKET_PER_MIN |
600 |
shared SDK request bucket per minute |
CONTROL_API_PLUGIN_SDK_PREAUTH_PER_MIN |
600 |
pre-authorization checks per minute |
The defaults above equal the code defaults in control-api/src/config.ts and
are registered in both deploy/base/control-plane/configmaps.yaml and the
minikube overlay patch (strategic-merge over the base ConfigMap; omitted keys
inherit base values)
deploy/overlays/minikube/configmaps/control-api-config.yaml; a drift between
code and manifests fails the config deploy-mirror test.
Limits are tunable without a code deploy. Edit the key in the
control-api-config ConfigMap for the target environment, then restart the
Control API so the new environment is picked up:
# Preflight: confirm you are pointed at the intended cluster before mutating it.
kubectl config current-context # must print the target environment's context ("$K8S_CONTEXT")
kubectl --context "$K8S_CONTEXT" -n control-plane rollout restart deployment/control-api
kubectl --context "$K8S_CONTEXT" -n control-plane rollout status deployment/control-api --timeout=240sValues must be positive integers; the Control API fails loudly at startup on an invalid value. See also production.md and minikube.md for environment-specific deployment procedures.
Use the branch-owned profile and generated random ports, then run:
make minikube-pre-gate-sync GATE=plugin-workload-sdk-reviewThe pre-gate fences the workflow reconciler and Control API before database
migration, restores their prior replica counts on success or failure, and
restores the Control API only after the schema-first deployment has converged.
It fails when a
legacy_unreviewed promptBridge policy remains. It does not fail merely
because an operator intentionally disabled or revoked a grant; those states
are fenced at authorization time and are reported by the inventory for
operational review.