Shared memory and context tools for agentic work.
Code Rooms
use std::collections::{BTreeMap, BTreeSet};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::policy::{ActionId, Effect, Ingress, RiskClass};
use crate::{digest_canonical, CanonicalError};
pub const ACTION_CATALOG_SCHEMA: &str = "m1nd-action-catalog-v1";
pub const ACTION_CATALOG_DIGEST_DOMAIN: &str = "m1nd-action-catalog-v1";
pub const M1ND10_ACTION_CATALOG_VERSION: &str = "m1nd10-g2-2026-07-18.4";
/// The minimum positive authority path an action may use.
///
/// `SafetyOnly` is deliberately not a positive authority. It can only select
/// immutable negative safety effects and can never authorize an ordinary or
/// sovereign mutation.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum AuthorityFloor {
Ordinary,
ScopedGrantA2,
PositiveSovereign,
ServiceIdentity,
SafetyOnly,
}
/// One semantic action, independent of the transport method that reaches it.
/// `complete_effects` contains both direct effects and effects reachable
/// transitively through helpers, subprocesses, hooks, or background work.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ActionCatalogEntryV1 {
pub action: ActionId,
pub ingresses: BTreeSet<Ingress>,
pub complete_effects: BTreeSet<Effect>,
pub risk_class: RiskClass,
pub authority_floor: AuthorityFloor,
pub struct ActionCatalogV1 {
pub schema: String,
pub catalog_version: String,
pub entries: Vec<ActionCatalogEntryV1>,
pub catalog_digest: String,
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ActionCatalogValidation {
pub entry_count: usize,
pub action_count: usize,
pub ingress_count: usize,
pub computed_catalog_digest: String,
#[derive(Debug, Error)]
pub enum ActionCatalogError {
#[error("unsupported action catalog schema '{actual}'")]
Schema { actual: String },
#[error("required field '{field}' is empty")]
EmptyRequired { field: &'static str },
#[error("action catalog must declare at least one entry")]
NoEntries,
#[error("action id '{action}' is not a lowercase dotted semantic id")]
InvalidSemanticActionId { action: ActionId },
#[error("duplicate action catalog entry '{action}'")]
DuplicateAction { action: ActionId },
#[error("catalog entries are not strictly sorted: '{previous}' precedes '{current}'")]
EntriesNotStrictlySorted {
previous: ActionId,
current: ActionId,
},
#[error("action '{action}' has no reachable ingress")]
EmptyIngresses { action: ActionId },
#[error("action '{action}' has no complete effects")]
EmptyEffects { action: ActionId },
#[error("SAFETY_ONLY action '{action}' contains non-safety effect {effect:?}")]
NonSafetyEffectInSafetyAction { action: ActionId, effect: Effect },
#[error("non-safety action '{action}' contains negative safety effect {effect:?}")]
SafetyEffectOutsideSafetyAction { action: ActionId, effect: Effect },
#[error("action '{action}' contains SOVEREIGN_MUTATION without POSITIVE_SOVEREIGN floor")]
SovereignEffectWithoutPositiveFloor { action: ActionId },
#[error("POSITIVE_SOVEREIGN action '{action}' omits SOVEREIGN_MUTATION")]
PositiveFloorWithoutSovereignEffect { action: ActionId },
#[error("POSITIVE_SOVEREIGN action '{action}' must be CRITICAL risk")]
PositiveSovereignMustBeCritical { action: ActionId },
#[error("SAFETY_ONLY action '{action}' must be CRITICAL risk")]
SafetyOnlyMustBeCritical { action: ActionId },
#[error("CRITICAL action '{action}' requires POSITIVE_SOVEREIGN or SAFETY_ONLY floor")]
CriticalActionHasInsufficientFloor { action: ActionId },
#[error("ORDINARY action '{action}' contains elevated effect {effect:?}")]
ElevatedEffectInOrdinaryAction { action: ActionId, effect: Effect },
#[error("EXECUTABLE_REPLACEMENT action '{action}' requires POSITIVE_SOVEREIGN floor")]
ExecutableReplacementWithoutPositiveFloor { action: ActionId },
#[error("SERVICE_IDENTITY action '{action}' has no service-capable ingress")]
ServiceIdentityWithoutServiceIngress { action: ActionId },
#[error("catalog digest mismatch: expected {expected}, observed {observed}")]
CatalogDigestMismatch { expected: String, observed: String },
#[error(transparent)]
Canonical(#[from] CanonicalError),
impl ActionCatalogV1 {
/// Compute the catalog self-hash while omitting only `catalog_digest`.
pub fn compute_catalog_digest(&self) -> Result<String, CanonicalError> {
let mut value = serde_json::to_value(self)?;
let object = value
.as_object_mut()
.expect("ActionCatalogV1 always serializes as an object");
object.remove("catalog_digest");
digest_canonical(ACTION_CATALOG_DIGEST_DOMAIN, &value)
pub fn seal(&mut self) -> Result<(), CanonicalError> {
self.catalog_digest = self.compute_catalog_digest()?;
Ok(())
/// Validate deterministic shape and authority/effect separation without
/// executing or authorizing any action.
pub fn validate(&self) -> Result<ActionCatalogValidation, ActionCatalogError> {
if self.schema != ACTION_CATALOG_SCHEMA {
return Err(ActionCatalogError::Schema {
actual: self.schema.clone(),
});
require_non_empty("catalog_version", &self.catalog_version)?;
require_non_empty("catalog_digest", &self.catalog_digest)?;
if self.entries.is_empty() {
return Err(ActionCatalogError::NoEntries);
let mut actions = BTreeMap::new();
let mut all_ingresses = BTreeSet::new();
let mut previous: Option<&ActionId> = None;
for entry in &self.entries {
validate_semantic_action_id(&entry.action)?;
if entry.ingresses.is_empty() {
return Err(ActionCatalogError::EmptyIngresses {
action: entry.action.clone(),
if entry.complete_effects.is_empty() {
return Err(ActionCatalogError::EmptyEffects {
if actions.insert(entry.action.clone(), ()).is_some() {
return Err(ActionCatalogError::DuplicateAction {
if let Some(previous) = previous {
if previous >= &entry.action {
return Err(ActionCatalogError::EntriesNotStrictlySorted {
previous: previous.clone(),
current: entry.action.clone(),
previous = Some(&entry.action);
all_ingresses.extend(entry.ingresses.iter().copied());
validate_entry_authority(entry)?;
let computed_catalog_digest = self.compute_catalog_digest()?;
if self.catalog_digest != computed_catalog_digest {
return Err(ActionCatalogError::CatalogDigestMismatch {
expected: computed_catalog_digest,
observed: self.catalog_digest.clone(),
Ok(ActionCatalogValidation {
entry_count: self.entries.len(),
action_count: actions.len(),
ingress_count: all_ingresses.len(),
computed_catalog_digest,
})
fn validate_semantic_action_id(action: &ActionId) -> Result<(), ActionCatalogError> {
if !action.is_semantic_catalog_id() {
return Err(ActionCatalogError::InvalidSemanticActionId {
action: action.clone(),
fn validate_entry_authority(entry: &ActionCatalogEntryV1) -> Result<(), ActionCatalogError> {
let first_safety_effect = entry
.complete_effects
.iter()
.find(|effect| effect.is_negative_safety())
.copied();
match entry.authority_floor {
AuthorityFloor::SafetyOnly => {
if let Some(effect) = entry
.find(|effect| !effect.is_negative_safety())
{
return Err(ActionCatalogError::NonSafetyEffectInSafetyAction {
effect: *effect,
if entry.risk_class != RiskClass::Critical {
return Err(ActionCatalogError::SafetyOnlyMustBeCritical {
AuthorityFloor::PositiveSovereign => {
if let Some(effect) = first_safety_effect {
return Err(ActionCatalogError::SafetyEffectOutsideSafetyAction {
effect,
if !entry.complete_effects.contains(&Effect::SovereignMutation) {
return Err(ActionCatalogError::PositiveFloorWithoutSovereignEffect {
return Err(ActionCatalogError::PositiveSovereignMustBeCritical {
floor => {
if entry.complete_effects.contains(&Effect::SovereignMutation) {
return Err(ActionCatalogError::SovereignEffectWithoutPositiveFloor {
if entry.risk_class == RiskClass::Critical {
return Err(ActionCatalogError::CriticalActionHasInsufficientFloor {
if floor == AuthorityFloor::Ordinary {
.find(|effect| effect_requires_elevated_authority(**effect))
return Err(ActionCatalogError::ElevatedEffectInOrdinaryAction {
if floor == AuthorityFloor::ServiceIdentity
&& !entry.ingresses.iter().any(|ingress| {
matches!(
ingress,
Ingress::Rest
| Ingress::Cli
| Ingress::Hook
| Ingress::BackgroundJob
| Ingress::Recovery
| Ingress::Migration
)
return Err(ActionCatalogError::ServiceIdentityWithoutServiceIngress {
if entry
.contains(&Effect::ExecutableReplacement)
&& entry.authority_floor != AuthorityFloor::PositiveSovereign
return Err(
ActionCatalogError::ExecutableReplacementWithoutPositiveFloor {
);
const fn effect_requires_elevated_authority(effect: Effect) -> bool {
Effect::SourceFilesystemWrite
| Effect::HostFilesystemWrite
| Effect::ProcessSpawn
| Effect::ProcessSignal
| Effect::ExecutableReplacement
| Effect::NetworkAccess
| Effect::NetworkExpose
fn require_non_empty(field: &'static str, value: &str) -> Result<(), ActionCatalogError> {
if value.trim().is_empty() {
return Err(ActionCatalogError::EmptyRequired { field });
fn entry<const I: usize, const E: usize>(
action: &str,
ingresses: [Ingress; I],
complete_effects: [Effect; E],
risk_class: RiskClass,
authority_floor: AuthorityFloor,
) -> ActionCatalogEntryV1 {
ActionCatalogEntryV1 {
action: ActionId::new(action).expect("built-in action ids are non-empty"),
ingresses: ingresses.into_iter().collect(),
complete_effects: complete_effects.into_iter().collect(),
risk_class,
authority_floor,
/// Canonical G2 inventory of every audited action that can mutate owner state,
/// source/host files, processes, or durable coordination. Read-like variants
/// are retained when they have a mutating ledger/cache side effect or when an
/// input-sensitive tool must be split fail-closed from its mutating variant.
pub fn m1nd10_action_catalog() -> Result<ActionCatalogV1, ActionCatalogError> {
use AuthorityFloor::{Ordinary, PositiveSovereign, SafetyOnly, ScopedGrantA2, ServiceIdentity};
use Effect::{
AbortPrepared, CoordinationRecord, DemoteGrant, EpochBump, EpochFence,
ExecutableReplacement, FreezeIssuance, GraphMutation, HostFilesystemWrite,
MissionStateWrite, NetworkAccess, NetworkExpose, ProcessSignal, ProcessSpawn, Read,
RevokeCapability, RollbackSignedCandidate, RuntimeStoreWrite, SourceFilesystemWrite,
SovereignMutation,
};
use Ingress::{BackgroundJob, Cli, Hook, Mcp, Migration, Recovery, Rest};
use RiskClass::{Critical, High, Low, Medium};
let mut entries = vec![
// The broker-issued lease is only an authorization artifact. It does
// not perform the target action, and the target is re-authorized and
// consumed separately at its own exact policy tuple.
entry(
"authority.authorize",
[Mcp, Rest],
[RuntimeStoreWrite, CoordinationRecord],
High,
),
// MCP/REST intercepts and graph/store mutations.
"brain.bootstrap",
[
GraphMutation,
RuntimeStoreWrite,
HostFilesystemWrite,
],
Critical,
// The BIRTH path (GENESIS-INGEST-CONSUMERS-SPEC.md §2, owner-ratified
// 2026-07-29). Its own action rather than a mode of `brain.bootstrap`,
// because its guards are different in kind: empty destination defined ON
// DISK, no `allow_overlap` at all, whole-or-nothing, and admission by an
// owner-stamped human origin. Same effects and the same
// `PositiveSovereign` floor — birth is not a lowering of anything, it is
// the sovereign frontier reached through a HUMAN gesture (`Cli`) instead
// of a lease. `Mcp`/`Rest` are declared because the verb is advertised
// there and REFUSES there; declaring only `Cli` would make the registry
// silent about the seams that must say no.
"brain.bootstrap.birth",
[Mcp, Rest, Cli],
"brain.promote",
[Mcp],
entry("graph.ingest.preview", [Mcp], [Read], Low, Ordinary),
"graph.ingest.merge_existing",
[GraphMutation, RuntimeStoreWrite],
// The freshness door (GENESIS-INGEST-CONSUMERS-SPEC.md §1, owner-ratified
// 2026-07-29). It re-scans a root the bound brain has ALREADY declared:
// same effects as `merge_existing`, and deliberately NO `SovereignMutation`,
// because it structurally cannot change the root set or cross to another
// brain's territory. Its floor is the ratified `ScopedGrantA2`.
"graph.ingest.refresh_declared_root",
"graph.ingest.change_roots",
[GraphMutation, RuntimeStoreWrite, SovereignMutation],
"graph.ingest.replace",
"graph.audit.replace",
[Mcp, Hook],
"graph.federate.replace",
entry("graph.federate_auto.preview", [Mcp], [Read], Low, Ordinary),
"graph.federate_auto.execute",
"graph.learn",
// Nominal reads with plasticity, findings, counters, or persistence.
"query.activate",
[Read, GraphMutation, RuntimeStoreWrite],
Medium,
"query.missing",
"query.orient",
[Read, GraphMutation, RuntimeStoreWrite, CoordinationRecord],
"query.north",
"query.seek",
"query.scan",
[Read, RuntimeStoreWrite, CoordinationRecord],
"query.scan_all",
"query.taint_trace",
[Read, RuntimeStoreWrite],
"query.twins",
"query.refactor_plan",
// Pure MCP reads share one semantic floor. The transport parity layer
// maps every such tool explicitly; this entry is not an unknown-tool
// fallback and cannot authorize a mutation.
entry("query.read", [Mcp], [Read], Low, Ordinary),
// G5 EvidenceQuery is explicit because its no-write guarantee is part of
// the contract: REST and Streamable MCP verify the committed projection
// prefix without lock creation, tail repair, or cache mutation.
entry("evidence.query", [Mcp, Rest], [Read], Low, Ordinary),
// Persistence, memory, and universal-document cache paths.
entry("store.persist.status", [Mcp], [Read], Low, Ordinary),
"store.persist.save_runtime",
[RuntimeStoreWrite],
"store.persist.save_explicit_path",
[HostFilesystemWrite],
"store.persist.checkpoint",
"store.persist.load_replace",
[Mcp, Recovery],
[Read, GraphMutation, RuntimeStoreWrite, SovereignMutation],
"memory.memorize.default",
"memory.memorize.explicit_path",
[GraphMutation, RuntimeStoreWrite, HostFilesystemWrite],
entry("boot_memory.read", [Mcp], [Read], Low, Ordinary),
"boot_memory.set",
"boot_memory.delete",
"documents.resolve.refresh_cache",
"documents.bindings.refresh_cache",
"documents.drift.refresh_cache",
// Surgical and XRay variants remain distinct by commit semantics.
"source.apply.single",
[SourceFilesystemWrite, RuntimeStoreWrite, CoordinationRecord],
"source.apply.batch",
"source.edit.commit",
"source.edit.preview",
[Read, CoordinationRecord],
Low,
"source.surgical_context.mark_proof_ready",
// The transplant verb moves a top-level item across files, writing
// source + dest + every derived referencer. Its
// effect tuple mirrors source.apply/source.edit.commit — a real on-disk
// source write consumed under the armed proof gate. `transplant_commit`
// lands a staged plan (the same write under a handle); `transplant_preview`
// only stages in memory, so it carries the read stance of source.edit.preview.
"source.transplant.single",
"source.transplant.commit",
"source.transplant.preview",
entry("xray.apply.dry_run", [Mcp], [Read], Medium, Ordinary),
"xray.apply.commit",
"xray.retag.dry_run",
"xray.retag.commit",
[GraphMutation, RuntimeStoreWrite, CoordinationRecord],
"xray.paint.dry_run",
"xray.paint.commit",
// System-block governance and candidate lifecycle.
"system_blocks.seed_import.force",
[RuntimeStoreWrite, SovereignMutation],
"system_blocks.skeleton_candidate",
CoordinationRecord,
ProcessSpawn,
NetworkAccess,
"system_blocks.candidate_naming",
"system_blocks.ratify",
"system_blocks.receipt_import",
"system_blocks.reconcile",
"system_blocks.archive",
"system_blocks.restore",
"system_blocks.delete.permanent",
"system_blocks.candidate_edit",
"system_blocks.lease.acquire",
[CoordinationRecord, RuntimeStoreWrite],
"system_blocks.lease.refresh",
"system_blocks.lease.release",
// Mission state, letters, delegation, and runner jobs.
"mission.start",
[MissionStateWrite, RuntimeStoreWrite],
"mission.event",
"mission.next",
"mission.verify",
[MissionStateWrite, RuntimeStoreWrite, CoordinationRecord],
"mission.handoff",
"mission.close",
"mission.close_with_memory",
[MissionStateWrite, RuntimeStoreWrite, GraphMutation],
"mission.post.ordinary",
"mission.post.landed",
MissionStateWrite,
"mission.post.archive",
// M1ND-10 G3: the versioned MissionService is the sole advertised
// external mission mutation boundary. The older post/import actions
// remain catalogued only as denied compatibility tombstones.
"mission.service.land_intent",
[Read],
"mission.service.mission_transition",
"mission.service.execution_dispatch",
"mission.service.execution_started",
"mission.service.execution_terminal",
"mission.service.land",
"mission.spawn",
"mission.curation_spawn",
[Rest],
"delegation.delegate",
"delegation.debrief",
"runner.mission.execute",
[Rest, BackgroundJob],
SourceFilesystemWrite,
"runner.naming.execute",
"runner.curation.execute",
// Daemon, auto-ingest, session lifecycle, and REST side effects.
"daemon.start",
"daemon.stop",
"daemon.tick",
[Mcp, BackgroundJob],
"daemon.alerts_ack",
"auto_ingest.start_existing_roots",
"auto_ingest.change_roots",
[RuntimeStoreWrite, CoordinationRecord, SovereignMutation],
"auto_ingest.stop",
"auto_ingest.tick",
"runtime.instance.save",
"runtime.target.save",
[RuntimeStoreWrite, NetworkAccess],
"runtime.delete_state.permanent",
[Rest, Recovery],
[RuntimeStoreWrite, HostFilesystemWrite, SovereignMutation],
"runtime.runnerd.announce",
"runtime.http.event_log_append",
"runtime.owner.boot",
[Cli],
"runtime.owner.shutdown",
"runtime.instance.heartbeat",
[BackgroundJob],
"runtime.instance.gc",
"runtime.agent_memory.reload",
[Cli, BackgroundJob],
"runtime.presence.track_agent",
"runtime.session.handshake",
"runtime.root.self_heal",
"runtime.project_brain.evict_persist",
"runtime.runner.secret_init",
"runtime.runner.heartbeat",
[NetworkAccess, CoordinationRecord],
"runtime.server.open_browser",
[ProcessSpawn],
"runtime.network_expose",
[NetworkExpose, SovereignMutation],
// Durable trails, perspectives, locks, calibration, and derived graph.
"trail.save",
"trail.resume",
"trail.merge",
"perspective.start",
"perspective.routes",
"perspective.inspect",
"perspective.peek",
"perspective.follow",
"perspective.suggest",
"perspective.affinity",
"perspective.branch",
"perspective.back",
"perspective.close",
"lock.create",
"lock.watch",
entry("lock.diff", [Mcp], [Read, RuntimeStoreWrite], Low, Ordinary),
"lock.rebase",
"lock.release",
"antibody.create",
[RuntimeStoreWrite, GraphMutation],
"antibody.enable",
"antibody.disable",
"antibody.delete",
"calibration.predict",
"calibration.envelope",
"graph.ghost_edges",
"graph.runtime_overlay",
// Host CLI, hooks, release, recovery, and migration.
"cli.startup.install_bwrap_compat",
"cli.inbox_sweep.distribute",
"cli.init",
"cli.install_skills",
"cli.host.skills_apply",
"cli.host.config_apply",
"cli.host.doctrine_apply",
"cli.host.hooks_apply",
"cli.demo.execute",
[HostFilesystemWrite, ProcessSpawn, NetworkAccess],
"cli.smoke.execute",
"release.update.verify",
"cli.agent.trust",
"cli.agent.orient",
Read,
"cli.agent.first_minute",
"cli.agent.kickstart",
"release.self_update.apply",
ExecutableReplacement,
ProcessSignal,
"release.self_update.restart",
"release.self_update.rollback",
[Cli, Recovery],
"hook.session_start.first_minute",
[Hook],
"hook.task_start.first_minute",
"hook.agent_spawn.first_minute",
"hook.kickstart",
"runtime.isolated_agent.start",
[Cli, Hook],
[HostFilesystemWrite, ProcessSpawn],
"runtime.isolated_agent.stop",
[HostFilesystemWrite, ProcessSignal],
"migration.medulla.apply",
[Migration],
"migration.medulla.rollback",
[Migration, Recovery],
// Immutable negative-only SafetyKernel catalog.
"safety.freeze_issuance",
[BackgroundJob, Recovery],
[FreezeIssuance],
"safety.epoch_fence",
[EpochFence],
"safety.epoch_bump",
[EpochBump],
"safety.revoke_capability",
[RevokeCapability],
"safety.abort_prepared",
[AbortPrepared],
"safety.demote_grant",
[DemoteGrant],
"safety.rollback_signed_candidate",
[RollbackSignedCandidate],
];
entries.sort_by(|left, right| left.action.cmp(&right.action));
let mut catalog = ActionCatalogV1 {
schema: ACTION_CATALOG_SCHEMA.into(),
catalog_version: M1ND10_ACTION_CATALOG_VERSION.into(),
entries,
catalog_digest: String::new(),
catalog.seal()?;
catalog.validate()?;
Ok(catalog)
#[cfg(test)]
mod tests {
use serde_json::{json, Value};
use super::*;
fn action(value: &str) -> ActionId {
ActionId::new(value).unwrap()
fn test_catalog(entry: ActionCatalogEntryV1) -> ActionCatalogV1 {
catalog_version: "test-v1".into(),
entries: vec![entry],
catalog.seal().unwrap();
catalog
fn ordinary_entry(action_id: &str) -> ActionCatalogEntryV1 {
action_id,
[Ingress::Mcp],
[Effect::Read],
RiskClass::Low,
AuthorityFloor::Ordinary,
#[test]
fn brain_promotion_is_a_critical_positive_sovereign_action() {
let catalog = m1nd10_action_catalog().expect("canonical catalog");
let promote = catalog
.entries
.find(|entry| entry.action.as_str() == "brain.promote")
.expect("brain.promote catalog entry");
assert_eq!(promote.authority_floor, AuthorityFloor::PositiveSovereign);
assert_eq!(promote.risk_class, RiskClass::Critical);
assert!(promote
.contains(&Effect::SovereignMutation));
fn wire_shape_is_exact_and_unknown_fields_are_denied() {
let catalog = test_catalog(ordinary_entry("query.read"));
let Value::Object(object) = serde_json::to_value(&catalog).unwrap() else {
panic!("catalog must serialize as object");
assert_eq!(
object.keys().map(String::as_str).collect::<BTreeSet<_>>(),
BTreeSet::from(["catalog_digest", "catalog_version", "entries", "schema"])
object["entries"][0]
.as_object()
.unwrap()
.keys()
.map(String::as_str)
.collect::<BTreeSet<_>>(),
BTreeSet::from([
"action",
"authority_floor",
"complete_effects",
"ingresses",
"risk_class",
])
serde_json::to_value(AuthorityFloor::ScopedGrantA2).unwrap(),
json!("SCOPED_GRANT_A2")
serde_json::to_value([
Effect::HostFilesystemWrite,
Effect::ExecutableReplacement,
Effect::ProcessSignal,
Effect::NetworkAccess,
.unwrap(),
json!([
"HOST_FILESYSTEM_WRITE",
"EXECUTABLE_REPLACEMENT",
"PROCESS_SIGNAL",
"NETWORK_ACCESS"
let mut top_level = serde_json::to_value(&catalog).unwrap();
top_level
.insert("unexpected".into(), json!(true));
assert!(serde_json::from_value::<ActionCatalogV1>(top_level).is_err());
let mut nested = serde_json::to_value(&catalog).unwrap();
nested["entries"][0]
assert!(serde_json::from_value::<ActionCatalogV1>(nested).is_err());
fn self_hash_detects_any_catalog_change() {
let mut catalog = m1nd10_action_catalog().unwrap();
let validation = catalog.validate().unwrap();
assert_eq!(validation.entry_count, validation.action_count);
assert_eq!(validation.ingress_count, 7);
assert_eq!(validation.computed_catalog_digest, catalog.catalog_digest);
catalog.entries[0].risk_class = RiskClass::Low;
assert!(matches!(
catalog.validate(),
Err(ActionCatalogError::CatalogDigestMismatch { .. })
| Err(ActionCatalogError::PositiveSovereignMustBeCritical { .. })
));
fn duplicate_action_is_rejected() {
let duplicate = ordinary_entry("query.read");
entries: vec![duplicate.clone(), duplicate],
Err(ActionCatalogError::DuplicateAction { .. })
fn empty_ingresses_and_effects_fail_closed() {
let empty_ingresses = ActionCatalogEntryV1 {
action: action("query.empty_ingress"),
ingresses: BTreeSet::new(),
complete_effects: BTreeSet::from([Effect::Read]),
risk_class: RiskClass::Low,
authority_floor: AuthorityFloor::Ordinary,
test_catalog(empty_ingresses).validate(),
Err(ActionCatalogError::EmptyIngresses { .. })
let empty_effects = ActionCatalogEntryV1 {
action: action("query.empty_effects"),
ingresses: BTreeSet::from([Ingress::Mcp]),
complete_effects: BTreeSet::new(),
test_catalog(empty_effects).validate(),
Err(ActionCatalogError::EmptyEffects { .. })
fn sovereign_effect_and_floor_are_bijective_and_critical() {
let sovereign_under_ordinary = ActionCatalogEntryV1 {
complete_effects: BTreeSet::from([Effect::SovereignMutation]),
..ordinary_entry("governance.bad_ordinary")
test_catalog(sovereign_under_ordinary).validate(),
Err(ActionCatalogError::SovereignEffectWithoutPositiveFloor { .. })
let missing_effect = ActionCatalogEntryV1 {
action: action("governance.missing_effect"),
complete_effects: BTreeSet::from([Effect::RuntimeStoreWrite]),
risk_class: RiskClass::Critical,
authority_floor: AuthorityFloor::PositiveSovereign,
test_catalog(missing_effect).validate(),
Err(ActionCatalogError::PositiveFloorWithoutSovereignEffect { .. })
let noncritical_sovereign = ActionCatalogEntryV1 {
action: action("governance.noncritical"),
risk_class: RiskClass::High,
test_catalog(noncritical_sovereign).validate(),
Err(ActionCatalogError::PositiveSovereignMustBeCritical { .. })
fn safety_only_is_negative_only_and_separate_from_positive_authority() {
let safety = ActionCatalogEntryV1 {
action: action("safety.test"),
ingresses: BTreeSet::from([Ingress::BackgroundJob]),
complete_effects: BTreeSet::from([Effect::FreezeIssuance, Effect::RevokeCapability]),
authority_floor: AuthorityFloor::SafetyOnly,
assert!(test_catalog(safety).validate().is_ok());
let mixed = ActionCatalogEntryV1 {
action: action("safety.mixed"),
complete_effects: BTreeSet::from([Effect::FreezeIssuance, Effect::RuntimeStoreWrite]),
test_catalog(mixed).validate(),
Err(ActionCatalogError::NonSafetyEffectInSafetyAction { .. })
let leaked = ActionCatalogEntryV1 {
action: action("governance.safety_leak"),
complete_effects: BTreeSet::from([Effect::SovereignMutation, Effect::FreezeIssuance]),
test_catalog(leaked).validate(),
Err(ActionCatalogError::SafetyEffectOutsideSafetyAction { .. })
fn executable_replacement_never_uses_ordinary_or_a2_authority() {
let replacement = ActionCatalogEntryV1 {
action: action("release.bad_replace"),
ingresses: BTreeSet::from([Ingress::Cli]),
complete_effects: BTreeSet::from([Effect::ExecutableReplacement]),
authority_floor: AuthorityFloor::ScopedGrantA2,
test_catalog(replacement).validate(),
Err(ActionCatalogError::ExecutableReplacementWithoutPositiveFloor { .. })
fn all_audited_p0_actions_are_present_as_semantic_variants() {
let catalog = m1nd10_action_catalog().unwrap();
let actions: BTreeSet<&str> = catalog
.map(|entry| entry.action.as_str())
.collect();
let required = [
"graph.federate_auto.preview",
"xray.apply.dry_run",
for required_action in required {
assert!(
actions.contains(required_action),
"missing {required_action}"
fn audited_inventory_count_and_ingress_coverage_are_stable() {
let ingresses: BTreeSet<Ingress> = catalog
.flat_map(|entry| entry.ingresses.iter().copied())
// `graph.ingest.preview` is now an explicit read-only governed action
// rather than an untracked pre-mutation side channel. Keep this pin in
// lock-step with the exhaustive consumer registry.
assert_eq!(catalog.entries.len(), 174);
assert_eq!(ingresses.len(), 7);