from __future__ import annotations from collections.abc import Sequence from kings_guard.cadence import EmissionCadence from kings_guard.contracts import ( STREAM_FINDING_PREFIX, EffectorRequest, ImmuneObservation, ImmuneSignal, PostureAssessment, PostureEvaluation, PostureLevel, ReconciliationView, RestrictiveDirection, SecurityGenome, SecurityPhenotype, SignalKind, StreamAssessment, StreamCompleteness, StreamHeartbeat, ) from kings_guard.stream import evaluate_stream as assess_stream CRITICAL_FINDINGS = frozenset( { "tenant_mismatch", "undeclared_capability", "unexpected_egress_destination", "credential_exfil_probe", } ) ELEVATED_FINDINGS = frozenset( { "constraint_probe_or_boundary_trip", "unexpected_protocol", "unverified_identity_binding", "control_plane_error", } ) RISK_WEIGHTS = { "tenant_mismatch": 50, "undeclared_capability": 40, "unexpected_egress_destination": 50, "credential_exfil_probe": 60, "constraint_probe_or_boundary_trip": 25, "unexpected_protocol": 15, "unverified_identity_binding": 20, "control_plane_error": 20, } UNKNOWN_STREAM_REASON = ( "per-observation evaluation cannot vouch for stream completeness; " "the record in hand was scored for richness only" ) class PostureEvaluator: def evaluate( self, genome: SecurityGenome, observation: ImmuneObservation, *, stream: StreamAssessment | None = None, ) -> PostureEvaluation: phenotype = self._derive_phenotype(genome, observation) assessment = self._assess(phenotype, observation, stream=stream) signals = self._build_signals(observation, assessment, stream=stream) return PostureEvaluation( phenotype=phenotype, assessment=assessment, signals=signals, stream=stream, ) def evaluate_stream( self, observations: Sequence[ImmuneObservation], cadence: EmissionCadence, *, now: str, watching_since: str | None = None, heartbeats: Sequence[StreamHeartbeat] = (), reconciliation: ReconciliationView | None = None, ) -> StreamAssessment: return assess_stream( observations, cadence, now=now, watching_since=watching_since, heartbeats=heartbeats, reconciliation=reconciliation, ) def evaluate_with_stream( self, genome: SecurityGenome, observation: ImmuneObservation, cadence: EmissionCadence, *, now: str, observations: Sequence[ImmuneObservation] | None = None, watching_since: str | None = None, heartbeats: Sequence[StreamHeartbeat] = (), reconciliation: ReconciliationView | None = None, ) -> PostureEvaluation: stream = self.evaluate_stream( observations if observations is not None else (observation,), cadence, now=now, watching_since=watching_since, heartbeats=heartbeats, reconciliation=reconciliation, ) return self.evaluate(genome, observation, stream=stream) def _derive_phenotype( self, genome: SecurityGenome, observation: ImmuneObservation, ) -> SecurityPhenotype: findings: list[str] = [] tolerated: list[str] = [] if observation.decision is observation.decision.HEARTBEAT: return SecurityPhenotype( subject_id=observation.subject_id, tenant_id=observation.tenant_id, observed_capability=observation.capability, protocol=observation.protocol, decision=observation.decision, active_findings=(), tolerated_findings=(), ) if observation.tenant_id != genome.tenant_id: findings.append("tenant_mismatch") if observation.capability not in genome.permitted_capabilities: findings.append("undeclared_capability") if observation.protocol not in genome.permitted_protocols: findings.append("unexpected_protocol") if ( observation.egress_destination is not None and observation.egress_destination not in genome.permitted_egress ): findings.append("unexpected_egress_destination") if observation.identity_binding and observation.identity_binding != "verified_token": tolerance = _matching_tolerance( genome, "identity_binding", observation.identity_binding ) if tolerance is None: findings.append("unverified_identity_binding") else: tolerated.append(f"tolerated:{tolerance.tolerance_id}") if observation.decision.value == "deny" and observation.deny_reason == "credential_exfil": findings.append("credential_exfil_probe") elif observation.decision.value == "deny" and observation.deny_reason == "arg_constraint": findings.append("constraint_probe_or_boundary_trip") elif observation.decision.value == "error": findings.append("control_plane_error") return SecurityPhenotype( subject_id=observation.subject_id, tenant_id=observation.tenant_id, observed_capability=observation.capability, protocol=observation.protocol, decision=observation.decision, active_findings=tuple(findings), tolerated_findings=tuple(tolerated), ) def _assess( self, phenotype: SecurityPhenotype, observation: ImmuneObservation, *, stream: StreamAssessment | None, ) -> PostureAssessment: findings = set(phenotype.active_findings) posture = PostureLevel.HEALTHY if findings & CRITICAL_FINDINGS: posture = PostureLevel.INFLAMED elif findings: posture = PostureLevel.ELEVATED if observation.decision.value == "allow" and "unexpected_egress_destination" in findings: posture = PostureLevel.COMPROMISED risk_score = 5 + sum(RISK_WEIGHTS.get(item, 10) for item in findings) if posture is PostureLevel.ELEVATED: risk_score = max(risk_score, 40) elif posture is PostureLevel.INFLAMED: risk_score = max(risk_score, 80) elif posture is PostureLevel.COMPROMISED: risk_score = max(risk_score, 95) risk_score = min(risk_score, 100) # Richness of the record received — never of the stream. confidence_score = 70 if observation.policy_version is not None: confidence_score += 10 if observation.latency_ms is not None: confidence_score += 5 if observation.resource_scope: confidence_score += 5 confidence_score = min(confidence_score, 95) if stream is None: completeness = StreamCompleteness.UNKNOWN completeness_reason = UNKNOWN_STREAM_REASON else: completeness = stream.completeness completeness_reason = stream.reason rationale = _build_rationale( posture=posture, findings=phenotype.active_findings, tolerated_findings=phenotype.tolerated_findings, completeness=completeness, completeness_reason=completeness_reason, ) return PostureAssessment( posture=posture, risk_score=risk_score, confidence_score=confidence_score, stream_completeness=completeness, completeness_reason=completeness_reason, findings=phenotype.active_findings, tolerated_findings=phenotype.tolerated_findings, rationale=rationale, ) def _build_signals( self, observation: ImmuneObservation, assessment: PostureAssessment, *, stream: StreamAssessment | None, ) -> tuple[ImmuneSignal, ...]: signals: list[ImmuneSignal] = [] if assessment.posture is not PostureLevel.HEALTHY: if observation.source_system == "qonto-assistant": signals.append(self._build_qonto_pilot_signal(observation, assessment)) else: signals.append( ImmuneSignal( signal_id=f"sig:{observation.observation_id}", signal_kind=SignalKind.OBSERVATION_ALERT, posture=assessment.posture, summary=assessment.rationale, target_system=observation.source_system, findings=assessment.findings, metadata={"source_system": observation.source_system}, ) ) if stream is not None and stream.findings: signals.append(self._build_stream_signal(observation, assessment, stream)) return tuple(signals) def _build_stream_signal( self, observation: ImmuneObservation, assessment: PostureAssessment, stream: StreamAssessment, ) -> ImmuneSignal: return ImmuneSignal( signal_id=f"sig:stream:{observation.observation_id}", signal_kind=SignalKind.STREAM_COMPLETENESS, posture=assessment.posture, summary=stream.reason, target_system=observation.source_system, findings=stream.findings, metadata={ "stream_completeness": stream.completeness.value, "finding_class": "stream", }, ) def _build_qonto_pilot_signal( self, observation: ImmuneObservation, assessment: PostureAssessment, ) -> ImmuneSignal: if "credential_exfil_probe" in assessment.findings: action = "lock_actor_temporarily" direction: RestrictiveDirection = "reduce_authority" reason = ( "Observed a credential-exfil deny signal; qonto-assistant should activate " "its fast local loop lockout and preserve metadata-only evidence." ) else: action = "tighten_actor_scrutiny" direction = "require_step_up" reason = ( "Observed repeated policy-boundary pressure; qonto-assistant should tighten " "local scrutiny without delegating final authorization to kings-guard." ) signal_id = f"sig:{observation.observation_id}" return ImmuneSignal( signal_id=signal_id, signal_kind=SignalKind.POSTURE_HINT, posture=assessment.posture, summary=reason, target_system="qonto-assistant", findings=assessment.findings, effector_requests=( EffectorRequest( target_system="qonto-assistant", action=action, authority_boundary="advisory_only", reason=reason, requires_human_approval=False, originating_observation_id=observation.observation_id, originating_signal_id=signal_id, stream_completeness=assessment.stream_completeness, restrictive_direction=direction, ), EffectorRequest( target_system="state-hub", action="record_non_secret_incident_evidence", authority_boundary="metadata_only", reason="Preserve posture evidence without copying secret values.", requires_human_approval=False, originating_observation_id=observation.observation_id, originating_signal_id=signal_id, stream_completeness=assessment.stream_completeness, restrictive_direction="none", ), ), metadata={ "pilot_lane": "qonto-assistant", "resource_scope": observation.resource_scope or "unknown", "originating_observation_id": observation.observation_id, }, ) def _matching_tolerance( genome: SecurityGenome, field_name: str, field_value: str, ): for tolerance in genome.tolerances: if tolerance.match_field == field_name and tolerance.match_value == field_value: return tolerance return None def _build_rationale( *, posture: PostureLevel, findings: tuple[str, ...], tolerated_findings: tuple[str, ...], completeness: StreamCompleteness, completeness_reason: str, ) -> str: if posture is PostureLevel.HEALTHY: if tolerated_findings: content = "Healthy posture with tolerated deviations only: " + ", ".join( tolerated_findings ) else: content = "Healthy posture: observation is compatible with declared intent." else: detail = ", ".join(findings) if findings else "no active findings" tolerated = ( f" Tolerated deviations still present: {', '.join(tolerated_findings)}." if tolerated_findings else "" ) content = f"{posture.value.title()} posture driven by {detail}.{tolerated}" if completeness is StreamCompleteness.COMPLETE: stream_text = "Stream completeness is complete." elif completeness is StreamCompleteness.DEGRADED: stream_text = f"This judgment rests on a stream I cannot vouch for ({completeness_reason})." else: stream_text = ( "Stream completeness is unknown; confidence scores the record, " f"not the stream ({completeness_reason})." ) return f"{content} {stream_text}" def is_stream_finding(finding: str) -> bool: return finding.startswith(STREAM_FINDING_PREFIX)