Repository navigation
Fix review findings: console 403, governance safety, consumer supervision, learning-loop plumbing - #74
Merged
Conversation
PR #72 made the console act as VITE_OPERATOR_NAME, defaulting to "demo-operator", but that name was in no role in rbac_policy.yaml and the console image never set the variable. Every Approve, Reject and "Draft a runbook with AI" click from a stock build was a 403. Grant demo-operator [operator, approver], pin it as a build ARG in Dockerfile.frontend so the identity is explicit and overridable, and add a test that reads the default from api.ts and the Dockerfile and checks it against the policy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… make graduation reversible
- POST /playbooks took no identity: anyone reaching governance could overwrite
a seed playbook with hitl_mode=auto and skip both the HITL gate and the
AI-proposal review. It now requires ?registered_by= with the new `register`
permission (coe-admin), refuses AUTO unless the actor may also graduate, and
writes an audit record.
- A human's approve/reject was never written to the audit log. It is now.
- /decide accepted any string (a typo was stored and later reported as a
timeout) and could re-decide a closed request. It now takes approved|rejected
only (422), checks the matching approve/reject permission, and returns 409
unless the request is still pending. Proposal approve/reject get the same
pending check, so a rejected AI runbook can't be approved into the registry.
- A request action-service stopped waiting for stayed "pending" forever and the
console kept offering a dead Approve button. Action now expires it on timeout
(POST /approvals/{id}/expire, requester only, best effort).
- Graduation counted dry-run successes (DryRunRemediator + AlwaysHealthyChecker
always succeed), so three approved simulations could make a playbook AUTO for
a later real cluster. TrainingRecord now carries the outcome's mode (additive,
payload-only) and simulated successes don't count unless
GRADUATION_COUNT_SIMULATED is set.
- Nothing ever demoted an auto playbook. Feedback now calls the new
POST /playbooks/{id}/demote when an auto playbook fails or rolls back.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…op losing suppressions - Consumer threads died for good on any unhandled exception (a governance restart surfacing as a ConnectError from the HTTP gate, a Postgres blip in an audit write). /health is static, so Kubernetes never restarted the pod and the service sat "ready" processing nothing. common/supervise.py restarts a crashed worker with capped backoff; every service's consumer (and the correlation flusher, and all four read-model topic threads) now runs under it. At-least-once re-serves the in-flight entry and the DLQ bounds a poison one. - The Helm Deployments used the default RollingUpdate, so each upgrade briefly ran two pods consuming under the same consumer name - the exact overlap ADR-033's replicas:1 assumption rules out. They are now strategy: Recreate. - CorrelationEngine kept suppressed situations in a single slot, so a flush_all() that suppressed two buckets reported one. It is a queue now and the drain loops until empty. - The read model had no lock while four consumer threads and the request threads mutated and pruned it. It now takes an RLock around every mutation and read. It is also occurrence-aware: a recurrence of a finished incident (same id, newer first_seen) starts a clean card instead of inheriting the last run's outcome and timeline; a late detection/diagnosis never moves a card's status backwards; and an event from an earlier occurrence is ignored. - Fix the stale values.yaml comment claiming values-live turns the sandbox on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ries detection switch - Signatures hashed the raw list of member fingerprints, so the same fault on the same service got a different signature depending on how many anomalous samples landed in the window (verified: 6 vs 7 samples -> two unrelated ids). Everything keyed on the signature - suppression, graduation evidence, RCA reliability, the author's past decisions - only matched by luck. It is now the hash of the SET of series involved. - Suppression cleared on a single success (reliability 1/1 = 1.0). It now needs RELIABILITY_SUPPRESS_MIN_SAMPLES (default 3) labelled outcomes. The service also never passed RELIABILITY_SUPPRESS_THRESHOLD to the engine - dead config, now wired. - RCA's reliability boost was one number per signature added equally to every runbook-bearing hypothesis: a uniform offset that could never change their order. It is now per (signature, runbook), so a proven runbook can overtake, with confidence as the tiebreak at the 1.0 clamp. It was also built once at boot; ReliabilityProvider re-reads the training store every RELIABILITY_REFRESH_SECONDS (default 60). - RobustCorrelator pooled every service's samples of a metric into one baseline, so a 10 -> 45 CPU jump on a quiet service scored ~0 beside one idling at 80. New CORRELATION_ROBUST_KEY_BY=series keeps one baseline per (metric, service). Opt-in, default unchanged: per-series windows fill ~5x slower, so the live warm-up needs re-tuning against a replay first. - Every top-of-hour opened an empty seasonal bucket and the robust detector scored 0 until it re-warmed. A cold bucket now scores against the fullest warm bucket of the same series. - CorrelationEngine.reset() rebuilt the correlator from three kwargs, silently resetting window size / seasonal buckets to defaults. Correlators now expose clone_empty() with their full configuration. Correlation's own reliability map (what drives suppression) is still loaded only at boot, deliberately - see the PR description: suppressing a situation drops it before RCA/action, so a live-refreshing map would stop remediating faults the system has learned to fix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… happened - Scale was undone with the inverse delta, which is wrong whenever the forward step was clamped: 9 replicas + 2 clamps to 10, and 10 - 2 = 8, one below the start. KubernetesRemediator now records the pre-scale count and rollback restores exactly that. - An unhealthy result was reported ROLLED_BACK even when the playbook had no rollback steps (restart-pod ships with none) or the rollback itself failed. Those are now FAILURE with "unhealthy:no-rollback" / "unhealthy:rollback-failed". - The post-remediation readiness check compared ready == replicas only, which the OLD ReplicaSet satisfies before the controller has observed the patch. It now also requires observedGeneration >= generation and updatedReplicas == replicas (both read defensively, so fakes/older clients are unaffected). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…uv:latest - geist was never imported (the fonts are vendored in frontend/src/fonts) but pulled in next + sharp - ~940 lockfile lines and the Dependabot "critical" alerts that never applied to this Vite app. - proposePlaybook (the sync draft call) had no caller left in the console since the draft-async + Agent Activity flow replaced it. The backend endpoint stays. - OutcomeReason claimed to be the exact health_after vocabulary but was missing preflight-failed, interrupted:unknown and the denied:unsafe-* reasons; add those and the two new unhealthy:* reasons. - Pin ghcr.io/astral-sh/uv to 0.11.28 in both Dockerfiles instead of :latest. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: 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. Comment |
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Fixes from a whole-repo review of
master@1ca3d3e. One commit per area. Every behaviour change has a regression test, and each default was picked so that nothing changes unless it was actually broken.Demo blocker
VITE_OPERATOR_NAME, which defaults todemo-operator. That name had no role inrbac_policy.yaml, and the console image never set the variable. This PR grantsdemo-operatorthe operator and approver roles and pins the name as a build ARG. A new test reads the default fromapi.tsand the Dockerfile and checks it against the policy.Governance safety
POST /playbookshad no permission check. Anyone who could reach governance could overwrite a seed playbook withhitl_mode: auto, skipping both the HITL gate and the AI-proposal review. It now requires?registered_by=with the newregisterpermission (coe-admin). It refusesautounless the actor may alsograduate, and writes an audit record./decideaccepts onlyapprovedorrejected. Anything else is a 422; previously a typo was stored and later reported asaborted:timeout. The decider needs the matching permission, and deciding a request that isn't pending is a 409. Proposal approve/reject get the same pending check, so a rejected AI runbook can't later be approved into the live registry.pendingforever and the console offered an Approve button that no longer did anything. Action now callsPOST /approvals/{id}/expireon timeout (only the requester may expire, best effort).TrainingRecordnow carriesmode(an optional field in the JSON payload, so no migration). Simulated successes don't count unlessGRADUATION_COUNT_SIMULATEDis set.POST /playbooks/{id}/demotewhen an auto playbook fails or rolls back.Reliability
/healthis static, so the pod was never restarted.common/supervise.pynow restarts a crashed worker with capped backoff. Every consumer runs under it, plus the correlation flusher and all four read-model threads.strategy: Recreate. The default RollingUpdate briefly ran two pods consuming under the same consumer name during every upgrade, which is exactly what ADR-033's replicas:1 assumption rules out.Learning loop
RELIABILITY_SUPPRESS_MIN_SAMPLES(default 3).RELIABILITY_SUPPRESS_THRESHOLDwas never passed to the engine (dead config); it is now wired.CORRELATION_ROBUST_KEY_BY=series. The robust detector pooled every service's samples of a metric into one baseline, so a 10→45 CPU jump on a quiet service scored ~0 next to a service idling at 80. The new mode keeps one baseline per metric and service. It's off by default: per-service windows fill about 5× slower, so the live warm-up needs re-tuning against a replay first.engine.reset()keeps the configuration. It used to silently reset window size and seasonal buckets to their defaults.Remediation
ROLLED_BACK. No rollback steps (restart-pod ships with none) now givesunhealthy:no-rollback, and a failed rollback givesunhealthy:rollback-failed.observedGeneration ≥ generationandupdatedReplicas == replicas, so the old ReplicaSet can't pass the check before the patch has been observed.Cleanup
geistnpm package. It was never imported, but it dragged innextandsharp(about 940 lockfile lines and the Dependabot alerts).proposePlaybookcall.OutcomeReasontype with the reasons it was missing.ghcr.io/astral-sh/uv:0.11.28instead of:latest(tag verified on the registry).Correlation's reliability map, which drives suppression, is still loaded only at boot, on purpose. A suppressed situation is dropped before RCA and action, so the fix is never applied. Suppression rarely fired before, because signatures were unstable and the map was boot-only. With stable signatures it will now fire after
RELIABILITY_SUPPRESS_MIN_SAMPLESsuccesses following a correlation restart. I'd recommend changing suppression to mean "auto-handle quietly" (skip paging a human but still remediate) rather than "drop". That's a product decision, so it isn't in this PR.Not changed
deploy/playbookslackssymptomswas wrong: both copies have them and a test pins it. The two copies still differ in their steps.Verification
uv run pytest -m "not postgres and not kafka": 831 passed (up from 782).ruff checkandruff format --checkare clean.npm run buildpasses (contrast gate, tsc, vite).helm lintpasses withvalues-live.yaml, and all 7 service Deployments renderRecreate.🤖 Generated with Claude Code