Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 168 additions & 0 deletions core/capability_engine.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import asyncio
import hashlib
import importlib
import inspect
import json
Expand Down Expand Up @@ -4354,6 +4355,14 @@ def _action_expectation_for(
evidence = cls._str_list(source.get("required_evidence") or source.get("evidence_required"))
visible_effect = source.get("user_visible_effect") or source.get("visible_effect")
if not criteria and not evidence and not visible_effect:
default_expectation = cls._default_action_expectation_for(
skill_name,
params,
context,
ActionExpectation,
)
if default_expectation is not None:
return default_expectation
return None

return ActionExpectation(
Expand All @@ -4373,6 +4382,67 @@ def _action_expectation_for(
allow_partial=cls._bool_value(source.get("allow_partial"), default=True),
)

@classmethod
def _default_action_expectation_for(
cls,
skill_name: str,
params: dict[str, Any],
context: dict[str, Any],
expectation_cls: Any,
) -> Any | None:
if cls._bool_value((context or {}).get("disable_auto_action_expectation"), default=False):
return None
if cls._bool_value((params or {}).get("disable_auto_action_expectation"), default=False):
return None

normalized_skill = str(skill_name or "").strip().lower()
if normalized_skill == "memory_ops":
action = str((params or {}).get("action") or "").strip().lower()
memory_expectations = {
"core_append": ("core memory appended", "append"),
"core_replace": ("core memory replaced", "replace"),
}
if action not in memory_expectations:
return None
criterion, verb = memory_expectations[action]
block = str((params or {}).get("block") or "user").strip()
return expectation_cls(
objective=f"{verb} core memory block {block or 'user'}",
acceptance_criteria=[criterion],
required_evidence=["block", "sha256", "effect_verified"],
user_visible_effect=f"core memory {verb} is persisted and verified",
repair_hint=f"verify_memory_ops_{action}_effect",
allow_partial=False,
)

if normalized_skill != "file_operation":
return None

action = str((params or {}).get("action") or "").strip().lower()
path = str((params or {}).get("path") or "").strip()
destination = str((params or {}).get("destination") or "").strip()
file_expectations = {
"write": ("file written", ["path", "sha256", "effect_verified"]),
"append": ("file appended", ["path", "sha256", "effect_verified"]),
"patch": ("file patched", ["path", "sha256", "effect_verified"]),
"delete": ("path deleted", ["path", "effect_verified"]),
"move": ("path moved", ["path", "destination", "sha256", "effect_verified"]),
"copy": ("path copied", ["path", "destination", "sha256", "effect_verified"]),
}
if action not in file_expectations:
return None

criterion, evidence = file_expectations[action]
target = f"{path} -> {destination}" if destination else path
return expectation_cls(
objective=f"{action} file_operation effect for {target or 'requested path'}",
acceptance_criteria=[criterion],
required_evidence=evidence,
user_visible_effect=f"filesystem {action} is observable and verified",
repair_hint=f"verify_file_operation_{action}_effect",
allow_partial=False,
)

@classmethod
def _apply_action_expectation_result(
cls,
Expand Down Expand Up @@ -4421,8 +4491,106 @@ def _apply_action_expectation_result(
payload["ok"] = checked.ok
if not checked.ok and checked.failure_reason and not payload.get("error"):
payload["error"] = checked.failure_reason
expectation_receipt_id = cls._emit_action_expectation_receipt(
skill_name,
payload,
expectation,
checked,
)
if expectation_receipt_id:
payload["expectation_receipt_id"] = expectation_receipt_id
payload["verification_evidence"]["expectation_receipt_id"] = expectation_receipt_id
if isinstance(payload.get("expectation_verdict"), dict):
payload["expectation_verdict"]["receipt_id"] = expectation_receipt_id
return payload

@staticmethod
def _action_expectation_digest(payload: dict[str, Any]) -> str:
try:
encoded = json.dumps(
payload,
sort_keys=True,
separators=(",", ":"),
default=str,
).encode("utf-8")
except (TypeError, ValueError):
encoded = str(payload).encode("utf-8", errors="replace")
return hashlib.sha256(encoded).hexdigest()

@classmethod
def _emit_action_expectation_receipt(
cls,
skill_name: str,
result: dict[str, Any],
expectation: Any,
checked: Any,
) -> str | None:
verdict = checked.verification_evidence.get("expectation_verdict", {})
if not isinstance(verdict, dict) or not verdict:
return None

try:
from core.runtime.receipts import ToolExecutionReceipt, get_receipt_store

receipt = ToolExecutionReceipt(
cause=str(getattr(expectation, "objective", "") or skill_name)[:240],
tool=skill_name,
status=str(checked.status.value),
output_digest=cls._action_expectation_digest(
{
"skill": skill_name,
"status": checked.status.value,
"ok": bool(result.get("ok", False)),
"verdict": verdict,
}
),
verification_evidence={
"expectation_verdict": verdict,
"original_receipt_id": checked.receipt_id,
"failure_reason": checked.failure_reason,
},
metadata={
"source": "capability_engine.action_expectation",
"expectation_objective": str(
getattr(expectation, "objective", "") or skill_name
)[:240],
"expectation_next_step": str(verdict.get("next_step") or "")[:240],
"passed": bool(verdict.get("passed", False)),
},
)
emitted = get_receipt_store().emit(receipt)
if not bool(verdict.get("passed", False)):
try:
from core.resilience.fault_taxonomy import get_fault_registry

missing = list(verdict.get("missing_criteria") or []) + list(
verdict.get("missing_evidence") or []
)
get_fault_registry().record_fault(
"PASSF-ACTION-SHALLOW-SUCCESS",
"capability_engine",
details=(
f"{skill_name} expectation downgraded before verified success; "
f"missing={missing[:6]}"
),
recovered=True,
recovery_time_s=0.0,
)
except (ImportError, AttributeError, RuntimeError, TypeError, ValueError) as fault_exc:
_record_capability_degradation(
fault_exc,
action="returned expectation-downgraded result after fault occurrence recording failed",
severity="warning",
)
return emitted.receipt_id
except (ImportError, OSError, RuntimeError, TypeError, ValueError) as exc:
_record_capability_degradation(
exc,
action="returned expectation verdict after durable receipt emit failed",
severity="warning",
)
return None

@staticmethod
def _outer_retry_disabled(
skill_name: str,
Expand Down
158 changes: 158 additions & 0 deletions core/resilience/fault_taxonomy.py
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,164 @@ def _register_builtin_faults(self) -> None:
recovery=RecoveryStrategy.GRACEFUL_DEGRADATION, mttr_seconds=0,
blast_radius="Logged and monitored; function continues (log+continue mode)",
),
FaultDefinition(
fault_id="PASSF-ACTION-SHALLOW-SUCCESS",
name="Shallow action reports success",
description="An action fires and returns a technically true success "
"without satisfying the user's implied acceptance "
"criteria or preserving effect evidence.",
domain=FaultDomain.TOOL_EXECUTION,
severity=FaultSeverity.CRITICAL,
probability=FaultProbability.PROBABLE,
detection=DetectionDifficulty.LOW,
recovery=RecoveryStrategy.RETRY_WITH_BACKOFF,
mttr_seconds=30,
blast_radius="User-facing task appears complete while the useful "
"work is missing or too shallow to rely on.",
runbook="docs/runbooks/pass-f-maturity-risks.md",
),
FaultDefinition(
fault_id="PASSF-FALSE-HEALTH",
name="False health/readiness signal",
description="A readiness, liveness, or proof-health path reports "
"green while the live user path remains blocked.",
domain=FaultDomain.OBSERVABILITY,
severity=FaultSeverity.CRITICAL,
probability=FaultProbability.PROBABLE,
detection=DetectionDifficulty.LOW,
recovery=RecoveryStrategy.QUARANTINE,
mttr_seconds=60,
blast_radius="Operators trust a green signal that does not match "
"actual demo or daily-use readiness.",
runbook="docs/runbooks/pass-f-maturity-risks.md",
),
FaultDefinition(
fault_id="PASSF-RESOURCE-SPAWN-LOOP",
name="Resource spawn loop",
description="Model, memory, or worker orchestration repeatedly "
"spawns work under pressure instead of admitting, "
"backing off, or degrading explicitly.",
domain=FaultDomain.RESOURCE,
severity=FaultSeverity.CRITICAL,
probability=FaultProbability.OCCASIONAL,
detection=DetectionDifficulty.MODERATE,
recovery=RecoveryStrategy.CIRCUIT_BREAKER,
mttr_seconds=45,
blast_radius="GPU/RAM pressure cascades into stalled requests, "
"wedged workers, or unreliable boot.",
runbook="docs/runbooks/pass-f-maturity-risks.md",
),
FaultDefinition(
fault_id="PASSF-DESKTOP-PERMISSION-DRIFT",
name="Desktop permission drift",
description="Desktop, Chrome, accessibility, camera, microphone, "
"or browser-control permissions drift after boot and "
"surface only as shallow action failure.",
domain=FaultDomain.TOOL_EXECUTION,
severity=FaultSeverity.CRITICAL,
probability=FaultProbability.OCCASIONAL,
detection=DetectionDifficulty.MODERATE,
recovery=RecoveryStrategy.GRACEFUL_DEGRADATION,
mttr_seconds=120,
blast_radius="Visible computer-use workflows cannot be demoed or "
"completed despite nominal capability registration.",
runbook="docs/runbooks/pass-f-maturity-risks.md",
),
FaultDefinition(
fault_id="PASSF-REPAIR-STORM",
name="Self-repair storm",
description="Autonomy or self-repair loops continue patching, "
"retrying, or re-planning without cooldown, budget, "
"causal progress, or operator-visible stop condition.",
domain=FaultDomain.AGENCY,
severity=FaultSeverity.CRITICAL,
probability=FaultProbability.REMOTE,
detection=DetectionDifficulty.LOW,
recovery=RecoveryStrategy.CIRCUIT_BREAKER,
mttr_seconds=60,
blast_radius="Repair behavior consumes resources and can make "
"the original fault harder to diagnose.",
runbook="docs/runbooks/pass-f-maturity-risks.md",
),
FaultDefinition(
fault_id="PASSF-STALE-OBLIGATION",
name="Stale obligation blocks current work",
description="Memory, prompt, or task-state residue from an older "
"objective keeps steering unrelated current work.",
domain=FaultDomain.MEMORY,
severity=FaultSeverity.MARGINAL,
probability=FaultProbability.PROBABLE,
detection=DetectionDifficulty.LOW,
recovery=RecoveryStrategy.AUTOMATIC_FALLBACK,
mttr_seconds=30,
blast_radius="Aura acts federated and distracted instead of "
"unified around the user's current objective.",
runbook="docs/runbooks/pass-f-maturity-risks.md",
),
FaultDefinition(
fault_id="PASSF-NEURAL-STREAM-FLOOD",
name="Neural stream flood hides state",
description="High-volume internal streams, events, or logs drown "
"out the actionable user-visible state needed for "
"debugging and daily operation.",
domain=FaultDomain.OBSERVABILITY,
severity=FaultSeverity.MARGINAL,
probability=FaultProbability.PROBABLE,
detection=DetectionDifficulty.MODERATE,
recovery=RecoveryStrategy.GRACEFUL_DEGRADATION,
mttr_seconds=30,
blast_radius="Operators see noise rather than crisp state, "
"blockers, receipts, and next actions.",
runbook="docs/runbooks/pass-f-maturity-risks.md",
),
FaultDefinition(
fault_id="PASSF-VISIBLE-WEB-PROOF-ACCESS",
name="Visible web proof inaccessible",
description="Visible Chrome or web-interlocutor proof depends on "
"a browser profile, extension, or session that is not "
"available to the active runtime.",
domain=FaultDomain.TOOL_EXECUTION,
severity=FaultSeverity.CRITICAL,
probability=FaultProbability.OCCASIONAL,
detection=DetectionDifficulty.HIGH,
recovery=RecoveryStrategy.MANUAL_INTERVENTION,
mttr_seconds=300,
blast_radius="External web proof and demo-critical browser "
"workflows remain honestly blocked.",
runbook="docs/runbooks/pass-f-maturity-risks.md",
),
FaultDefinition(
fault_id="PASSF-PROOF-ARTIFACT-CONTAMINATION",
name="Proof artifact contamination",
description="A proof, certification, or report consumes stale, "
"fixture-backed, hardcoded, or cross-run artifact "
"data as if it were fresh live evidence.",
domain=FaultDomain.OBSERVABILITY,
severity=FaultSeverity.CRITICAL,
probability=FaultProbability.REMOTE,
detection=DetectionDifficulty.LOW,
recovery=RecoveryStrategy.QUARANTINE,
mttr_seconds=120,
blast_radius="Certification can overstate maturity and hide "
"runtime regressions until live demo.",
runbook="docs/runbooks/pass-f-maturity-risks.md",
),
FaultDefinition(
fault_id="PASSF-SEMANTIC-REVIEW-GAP",
name="Semantic review gap",
description="Mechanical closeout, line counting, or path hashing "
"is mistaken for semantic review of code behavior, "
"runtime contracts, and user-facing obligations.",
domain=FaultDomain.GOVERNANCE,
severity=FaultSeverity.CRITICAL,
probability=FaultProbability.PROBABLE,
detection=DetectionDifficulty.LOW,
recovery=RecoveryStrategy.MANUAL_INTERVENTION,
mttr_seconds=600,
blast_radius="Remaining code debt is hidden behind a green "
"mechanical audit surface.",
runbook="docs/runbooks/pass-f-maturity-risks.md",
),
]
for defn in builtins:
self._definitions[defn.fault_id] = defn
Expand Down
Loading
Loading