-
Notifications
You must be signed in to change notification settings - Fork 31
feat: gate MCP killswitch rollout #5927
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
danielkov
wants to merge
4
commits into
daniel/dno-986-test-prove-mcp-killswitch-lifecycle-and-denial-end-to-end
Choose a base branch
from
daniel/dno-987-docs-gate-and-operate-the-mcp-killswitch-rollout
base: daniel/dno-986-test-prove-mcp-killswitch-lifecycle-and-denial-end-to-end
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 3 commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
36a18a0
feat: gate MCP killswitch rollout
danielkov 3880661
chore(changeset): note MCP killswitch rollout gate
danielkov a1737bc
fix: enable killswitch rollout in serving tests
danielkov 55e866a
fix: address killswitch rollout review findings
danielkov File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "server": patch | ||
| --- | ||
|
|
||
| Authenticated MCP tool-call Killswitch evaluation now has a default-off server-side shadow and enforcement rollout gate, with audited break-glass deactivation and operational readiness guidance. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| # MCP Killswitch Evaluator Incidents | ||
|
|
||
| Use this runbook when authenticated MCP `tools/call` traffic is denied because | ||
| the authoritative Killswitch checkpoint cannot evaluate PostgreSQL state, or | ||
| when rollout telemetry indicates unsafe evaluator behavior. The registered M2 | ||
| policy fails closed in enforce mode. | ||
|
|
||
| ## Immediate response | ||
|
|
||
| 1. Declare an incident and assign an incident commander and database owner. | ||
| 2. Stop rollout cohort expansion. Do not create or edit prescriptions. | ||
| 3. Check `killswitch.evaluation.duration{outcome:evaluator_failure}`, p95/p99 | ||
| latency, PostgreSQL health, pool saturation, lock waits, and deploy changes. | ||
| 4. Compare hosted and private proxy outcomes. A one-surface failure can indicate | ||
| a mixed or unhealthy serving fleet. | ||
| 5. Determine whether lifecycle APIs can still reach PostgreSQL. Do not assume | ||
| that management access works merely because it bypasses evaluation. | ||
|
|
||
| Do not copy bearer tokens, external notes, user identifiers, organization | ||
| identifiers, server identifiers, or customer request bodies into logs, tickets, | ||
| dashboards, or comments. | ||
|
|
||
| ## Safe mitigation order | ||
|
|
||
| When lifecycle access is healthy and PostgreSQL is not saturated: | ||
|
|
||
| 1. Enumerate active and scheduled prescriptions through the restricted platform | ||
| management path. | ||
| 2. Lift or deactivate them with unique operation IDs. Deactivation remains | ||
| available while activation and change are rollout-gated. | ||
| 3. Verify each operation is audited and no active or scheduled prescriptions | ||
| remain. | ||
| 4. Switch affected cohorts to rollout off and verify every serving instance | ||
| received the mode and calls continue without an evaluator query. | ||
| 5. Repair the evaluator or database, restore shadow, observe, then re-enter the | ||
| normal rollout gate. | ||
|
|
||
| When PostgreSQL latency, pool saturation, or lock load is itself the incident, | ||
| switch affected cohorts off first and prove the local flag converged across the | ||
| fleet. After load stabilizes, enumerate and deactivate prescriptions in bounded, | ||
| paginated batches. If flag convergence cannot be proved, prefer deactivation | ||
| before any binary rollback. | ||
|
|
||
| When lifecycle access is also unavailable, use the approved out-of-band | ||
| configuration path to switch affected cohorts off from the locally cached | ||
| feature state. Confirm the new local state reached every serving instance. If | ||
| that path cannot converge, perform an explicitly approved full-fleet rollback. | ||
| A partial binary rollback is unsafe while prescriptions might remain active. | ||
|
|
||
| ## Break-glass rules | ||
|
|
||
| - Break-glass credentials are restricted to approved incident responders. | ||
| - Prefer deactivation. Activation or change during an evaluator incident needs | ||
| explicit incident-commander approval and an audited reason. | ||
| - Use a new operation ID for each intended mutation; never retry with altered | ||
|
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated
|
||
| input under an existing operation ID. | ||
| - Confirm organization authorization and tenant binding. Never infer authority | ||
| from email, API-key ownership, creator fields, or cached attribution. | ||
| - Record only prescription IDs and internal evidence in the restricted incident | ||
| system. Keep concrete customer identifiers out of repository artifacts. | ||
|
|
||
| Break-glass lifecycle operations still depend on PostgreSQL. They do not solve a | ||
| total database outage. | ||
|
|
||
| ## Recovery checks | ||
|
|
||
| Before leaving off mode: | ||
|
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated
|
||
|
|
||
| 1. PostgreSQL query latency, lock waits, CPU, and pool saturation are healthy. | ||
| 2. Every serving instance runs the approved checkpoint build. | ||
| 3. Hosted and private proxy coverage metrics are present. | ||
| 4. In a controlled environment, unmatched, matched, and evaluator-failure | ||
| outcomes are observable and bounded. | ||
| 5. Shadow traffic runs through a peak period without exceeding approved latency, | ||
| failure, coverage, or database-load thresholds. | ||
| 6. A controlled activation denies the next matching call, lift restores the | ||
| next call, and expiry follows database time. | ||
| 7. The incident commander and service owner approve cohort enforcement. | ||
|
|
||
| ## Rollback validation | ||
|
|
||
| After mitigation or binary rollback: | ||
|
|
||
| - no mixed serving version remains; | ||
| - no active or scheduled prescription can be silently bypassed; | ||
| - off mode performs no evaluator query; | ||
| - ordinary MCP traffic is restored; | ||
| - audit records exist for every break-glass mutation; | ||
| - monitors have returned to baseline; | ||
| - unsupported identities, resources, and methods remain outside the published | ||
| coverage boundary. | ||
|
|
||
| Do not add a TTL negative cache as an incident fix. A future summary or cache | ||
| must be transactionally updated with prescription activation and changes so the | ||
| next call observes authoritative state. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,146 @@ | ||
| # MCP Killswitch Rollout | ||
|
|
||
| This runbook gates the authenticated MCP tool-call Killswitch rollout. Do not | ||
| enable customer prescriptions until every serving instance has the approved | ||
| hosted and private checkpoint build. | ||
|
|
||
| ## Coverage boundary | ||
|
|
||
| This rollout covers `tools/call` for an authoritative, active organization user | ||
| and a canonical organization-owned MCP server on these surfaces: | ||
|
|
||
| - hosted MCP dispatch; | ||
| - private remote or tunnel proxy forwarding. | ||
|
|
||
| It does not cover anonymous sessions, API keys, assistants, chat-session end | ||
| users, inactive users, unattributed requests, legacy toolset-only routes, meta | ||
| or platform MCP, direct internal calls, or MCP methods other than `tools/call`. | ||
| Do not describe this rollout as controlling chat, hooks, model inference, | ||
| assistant work, or all AI activity. | ||
|
|
||
| ## Server-side modes | ||
|
|
||
| Two locally evaluated PostHog flags form the backend gate. Target them by the | ||
| organization distinct ID; do not put concrete organization identifiers in this | ||
| repository or rollout tickets. | ||
|
|
||
| | Mode | `mcp-killswitch-shadow` | `mcp-killswitch-enforce` | Serving behavior | Management behavior | | ||
| | ------- | ----------------------- | ------------------------ | ----------------------------------------------- | --------------------------------------------------- | | ||
| | Off | off | off | Skip derivation and evaluation | Create and edit unavailable; lift remains available | | ||
|
cubic-dev-ai[bot] marked this conversation as resolved.
|
||
| | Shadow | on | off | Evaluate and emit metrics, but always continue | Create and edit unavailable; lift remains available | | ||
| | Enforce | either | on | Apply matched denial and registered fail policy | Create and edit available | | ||
|
|
||
| Enforce takes precedence if both flags are on. Missing, unavailable, or | ||
| indeterminate local evaluation resolves to off. A successfully cached PostHog | ||
| result remains in effect until the local poller refreshes it, so every mode | ||
| change must be verified on every serving instance. Flag evaluation stays local | ||
| to the process; the serving path does not perform a remote PostHog request. | ||
|
|
||
| ## Fleet-readiness gate | ||
|
|
||
| Before enabling shadow for any production cohort: | ||
|
|
||
| 1. Identify the approved build containing both hosted and private checkpoints. | ||
| 2. Prove every server instance and tunnel/private forwarding instance runs that | ||
| build. Drain old instances; a mixed fleet is not ready. | ||
| 3. Confirm dashboards and monitors below are live and owned. | ||
| 4. Confirm no active production prescriptions exist before a mixed-version | ||
| deploy. If any exist, lift them before changing serving versions. | ||
| 5. Enable shadow for a controlled cohort. Then confirm both | ||
| `gram.mcp.killswitch.surface:hosted` and | ||
| `gram.mcp.killswitch.surface:private_proxy` appear in | ||
| `mcp.tool.call.killswitch_identity`. Off mode intentionally emits no coverage | ||
| observation. | ||
|
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated
|
||
| 6. Confirm `killswitch.evaluation.duration` emits | ||
| `gram.outcome:matched`, `gram.outcome:unmatched`, and | ||
| `gram.outcome:evaluator_failure` in a non-production or controlled test. Do | ||
| not create synthetic customer prescriptions. | ||
|
|
||
| Record the build, instance inventory, evidence links, reviewer, and timestamp in | ||
| the internal rollout record. Repository tests cannot prove fleet uniformity. | ||
|
|
||
| ## Shadow observation | ||
|
|
||
| Start with an internal or restricted cohort. Keep shadow active for at least one | ||
| normal traffic cycle and one peak period. Select quantitative thresholds from a | ||
| reviewed baseline; the values below are initial stop conditions, not permanent | ||
| SLOs. | ||
|
|
||
| Observe: | ||
|
|
||
| - p95 and p99 of `killswitch.evaluation.duration`; p99 must remain comfortably | ||
| below the one-second private checkpoint timeout and two-second hosted timeout; | ||
| - `outcome:evaluator_failure` ratio; stop on any sustained rate above 0.1% for | ||
| five minutes or any correlated serving incident; | ||
| - matched and unmatched query volume; histogram count is the authoritative | ||
| evaluator-query count; | ||
|
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated
|
||
| - active-user plus canonical-server coverage by `surface`; investigate any | ||
| increase in `unavailable`, `invalid_owner`, or unsupported classes; | ||
| - PostgreSQL query rate, latency, CPU, lock waits, and pool saturation compared | ||
| with the pre-shadow baseline. Stop on a sustained 10% load increase or pool | ||
| saturation unless the database owner approves a different bound. | ||
|
|
||
| Shadow suppresses transport denial, including fail-closed evaluator outcomes. | ||
| It does not suppress evaluator and coverage telemetry. Shadow evaluation is | ||
| synchronous by design so it measures the serving-path cost that enforce mode | ||
| will add; restrict cohort size and monitor end-to-end MCP request latency. | ||
|
|
||
| ## Datadog monitors | ||
|
|
||
| Create these in Datadog; monitor configuration is not stored in this repository. | ||
| Scope every monitor by environment. Evaluator duration has no surface attribute; | ||
| only the identity-coverage counter can be split by surface. Tune only after a | ||
| reviewed baseline. | ||
|
|
||
| 1. **Evaluator failure ratio**: count of | ||
| `killswitch.evaluation.duration{gram.outcome:evaluator_failure}` divided by | ||
| all outcomes over five minutes. Warn at 0.05%; alert at 0.1%. | ||
| 2. **Evaluator latency**: p95 and p99 of `killswitch.evaluation.duration`. Warn | ||
| at 250 ms; alert at 500 ms. | ||
| 3. **Coverage regression**: ratio of | ||
| `mcp.tool.call.killswitch_identity{gram.mcp.killswitch.identity_class:active_user,gram.mcp.killswitch.resource_class:canonical_server}` | ||
| to all observations, split by `gram.mcp.killswitch.surface`. Alert on a | ||
| reviewed baseline regression, not on an arbitrary global percentage. | ||
| 4. **Coverage unavailable**: any sustained | ||
| `gram.mcp.killswitch.identity_class:unavailable` or | ||
| `gram.mcp.killswitch.resource_class:unavailable`, plus | ||
| `gram.mcp.killswitch.resource_class:invalid_owner` above baseline. | ||
| 5. **PostgreSQL load**: query latency, pool saturation, lock waits, CPU, and | ||
| evaluator query volume. Correlate changes with shadow cohort expansion. | ||
|
|
||
| Metric dimensions are bounded server classes. Never add organization IDs, user | ||
| IDs, server IDs, notes, URLs, or error text as metric tags. | ||
|
|
||
| ## Enforce progression | ||
|
|
||
| Enable `mcp-killswitch-enforce` only after the fleet gate and shadow criteria | ||
| pass. Progress one restricted cohort at a time. For each cohort: | ||
|
|
||
| 1. Enable enforce while shadow remains on. | ||
| 2. Create a controlled prescription through the audited management path. | ||
| 3. Verify the next matching call is denied with the exact external note. No | ||
| restart, cache expiry, or propagation delay is expected. | ||
| 4. Verify a non-matching call continues. | ||
| 5. Lift the prescription and verify the next call continues. | ||
| 6. Exercise a bounded expiry and verify database time restores the next call. | ||
| 7. Review all monitors before expanding the cohort. | ||
|
|
||
| There is no TTL allow/deny cache. Any future summary optimization must be | ||
| updated transactionally with activation or change and visible on the next call. | ||
|
|
||
| ## Rollback criteria | ||
|
|
||
| Stop expansion and roll back the cohort to off when any of these occurs: | ||
|
|
||
| - a mixed serving fleet is detected; | ||
| - evaluator failures exceed the approved threshold; | ||
| - p99 approaches a checkpoint timeout; | ||
| - authoritative identity or canonical-resource coverage regresses; | ||
| - PostgreSQL load exceeds the approved bound; | ||
| - denial text or JSON-RPC behavior differs from acceptance evidence. | ||
|
|
||
| Before rolling binaries backward, stop cohort expansion, lift active | ||
| prescriptions, verify no active or scheduled prescriptions remain, switch the | ||
| cohort off, and then drain newer instances. Never leave active prescriptions | ||
| while an older instance can bypass evaluation. Use the evaluator incident | ||
| runbook for an active failure. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.