@@ -370,11 +370,23 @@ def _handle_revoke_intent(self) -> None:
370370 def _handle_revoke_mandate (self ) -> None :
371371 payload = self ._read_json_body ()
372372 mandate_id = payload .get ("mandate_id" )
373+ cascade_raw = payload .get ("cascade" )
374+ cascade = bool (cascade_raw ) if isinstance (cascade_raw , bool ) else False
373375 if not isinstance (mandate_id , str ) or mandate_id .strip () == "" :
374376 self ._send_json (400 , {"error" : "mandate_id is required" })
375377 return
376- self .server .daemon_ref .revoke_mandate (mandate_id .strip ()) # type: ignore[attr-defined]
377- self ._send_json (200 , {"ok" : True , "mandate_id" : mandate_id .strip ()})
378+ revoked_count = self .server .daemon_ref .revoke_mandate ( # type: ignore[attr-defined]
379+ mandate_id .strip (), cascade = cascade
380+ )
381+ self ._send_json (
382+ 200 ,
383+ {
384+ "ok" : True ,
385+ "mandate_id" : mandate_id .strip (),
386+ "cascade" : cascade ,
387+ "revoked_count" : int (revoked_count ),
388+ },
389+ )
378390
379391 def _handle_identity_task (self ) -> None :
380392 payload = self ._read_json_body ()
@@ -585,8 +597,8 @@ def revoke_principal(self, principal_id: str) -> None:
585597 def revoke_intent (self , intent_hash : str ) -> None :
586598 self ._sidecar .revoke_intent_hash (intent_hash )
587599
588- def revoke_mandate (self , mandate_id : str ) -> None :
589- self ._sidecar .revoke_mandate_id (mandate_id )
600+ def revoke_mandate (self , mandate_id : str , cascade : bool = False ) -> int :
601+ return self ._sidecar .revoke_mandate_id (mandate_id , cascade = cascade )
590602
591603 def max_request_body_bytes (self ) -> int :
592604 return max (0 , int (self ._config .max_request_body_bytes ))
@@ -826,15 +838,16 @@ def _apply_sync_snapshot(self, snapshot: AuthoritySyncSnapshot) -> None:
826838 elif item .type == "intent" and item .intent_hash is not None :
827839 self ._sidecar .revoke_intent_hash (item .intent_hash )
828840 elif item .type == "tags" :
829- # Tag revocation support is modeled in control-plane API but not yet represented in
830- # sidecar's revocation cache keys.
831- continue
841+ tags = { tag . strip (). lower () for tag in item . tags if tag . strip () != "" }
842+ if "global_kill_switch" in tags :
843+ self . _sidecar . activate_global_kill_switch ()
832844
833845
834846def _build_default_sidecar (
835847 mode : AuthorityMode ,
836848 policy_file : str | None ,
837849 credential_store_file : str ,
850+ mandate_store_file : str | None = None ,
838851 control_plane_config : ControlPlaneBootstrapConfig | None = None ,
839852 local_identity_config : LocalIdentityBootstrapConfig | None = None ,
840853 identity_bridge : ExchangeTokenBridge | None = None ,
@@ -912,7 +925,7 @@ def _build_default_sidecar(
912925 proof_ledger = proof_ledger ,
913926 identity_bridge = identity_bridge or IdentityBridge (),
914927 credential_store = LocalCredentialStore (credential_store_file ),
915- revocation_cache = LocalRevocationCache (),
928+ revocation_cache = LocalRevocationCache (store_file_path = mandate_store_file ),
916929 policy_engine = policy_engine ,
917930 local_identity_registry = local_identity_registry ,
918931 )
@@ -1070,6 +1083,14 @@ def main() -> None:
10701083 "--credential-store-file" ,
10711084 default = str (Path .home () / ".predicate-authorityd" / "credentials.json" ),
10721085 )
1086+ parser .add_argument (
1087+ "--mandate-store-file" ,
1088+ default = None ,
1089+ help = (
1090+ "Optional path for persisted local revocation/mandate cache. "
1091+ "If omitted, mandate cache remains in-memory (ephemeral default)."
1092+ ),
1093+ )
10731094 parser .add_argument (
10741095 "--local-identity-enabled" ,
10751096 action = "store_true" ,
@@ -1307,6 +1328,7 @@ def main() -> None:
13071328 mode = mode ,
13081329 policy_file = args .policy_file ,
13091330 credential_store_file = args .credential_store_file ,
1331+ mandate_store_file = args .mandate_store_file ,
13101332 control_plane_config = control_plane_bootstrap ,
13111333 local_identity_config = local_identity_bootstrap ,
13121334 identity_bridge = identity_bridge ,
0 commit comments