1010 kb serve [--once] the scheduler (holds the single-writer lease)
1111 kb activate activate the knowledge snapshot of the repository's main now
1212 kb rollback --to SHA point `active` back at an earlier snapshot
13+ kb publish (--remote HOST:/STATE_DIR | --local DIR) [--once]
14+ the publisher (GPU box, owner's gh login): perform
15+ the signed outbox items; writes need ALLOW_POST=1
16+ (and ALLOW_PUSH=1 to push branches)
1317
1418Pause/resume bump the repository's generation, so every outbox item issued
1519before it is void; they then re-sign the control record and hold list at once.
2024import argparse
2125import json
2226import os
27+ import re
2328import sys
2429import time
2530from pathlib import Path
@@ -105,6 +110,14 @@ def main(argv: list[str] | None = None) -> int:
105110 sub .add_parser ("activate" )
106111 rollback = sub .add_parser ("rollback" )
107112 rollback .add_argument ("--to" , required = True )
113+ publish = sub .add_parser ("publish" )
114+ where = publish .add_mutually_exclusive_group (required = True )
115+ where .add_argument ("--remote" , help = "host:/absolute/path of the service state directory (over ssh)" )
116+ where .add_argument ("--local" , help = "the service state directory on this machine" )
117+ publish .add_argument ("--once" , action = "store_true" )
118+ publish .add_argument ("--interval" , type = float , default = 60.0 )
119+ publish .add_argument ("--publisher-state" ,
120+ help = "default: $KB_PUBLISHER_STATE or ~/.infermatrix-copilot/kb-publisher" )
108121 args = parser .parse_args (argv )
109122
110123 if args .command == "keygen" :
@@ -114,6 +127,9 @@ def main(argv: list[str] | None = None) -> int:
114127 print (public_key_text (key .public_key ()))
115128 return 0
116129
130+ if args .command == "publish" :
131+ return _publish (args )
132+
117133 state_dir = _state_dir (args .state_dir )
118134 ledger = _ledger (state_dir )
119135 try :
@@ -220,3 +236,38 @@ def _resume():
220236 finally :
221237 ledger .close ()
222238 return 2
239+
240+
241+ def _publish (args ) -> int :
242+ """The publisher never opens the service ledger: it only sees the outbox."""
243+ from ..knowledge_service .signing import load_private_key , load_public_key
244+ from .merge import knowledge_repository
245+ from .publisher import Gh , LocalTransport , Publisher , SshTransport
246+
247+ for name in ("KB_SERVICE_PUBKEY" , "KB_PUBLISHER_KEY" , "KB_PUBLISHER_GIT_AUTHOR" ):
248+ if not os .environ .get (name ):
249+ raise SystemExit (f"{ name } is not set" )
250+ author = re .fullmatch (r"\s*(.+?)\s*<([^<>\s]+@[^<>\s]+)>\s*" , os .environ ["KB_PUBLISHER_GIT_AUTHOR" ])
251+ if not author :
252+ raise SystemExit ("KB_PUBLISHER_GIT_AUTHOR must look like 'Name <email>'" )
253+ state = Path (args .publisher_state or os .environ .get ("KB_PUBLISHER_STATE" )
254+ or Path .home () / ".infermatrix-copilot" / "kb-publisher" ).expanduser ()
255+ state .mkdir (parents = True , exist_ok = True )
256+ registry = _registry ()
257+ publisher = Publisher (
258+ transport = SshTransport .parse (args .remote ) if args .remote else LocalTransport (Path (args .local )),
259+ service_public_key = load_public_key (Path (os .environ ["KB_SERVICE_PUBKEY" ]).read_text (encoding = "utf-8" )),
260+ publisher_key = load_private_key (os .environ ["KB_PUBLISHER_KEY" ]),
261+ github = Gh (knowledge_repository (), cwd = state ),
262+ state_dir = state ,
263+ author = (author .group (1 ), author .group (2 )),
264+ repo_flags = {name : (lc .publishes , lc .auto_merge ) for name , lc in registry .items () if lc .enabled },
265+ allow_post = os .environ .get ("ALLOW_POST" ) == "1" ,
266+ allow_push = os .environ .get ("ALLOW_PUSH" ) == "1" ,
267+ )
268+ if args .once :
269+ json .dump (publisher .run_once (), sys .stdout , sort_keys = True )
270+ print ()
271+ return 0
272+ publisher .serve (interval = args .interval )
273+ return 0
0 commit comments