@@ -139,6 +139,8 @@ def _cmd_run_rust(args: argparse.Namespace) -> int:
139139
140140def _cmd_run_python (args : argparse .Namespace ) -> int :
141141 """Original Python tree-walking interpreter."""
142+ from .store .remote import RemoteStore
143+
142144 try :
143145 src = _read (args .file )
144146 store : Optional [SymbolStore ] = None
@@ -149,8 +151,18 @@ def _cmd_run_python(args: argparse.Namespace) -> int:
149151 module = parse (src , store = store )
150152 if store is None and module .imports :
151153 store = SymbolStore (_store_root (args ))
154+
155+ # Wrap with RemoteStore if --registry is set (V3-2).
156+ effective_store = store
157+ registry = getattr (args , "registry" , None )
158+ if registry and store is not None :
159+ effective_store = RemoteStore (store , registry = registry )
160+ elif registry and module .imports :
161+ local = SymbolStore (_store_root (args ))
162+ effective_store = RemoteStore (local , registry = registry )
163+
152164 entry = args .entry or _default_entry (module )
153- result = run (module , entry , store = store )
165+ result = run (module , entry , store = effective_store )
154166 if result is not None :
155167 print (result )
156168 return 0
@@ -468,6 +480,90 @@ def cmd_store_verify(args: argparse.Namespace) -> int:
468480 return 0
469481
470482
483+ def cmd_store_push (args : argparse .Namespace ) -> int :
484+ """Push a locally-stored symbol to a remote registry.
485+
486+ Reads the symbol bytes from the local store, POSTs them to the
487+ registry's POST /symbols endpoint, and verifies the returned
488+ identity matches. Idempotent: a second push of the same symbol
489+ returns 200 with the existing identity.
490+
491+ The registry URL defaults to https://codifide.com. Agents running
492+ private registries pass --registry https://my-registry.example.com.
493+ """
494+ import urllib .error
495+ import urllib .request
496+
497+ registry = (args .registry or "https://codifide.com" ).rstrip ("/" )
498+ identity = args .identity
499+
500+ # Validate identity shape before touching the store.
501+ if not (identity .startswith ("sha256:" ) and len (identity ) == 71 ):
502+ print (
503+ f"codifide: invalid identity { identity !r} ; "
504+ f"expected sha256: followed by 64 hex chars" ,
505+ file = sys .stderr ,
506+ )
507+ return 1
508+
509+ try :
510+ store = SymbolStore (_store_root (args ))
511+ data = store .get_bytes (identity )
512+ except StoreError as e :
513+ print (f"codifide: { e } " , file = sys .stderr )
514+ return 1
515+
516+ url = f"{ registry } /symbols"
517+ req = urllib .request .Request (
518+ url ,
519+ data = data ,
520+ method = "POST" ,
521+ headers = {"Content-Type" : "application/cbor" },
522+ )
523+ try :
524+ with urllib .request .urlopen (req , timeout = 30 ) as resp :
525+ body = resp .read (1024 * 1024 )
526+ except urllib .error .HTTPError as exc :
527+ body = exc .read (4096 )
528+ try :
529+ import json as _json
530+ err = _json .loads (body )
531+ detail = err .get ("detail" , err .get ("error" , str (exc )))
532+ except Exception :
533+ detail = body .decode ("utf-8" , errors = "replace" )
534+ print (
535+ f"codifide: registry { url } returned HTTP { exc .code } : { detail } " ,
536+ file = sys .stderr ,
537+ )
538+ return 1
539+ except urllib .error .URLError as exc :
540+ print (
541+ f"codifide: cannot reach registry { registry } : { exc .reason } " ,
542+ file = sys .stderr ,
543+ )
544+ return 1
545+
546+ try :
547+ import json as _json
548+ result = _json .loads (body )
549+ except Exception as exc :
550+ print (f"codifide: cannot parse registry response: { exc } " , file = sys .stderr )
551+ return 1
552+
553+ returned_identity = result .get ("identity" , "" )
554+ if returned_identity != identity :
555+ print (
556+ f"codifide: registry returned identity { returned_identity !r} "
557+ f"but expected { identity !r} ; refusing to accept" ,
558+ file = sys .stderr ,
559+ )
560+ return 1
561+
562+ name = result .get ("name" , "?" )
563+ print (f"{ identity } \t { name } " )
564+ return 0
565+
566+
471567def cmd_store_gc (args : argparse .Namespace ) -> int :
472568 """Report or delete unreachable identities.
473569
@@ -644,11 +740,15 @@ def cmd_serve(args: argparse.Namespace) -> int:
644740 Thin HTTP wrapper over the symbol store. See ``docs/RPC_API.md``.
645741 Binds to 127.0.0.1 by default — not safe to expose over a network
646742 without a reverse proxy with TLS and auth.
743+
744+ Pass ``--read-only`` to disable POST /symbols for public registry
745+ deployments (V3-2).
647746 """
648747 from .server import serve
649748
650749 store = SymbolStore (_store_root (args ))
651- serve (store , host = args .host , port = args .port )
750+ read_only = getattr (args , "read_only" , False )
751+ serve (store , host = args .host , port = args .port , read_only = read_only )
652752 return 0
653753
654754
@@ -820,6 +920,13 @@ def main(argv=None) -> int:
820920 "--store" ,
821921 help = "store root for import resolution (default: $CODIFIDE_STORE or ~/.codifide/store)" ,
822922 )
923+ p_run .add_argument (
924+ "--registry" ,
925+ default = None ,
926+ help = "remote registry URL for resolving imports on cache miss "
927+ "(e.g. https://codifide.com). Opt-in: without this flag, "
928+ "only the local store is used." ,
929+ )
823930 p_run .set_defaults (func = cmd_run )
824931
825932 p_can = sub .add_parser ("canonical" , help = "print canonical JSON or CBOR" )
@@ -940,12 +1047,27 @@ def main(argv=None) -> int:
9401047 )
9411048 p_index .set_defaults (func = cmd_store_index )
9421049
943- p_verify = store_sub .add_parser (
1050+ p_verify_store = store_sub .add_parser (
9441051 "verify" ,
9451052 help = "verify a stored module's imports resolve in the store" ,
9461053 )
947- p_verify .add_argument ("hash" , help = "identity of the module to verify" )
948- p_verify .set_defaults (func = cmd_store_verify )
1054+ p_verify_store .add_argument ("hash" , help = "identity of the module to verify" )
1055+ p_verify_store .set_defaults (func = cmd_store_verify )
1056+
1057+ p_push = store_sub .add_parser (
1058+ "push" ,
1059+ help = "push a locally-stored symbol to a remote registry" ,
1060+ )
1061+ p_push .add_argument (
1062+ "identity" ,
1063+ help = "sha256:<hex> identity of the symbol to push" ,
1064+ )
1065+ p_push .add_argument (
1066+ "--registry" ,
1067+ default = None ,
1068+ help = "registry URL (default: https://codifide.com)" ,
1069+ )
1070+ p_push .set_defaults (func = cmd_store_push )
9491071
9501072 # -- Garbage collection (2026-05-11 design dispatch) --------------
9511073 p_gc = store_sub .add_parser (
@@ -1003,6 +1125,12 @@ def main(argv=None) -> int:
10031125 "--store" ,
10041126 help = "store root directory (default: $CODIFIDE_STORE or ~/.codifide/store)" ,
10051127 )
1128+ p_serve .add_argument (
1129+ "--read-only" ,
1130+ action = "store_true" ,
1131+ dest = "read_only" ,
1132+ help = "disable POST /symbols — for public registry deployments (V3-2)" ,
1133+ )
10061134 p_serve .set_defaults (func = cmd_serve )
10071135
10081136 args = parser .parse_args (argv )
0 commit comments