Finish KG-WP-0003: stream completeness and live qonto observation
Classify evidence as load-bearing or attributive, draft the emission-cadence declaration for Taxonomy, treat silence as a stream finding, keep completeness separate from record richness, forbid immune memory as a state plane, and make containment proposals reconstructable to their origin. Observe real qonto-assistant audit events; deny-class completeness stays unknown until the source publishes a heartbeat. Assistant: grok Assistant-Session: 01a05ef1-9e5a-70f2-b0ff-0b05d6b38ae9
This commit is contained in:
parent
c85646dc3c
commit
9daea96c43
35 changed files with 2023 additions and 138 deletions
|
|
@ -1,16 +1,26 @@
|
|||
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(
|
||||
{
|
||||
|
|
@ -39,13 +49,70 @@ RISK_WEIGHTS = {
|
|||
"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) -> PostureEvaluation:
|
||||
def evaluate(
|
||||
self,
|
||||
genome: SecurityGenome,
|
||||
observation: ImmuneObservation,
|
||||
*,
|
||||
stream: StreamAssessment | None = None,
|
||||
) -> PostureEvaluation:
|
||||
phenotype = self._derive_phenotype(genome, observation)
|
||||
assessment = self._assess(phenotype, observation)
|
||||
signals = self._build_signals(observation, assessment)
|
||||
return PostureEvaluation(phenotype=phenotype, assessment=assessment, signals=signals)
|
||||
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,
|
||||
|
|
@ -55,6 +122,17 @@ class PostureEvaluator:
|
|||
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:
|
||||
|
|
@ -95,6 +173,8 @@ class PostureEvaluator:
|
|||
self,
|
||||
phenotype: SecurityPhenotype,
|
||||
observation: ImmuneObservation,
|
||||
*,
|
||||
stream: StreamAssessment | None,
|
||||
) -> PostureAssessment:
|
||||
findings = set(phenotype.active_findings)
|
||||
|
||||
|
|
@ -115,6 +195,7 @@ class PostureEvaluator:
|
|||
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
|
||||
|
|
@ -124,15 +205,26 @@ class PostureEvaluator:
|
|||
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,
|
||||
|
|
@ -142,23 +234,47 @@ class PostureEvaluator:
|
|||
self,
|
||||
observation: ImmuneObservation,
|
||||
assessment: PostureAssessment,
|
||||
*,
|
||||
stream: StreamAssessment | None,
|
||||
) -> tuple[ImmuneSignal, ...]:
|
||||
if assessment.posture is PostureLevel.HEALTHY:
|
||||
return ()
|
||||
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)
|
||||
|
||||
if observation.source_system == "qonto-assistant":
|
||||
return (self._build_qonto_pilot_signal(observation, assessment),)
|
||||
|
||||
signal = ImmuneSignal(
|
||||
signal_id=f"sig:{observation.observation_id}",
|
||||
signal_kind=SignalKind.OBSERVATION_ALERT,
|
||||
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=assessment.rationale,
|
||||
summary=stream.reason,
|
||||
target_system=observation.source_system,
|
||||
findings=assessment.findings,
|
||||
metadata={"source_system": observation.source_system},
|
||||
findings=stream.findings,
|
||||
metadata={
|
||||
"stream_completeness": stream.completeness.value,
|
||||
"finding_class": "stream",
|
||||
},
|
||||
)
|
||||
return (signal,)
|
||||
|
||||
def _build_qonto_pilot_signal(
|
||||
self,
|
||||
|
|
@ -167,19 +283,22 @@ class PostureEvaluator:
|
|||
) -> 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=f"sig:{observation.observation_id}",
|
||||
signal_id=signal_id,
|
||||
signal_kind=SignalKind.POSTURE_HINT,
|
||||
posture=assessment.posture,
|
||||
summary=reason,
|
||||
|
|
@ -192,6 +311,10 @@ class PostureEvaluator:
|
|||
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",
|
||||
|
|
@ -199,11 +322,16 @@ class PostureEvaluator:
|
|||
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,
|
||||
},
|
||||
)
|
||||
|
||||
|
|
@ -224,19 +352,40 @@ 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:
|
||||
return (
|
||||
content = (
|
||||
"Healthy posture with tolerated deviations only: "
|
||||
+ ", ".join(tolerated_findings)
|
||||
)
|
||||
return "Healthy posture: observation is compatible with declared intent."
|
||||
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}"
|
||||
|
||||
detail = ", ".join(findings) if findings else "no active findings"
|
||||
tolerated = (
|
||||
f" Tolerated deviations still present: {', '.join(tolerated_findings)}."
|
||||
if tolerated_findings
|
||||
else ""
|
||||
)
|
||||
return 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 = (
|
||||
"This judgment rests on a stream I cannot vouch for "
|
||||
f"({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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue