Skip to content
Merged
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
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## [0.22.3] - 2026-07-22
### Fixed

- `specs/004-memory`: `004-16-memory-type-aware-retrieval.md` and `004-16-shadow-memory-safety.md`
both claimed the `004-16` slot (issue #6636, found during the spec audit in #6629). Renamed
shadow-memory-safety to `004-19` and synced all 26 rustdoc citation sites across `crates/`
(MAGE/`TrajectoryRiskAccumulator`/shadow-memory citations moved to `004-19`; MemGuard/
type-aware-retrieval citations stay at `004-16`), plus the Obsidian wikilinks in
`004-memory/spec.md` and the cross-directory reference in
`083-memory-write-consent-gate/spec.md`. Also added an authoritative statement in
`specs/004-memory/spec.md` resolving which of three coexisting edge-strengthening mechanisms
(A-MEM `retrieval_count` boost, Benna-Fusi dual-rate `confidence_fast`/`confidence_slow`,
HeLa-Mem Hebbian `weight`) governs real graph traversal today, grounded in file:line
citations against `crates/zeph-memory/src/graph/` (issue #6635).
- `.github/workflows/ci-non-linux.yml`: the sharded macOS/Windows `Test` jobs intermittently
crashed with `fatal runtime error: stack overflow, aborting` on
`serve::agent_factory::tests::build_agent_factory_gates_trust_state_independently_per_session`
Expand Down
2 changes: 1 addition & 1 deletion crates/zeph-common/src/audit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

/// Signal type emitted by a sanitizer subsystem.
///
/// Variants correspond to the four signal classes defined in spec 004-16, FR-007.
/// Variants correspond to the four signal classes defined in spec 004-19, FR-007.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum AuditSignalType {
Expand Down
6 changes: 3 additions & 3 deletions crates/zeph-config/src/memory/persona.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ impl Default for TreeConfig {
}
}

// ── TrajectoryRiskAccumulator config (spec 004-16) ─────────────────────────────
// ── TrajectoryRiskAccumulator config (spec 004-19) ─────────────────────────────

fn validate_tra_nonneg_weight<'de, D>(deserializer: D) -> Result<f64, D::Error>
where
Expand Down Expand Up @@ -337,7 +337,7 @@ impl Default for TrajectorySeverityMultipliers {
}
}

/// Configuration for the MAGE trajectory risk accumulator (spec 004-16).
/// Configuration for the MAGE trajectory risk accumulator (spec 004-19).
///
/// Controls how per-turn safety signals accumulate into a session-level risk score
/// and when tool execution is blocked or escalated.
Expand Down Expand Up @@ -410,7 +410,7 @@ impl TrajectoryRiskAccumulatorConfig {
/// `[escalation_threshold, risk_threshold)` band becomes empty) — the hard block
/// (`is_blocked`) still works, so this is a degraded-but-safe misconfiguration, not a
/// security gap; validation exists to surface it instead of leaving it silent (critic
/// finding F4, spec 004-16).
/// finding F4, spec 004-19).
///
/// # Errors
///
Expand Down
2 changes: 1 addition & 1 deletion crates/zeph-config/src/memory/root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,7 @@ pub struct MemoryConfig {
/// key facts, and promotes them to the semantic tier in `zeph_key_facts`.
#[serde(default)]
pub episodic_consolidation: EpisodicConsolidationConfig,
/// MAGE shadow memory trajectory risk accumulator (spec 004-16).
/// MAGE shadow memory trajectory risk accumulator (spec 004-19).
///
/// Maintains a per-session rolling risk score fed by sanitizer audit signals.
/// When `shadow_memory.enabled = true`, tool execution is gated if cumulative
Expand Down
8 changes: 4 additions & 4 deletions crates/zeph-core/src/agent/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1031,7 +1031,7 @@ impl<C: Channel> Agent<C> {
self.runtime.lifecycle.turn_tool_calls = 0;

// Spec 050 §2: drain pending risk signals from executor layers before advancing.
// Also advance MAGE accumulator (spec 004-16 FR-009) and ingest mapped signals.
// Also advance MAGE accumulator (spec 004-19 FR-009) and ingest mapped signals.
{
use crate::agent::trajectory::{RiskSignal, VigilRiskLevel};
use zeph_memory::shadow::{AuditSignalType as MageSignal, Severity as MageSev};
Expand All @@ -1043,14 +1043,14 @@ impl<C: Channel> Agent<C> {
for code in pending {
let signal = RiskSignal::from_code(code);
self.services.security.trajectory.record(signal);
// Map RiskSignal to MAGE AuditSignalType + Severity (spec 004-16 FR-002, FR-007).
// Map RiskSignal to MAGE AuditSignalType + Severity (spec 004-19 FR-002, FR-007).
// Matching on the already-decoded `RiskSignal` (rather than the raw `code`)
// keeps this in sync with `RiskSignal::from_code`, the single source of truth
// for the code-to-meaning table. Only the four spec-004-16 signal classes have a
// for the code-to-meaning table. Only the four spec-004-19 signal classes have a
// MAGE equivalent; the remaining RiskSignal variants (OutOfScope, PiiRedaction,
// ToolFailure, HighCallRate, UnusualReadVolume, ToolPairTransition,
// ExfilReadThenSend, CredThenEgress, and VigilFlagged(Low)) are trajectory-only
// and intentionally not surfaced to MAGE (spec 004-16's four classes are a fixed
// and intentionally not surfaced to MAGE (spec 004-19's four classes are a fixed
// set; widening MAGE's mapping is a separate, spec-governed change, not part of
// #6561/F2's scope, which only fixes these two signals' TrajectorySentinel
// weight).
Expand Down
2 changes: 1 addition & 1 deletion crates/zeph-core/src/agent/state/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,7 @@ pub(crate) struct SecurityState {
/// `None` by default. When `Some`, `begin_turn()` calls `reset()` to clear per-turn state.
/// The same `Arc` must be passed to `ShellExecutor::with_risk_chain` at build time.
pub(crate) risk_chain_accumulator: Option<std::sync::Arc<zeph_tools::RiskChainAccumulator>>,
/// MAGE trajectory risk accumulator (spec 004-16).
/// MAGE trajectory risk accumulator (spec 004-19).
///
/// Per-session in-memory accumulator that ingests sanitizer audit signals with exponential
/// temporal decay and gates tool execution when cumulative risk exceeds `risk_threshold`.
Expand Down
6 changes: 3 additions & 3 deletions crates/zeph-core/src/agent/tests/mage_signal_mapping_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
//! Tests for #6272: `Agent::begin_turn` maps drained `RiskSignal`s to MAGE
//! `(AuditSignalType, Severity)` pairs by matching on the already-decoded `RiskSignal` enum
//! rather than re-deriving the mapping from the raw `u8` signal code. These tests pin the
//! resulting mapping table (spec 004-16 FR-002/FR-007) so a future refactor of either
//! resulting mapping table (spec 004-19 FR-002/FR-007) so a future refactor of either
//! `RiskSignal::from_code` or the MAGE match arm cannot silently desync the two.

use zeph_config::TrajectoryRiskAccumulatorConfig;
Expand Down Expand Up @@ -45,7 +45,7 @@ fn drain_one_code(agent: &mut Agent<MockChannel>, code: u8) {

/// Codes 1, 2, 6, 7 (`PolicyDeny`, `ExfiltrationRedaction`, `VigilFlagged(Medium)`,
/// `VigilFlagged(High)`) are the only `RiskSignal` variants with a MAGE equivalent
/// (spec 004-16 FR-002). Each must ingest into `mage_accumulator` with the exact
/// (spec 004-19 FR-002). Each must ingest into `mage_accumulator` with the exact
/// `AuditSignalType`/`Severity` pair documented at the match site in `begin_turn`.
#[test]
fn begin_turn_maps_known_risk_codes_to_mage_signals() {
Expand Down Expand Up @@ -88,7 +88,7 @@ fn begin_turn_maps_known_risk_codes_to_mage_signals() {
#[test]
fn begin_turn_no_mage_signal_for_trajectory_only_codes() {
// 10/11 (ExfilReadThenSend/CredThenEgress, #6561/F2) are trajectory-only too — MAGE's
// mapping stays at the fixed spec 004-16 four-class set; widening it is out of scope.
// mapping stays at the fixed spec 004-19 four-class set; widening it is out of scope.
for code in [3u8, 4, 5, 10, 11, 99] {
let mut agent = make_agent_with_mage();
drain_one_code(&mut agent, code);
Expand Down
8 changes: 4 additions & 4 deletions crates/zeph-core/src/agent/tool_execution/confirmation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
//! MAGE trajectory-risk confirmation and escalation gates.
//!
//! Covers the human-in-the-loop confirmation phase (`ConfirmationRequired` tool errors) and
//! the MAGE trajectory risk hard-block/soft-escalation gates (spec 004-16 FR-004–FR-006).
//! the MAGE trajectory risk hard-block/soft-escalation gates (spec 004-19 FR-004–FR-006).
//! Split out of `tier_loop.rs` — see that module for the orchestration entry point that calls
//! into these gates.

Expand All @@ -14,7 +14,7 @@ use crate::agent::Agent;
use crate::channel::Channel;

impl<C: Channel> Agent<C> {
/// Single batch-level human confirmation for the MAGE soft-escalation tier (spec 004-16
/// Single batch-level human confirmation for the MAGE soft-escalation tier (spec 004-19
/// FR-006).
///
/// Returns `Ok(true)` if the user declined — the tombstone and `[Cancelled]` notice are
Expand Down Expand Up @@ -112,7 +112,7 @@ impl<C: Channel> Agent<C> {
Ok(false)
}

/// Check MAGE trajectory risk gate (spec 004-16 FR-004, FR-005).
/// Check MAGE trajectory risk gate (spec 004-19 FR-004, FR-005).
///
/// Returns `Some((score, top_signals))` when the accumulator is blocked. Emits a security
/// event, increments `pre_execution_blocks`, and calls `record_block()` on the accumulator.
Expand Down Expand Up @@ -144,7 +144,7 @@ impl<C: Channel> Agent<C> {
Some((score, top))
}

/// Check MAGE trajectory risk soft-escalation gate (spec 004-16 FR-006).
/// Check MAGE trajectory risk soft-escalation gate (spec 004-19 FR-006).
///
/// Returns `true` when the accumulator's risk is in `[escalation_threshold,
/// risk_threshold)`. Emits a security event, increments `pre_execution_warnings`, and
Expand Down
4 changes: 2 additions & 2 deletions crates/zeph-core/src/agent/tool_execution/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,12 @@ struct ToolDispatchContext {
/// (`is_cacheable`) and the cache-store gate later in `apply_tier_results`, so the tier
/// loop never re-scans the registry per call or per tier (#5733 follow-up, M1).
mcp_tool_ids: std::collections::HashSet<String>,
/// MAGE trajectory risk gate (spec 004-16 FR-005).
/// MAGE trajectory risk gate (spec 004-19 FR-005).
///
/// When `Some((score, top_signals))`, all tool calls in this batch are blocked with
/// `ToolError::TrajectoryRiskExceeded`. Set when `mage_accumulator.is_blocked()` at dispatch time.
mage_blocked: Option<(f64, Vec<String>)>,
/// MAGE trajectory risk soft-escalation gate (spec 004-16 FR-006).
/// MAGE trajectory risk soft-escalation gate (spec 004-19 FR-006).
///
/// When `true`, the batch requires a single up-front human confirmation
/// (`Agent::confirm_mage_escalation`) before the normal tier execution loop runs — approval
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: 2026 Andrei G <bug-ops>
// SPDX-License-Identifier: MIT OR Apache-2.0

//! Tests for the MAGE trajectory-risk soft-escalation gate (spec 004-16 FR-006, #5956).
//! Tests for the MAGE trajectory-risk soft-escalation gate (spec 004-19 FR-006, #5956).
//!
//! `TrajectoryRiskAccumulator::should_escalate()`/`record_escalation()` existed but were never
//! queried by the agent loop before this fix. These tests exercise the wiring added to
Expand Down
8 changes: 4 additions & 4 deletions crates/zeph-core/src/agent/tool_execution/tier_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ use crate::channel::{Channel, StopHint, ToolStartEvent};
///
/// Returns `Some(TierLoopData)` synthesizing `ToolError::TrajectoryRiskExceeded` for every
/// call in the batch — bypassing `run_tier_execution_loop` entirely — when the hard-block
/// tier (`mage_blocked`, spec 004-16 FR-005) fired. Returns `None` when it did not, so the
/// tier (`mage_blocked`, spec 004-19 FR-005) fired. Returns `None` when it did not, so the
/// caller runs the normal tier execution loop (this also covers the soft-escalation tier,
/// spec 004-16 FR-006, which gates on a single batch-level confirmation but then falls
/// spec 004-19 FR-006, which gates on a single batch-level confirmation but then falls
/// through to the normal tier loop — see `Agent::confirm_mage_escalation` — so that
/// `check_trust`/`PermissionPolicy`/shadow-probe still apply per call; critic finding F1
/// caught an earlier version of this function that bypassed those gates for escalation too).
Expand Down Expand Up @@ -734,10 +734,10 @@ impl<C: Channel> Agent<C> {
// Inject active skill secrets before tool execution.
self.inject_active_skill_env();

// MAGE trajectory risk gate (spec 004-16 FR-004, FR-005).
// MAGE trajectory risk gate (spec 004-19 FR-004, FR-005).
// Extracted to keep prepare_tool_dispatch under the line limit.
let mage_blocked = self.check_mage_block();
// Soft-escalation tier (spec 004-16 FR-006): only meaningful when the hard block
// Soft-escalation tier (spec 004-19 FR-006): only meaningful when the hard block
// above did not already fire — the two threshold ranges never overlap, but the
// guard keeps this call site independent of that invariant.
let mage_escalate = mage_blocked.is_none() && self.check_mage_escalation();
Expand Down
4 changes: 2 additions & 2 deletions crates/zeph-memory/src/shadow/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: 2026 Andrei G <bug-ops>
// SPDX-License-Identifier: MIT OR Apache-2.0

//! MAGE shadow memory stream — trajectory-level risk accumulation (spec 004-16).
//! MAGE shadow memory stream — trajectory-level risk accumulation (spec 004-19).
//!
//! [`TrajectoryRiskAccumulator`] maintains a per-session rolling risk score by ingesting
//! [`AuditSignalType`] events from `zeph-sanitizer`. The score decays exponentially between
Expand Down Expand Up @@ -60,7 +60,7 @@ pub struct SignalEvent {
pub raw_score: f64,
}

/// Per-session trajectory risk accumulator (MAGE spec 004-16).
/// Per-session trajectory risk accumulator (MAGE spec 004-19).
///
/// Maintains a rolling `trajectory_risk` score in `[0.0, 1.0]` that accumulates safety
/// signals with exponential temporal decay. Designed to detect multi-turn attacks that
Expand Down
2 changes: 1 addition & 1 deletion crates/zeph-tools/src/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -529,7 +529,7 @@ pub enum ToolError {
reason: String,
},

/// Tool call blocked by the MAGE `TrajectoryRiskAccumulator` (spec 004-16).
/// Tool call blocked by the MAGE `TrajectoryRiskAccumulator` (spec 004-19).
///
/// Cumulative session risk exceeded `risk_threshold`. The agent loop receives the
/// score and the top contributing signals so it can explain the denial to the user.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ AND no tool call is blocked by shadow memory
> The bullet below previously claimed `TrajectoryRiskAccumulator` was renamed to `ShadowSentinel`
> during implementation. This is incorrect: both structs exist and coexist as **separate**
> components. `TrajectoryRiskAccumulator` (`crates/zeph-memory/src/shadow/mod.rs`, doc comment
> literally reads "Per-session trajectory risk accumulator (MAGE spec 004-16)") is this spec's
> literally reads "Per-session trajectory risk accumulator (MAGE spec 004-19)") is this spec's
> actual, unrenamed implementation — wired into `zeph-core` via `agent/builder.rs` and
> `agent/state/security.rs`. `ShadowSentinel` (`crates/zeph-core/src/agent/shadow_sentinel.rs`) is
> a distinct, additional defense-in-depth feature (an LLM pre-execution safety probe) that belongs
Expand Down
Loading
Loading