sally 1.0.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
sally/__init__.py ADDED
@@ -0,0 +1,35 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ """
4
+ SALLY
5
+ sally package
6
+
7
+ """
8
+
9
+ __version__ = '1.0.5' # also change in setup.py
10
+
11
+ import logging
12
+
13
+ from hio.help import ogling
14
+
15
+ from sally.app.logs import TruncatedFormatter
16
+
17
+ log_name = 'sally' # name of this project that shows up in log messages
18
+ log_format_str = f'%(asctime)s [{log_name}] %(levelname)-8s %(module)s.%(funcName)s-%(lineno)s %(message)s'
19
+
20
+ ogler = ogling.initOgler(prefix=log_name, syslogged=False)
21
+ ogler.level = logging.INFO
22
+
23
+ formatter = TruncatedFormatter(log_format_str)
24
+ formatter.default_msec_format = None
25
+ logHandler = logging.StreamHandler()
26
+ logHandler.setFormatter(formatter)
27
+ ogler.baseFormatter = formatter
28
+ ogler.baseConsoleHandler = logHandler
29
+ ogler.baseConsoleHandler.setFormatter(formatter)
30
+ ogler.reopen(name=log_name, temp=True, clear=True)
31
+
32
+ def set_log_level(loglevel, logger):
33
+ """Set the log level for the logger."""
34
+ ogler.level = logging.getLevelName(loglevel.upper())
35
+ logger.setLevel(ogler.level)
sally/app/__init__.py ADDED
@@ -0,0 +1,7 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ """
4
+ KERI
5
+ sally.app package
6
+
7
+ """
@@ -0,0 +1,82 @@
1
+ from typing import Callable
2
+
3
+ from hio.base import doing
4
+ from keri.app import habbing, oobiing
5
+
6
+ from sally import ogler, log_name
7
+
8
+ logger = ogler.getLogger(log_name)
9
+
10
+
11
+ class BootstrapRunner(doing.DoDoer):
12
+ def __init__(self, hby: habbing.Habery, tymth: Callable):
13
+ """
14
+ Parameters:
15
+ hby (Habery): the hab in which to create the AID, if needed
16
+ tymth (function): the clock time (scheduler tick rate) to use from the parent Doist
17
+ """
18
+ self.hby = hby
19
+ self.complete = False
20
+ super(BootstrapRunner, self).__init__(tymth=tymth)
21
+ self.extend([OobiResolveBootstrapper(hby, self)])
22
+
23
+ def recur(self, tyme, deeds=None):
24
+ if self.complete:
25
+ return True
26
+ super(BootstrapRunner, self).recur(tyme, deeds)
27
+ return False
28
+
29
+ def configureAndIncept(self):
30
+ """Configure a keystore by resolving OOBIs in config"""
31
+ self.extend(OobiResolveBootstrapper(self.hby, self))
32
+
33
+ class OobiResolveBootstrapper(doing.Doer):
34
+ """
35
+ Bootstraps OOBI Resolutions for a Habery and an AID, resolving all OOBIs in the bootstrap configuration file.
36
+ """
37
+
38
+ def __init__(self, hby: habbing.Habery, parent: BootstrapRunner, tock=0.0, **kwa):
39
+ self.hby = hby
40
+ self.parent = parent
41
+ super(OobiResolveBootstrapper, self).__init__(tock=tock, **kwa)
42
+
43
+ def configure_and_incept(self):
44
+ oobi_count = self.hby.db.oobis.cntAll()
45
+ if oobi_count:
46
+ obi = oobiing.Oobiery(hby=self.hby)
47
+ self.parent.extend(obi.doers)
48
+
49
+ while oobi_count > self.hby.db.roobi.cntAll():
50
+ yield 0.25
51
+
52
+ for (oobi,), obr in self.hby.db.roobi.getItemIter():
53
+ if obr.state in (oobiing.Result.resolved,):
54
+ logger.info(f"{oobi} succeeded")
55
+ if obr in (oobiing.Result.failed,):
56
+ logger.error(f"{oobi} failed")
57
+
58
+ self.parent.remove(obi.doers)
59
+
60
+ wc = [oobi for (oobi,), _ in self.hby.db.woobi.getItemIter()]
61
+ if len(wc) > 0:
62
+ logger.info(f"\nAuthenticating Well-Knowns...")
63
+ authn = oobiing.Authenticator(hby=self.hby)
64
+ self.parent.extend(authn.doers)
65
+
66
+ while True:
67
+ cap = []
68
+ for (_,), wk in self.hby.db.wkas.getItemIter(keys=b''):
69
+ cap.append(wk.url)
70
+
71
+ if set(wc) & set(cap) == set(wc):
72
+ break
73
+
74
+ yield 0.5
75
+
76
+ self.parent.remove(authn.doers)
77
+
78
+ self.parent.complete = True
79
+
80
+ def recur(self, tock=0.0, **opts):
81
+ yield from self.configure_and_incept()
82
+ return True
@@ -0,0 +1,7 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ """
4
+ KERI
5
+ sally.app.cli package
6
+
7
+ """
@@ -0,0 +1,7 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ """
4
+ KERI
5
+ sally.app.cli.commands package
6
+
7
+ """
File without changes
@@ -0,0 +1,120 @@
1
+ # -*- encoding: utf-8 -*-
2
+ """
3
+ sally.cli.commands module
4
+
5
+ """
6
+ import argparse
7
+ import logging
8
+ import pprint
9
+
10
+ import falcon
11
+ from hio.core import http
12
+ from keri import help
13
+ from keri.app import directing
14
+
15
+ from sally import log_name, ogler
16
+ from sally.core import handling, httping
17
+ from sally.core.monitoring import HealthEnd
18
+
19
+ logger = ogler.getLogger(log_name)
20
+
21
+ parser = argparse.ArgumentParser(description='Launch SALLY sample web hook server')
22
+ parser.set_defaults(handler=lambda args: launch(args),
23
+ transferable=True)
24
+ parser.add_argument('-p', '--http',
25
+ action='store',
26
+ default=9923,
27
+ help="Port on which to listen for web hook events. Defaults to 9923")
28
+
29
+
30
+ def launch(args, expire=0.0):
31
+ baseFormatter = logging.Formatter('%(asctime)s [hook] %(levelname)-8s %(message)s')
32
+ baseFormatter.default_msec_format = None
33
+ help.ogler.baseConsoleHandler.setFormatter(baseFormatter)
34
+ help.ogler.level = logging.getLevelName(logging.INFO)
35
+ help.ogler.reopen(name="hook", temp=True, clear=True)
36
+
37
+ httpPort = args.http
38
+
39
+ app = falcon.App(
40
+ middleware=httping.cors_middleware())
41
+ app.add_route("/", WebhookListener())
42
+ app.add_route("/health", HealthEnd())
43
+
44
+ server = http.Server(port=httpPort, app=app)
45
+ httpServerDoer = http.ServerDoer(server=server)
46
+
47
+ logger.info(f"Sally Web Hook Sample listening on {httpPort}")
48
+ directing.runController(doers=[httpServerDoer], expire=expire)
49
+
50
+
51
+ class WebhookListener:
52
+ """
53
+ Demonstration endpoint for web hook calls that prints events to stdout and stores a simple presentation cache.
54
+ """
55
+ def __init__(self):
56
+ self.received = dict()
57
+
58
+ def on_post(self, req, resp):
59
+ """Receives web hook POST events by printing the credential results to stdout and storing presentations them in memory
60
+ Parameters:
61
+ req: falcon.Request HTTP request
62
+ rep: falcon.Response HTTP response
63
+ """
64
+ logger.info("** HEADERS **")
65
+ logger.info(pprint.pprint(req.headers))
66
+ logger.info("*************")
67
+
68
+ logger.info("**** BODY ****")
69
+ body = req.get_media()
70
+ logger.info(pprint.pprint(body))
71
+ logger.info("**************")
72
+
73
+ data = body.get("data", {})
74
+ if not data:
75
+ logger.error("No data in body")
76
+ resp.media = {"error": "No data in body"}
77
+ resp.status = falcon.HTTP_400
78
+ return
79
+ type = self._resolve_type(data["schema"])
80
+
81
+ holder = data.get("recipient", "")
82
+ presentation = dict(
83
+ credential=data.get("credential", ""),
84
+ type=type,
85
+ issuer=body.get("actor", ""),
86
+ holder=holder,
87
+ LEI=data.get("LEI", ""),
88
+ personLegalName=data.get("personLegalName", ""),
89
+ officialRole=data.get("officialRole", ""),
90
+ )
91
+ self.received[holder] = presentation
92
+ resp.status = falcon.HTTP_202
93
+
94
+ def _resolve_type(self, schema_said):
95
+ """Return human friendly name for schema type"""
96
+ match schema_said:
97
+ case handling.QVI_SCHEMA:
98
+ return "QVI"
99
+ case handling.LE_SCHEMA:
100
+ return "LE"
101
+ case handling.OOR_AUTH_SCHEMA:
102
+ return "OOR Auth"
103
+ case handling.OOR_SCHEMA:
104
+ return "OOR"
105
+ case _:
106
+ raise ValueError(f"Unknown schema type with SAID: {schema_said}")
107
+
108
+ def on_get(self, req, resp):
109
+ """
110
+ Tells the presenter if they have presented a credential before.
111
+ Used in testing to determine that a presentation has succeeded.
112
+ """
113
+ holder = req.get_param("holder", required=True)
114
+ if holder in self.received:
115
+ resp.media = self.received[holder]
116
+ resp.status = falcon.HTTP_200
117
+ else:
118
+ resp.media = {"error": f"No credential presented by {holder}"}
119
+ resp.status = falcon.HTTP_404
120
+
File without changes
@@ -0,0 +1,134 @@
1
+ # -*- encoding: utf-8 -*-
2
+ """
3
+ sally.cli.commands module
4
+
5
+ """
6
+ import argparse
7
+ import os
8
+
9
+ from hio.base import doing
10
+ from keri.app import keeping, habbing, configing
11
+ from keri.app.cli.common import existing
12
+
13
+ import sally
14
+ from sally import ogler, log_name, set_log_level
15
+ from sally.app.bootstrapping import BootstrapRunner
16
+ from sally.core import serving
17
+
18
+ parser = argparse.ArgumentParser(description='Launch Sally vLEI credential presentation receiver service.')
19
+ parser.set_defaults(handler=lambda args: launch(args),
20
+ transferable=True)
21
+
22
+ parser.add_argument(
23
+ "-d", "--direct", action="store_true", default=False,
24
+ help="Listen for direct-mode messages on HTTP port. Default is True")
25
+ parser.add_argument(
26
+ '-p', '--http', action='store', default=9723,
27
+ help="Port on which to listen for OOBI requests. Defaults to 9723")
28
+ parser.add_argument(
29
+ '-n', '--name', action='store', default="sally",
30
+ help="Name of controller. Default is sally.")
31
+ parser.add_argument(
32
+ '-b', '--base', required=False, default="",
33
+ help='additional optional prefix to file location of KERI keystore')
34
+ parser.add_argument(
35
+ "-c", "--config-dir", dest="configDir",
36
+ help="directory override for configuration data")
37
+ parser.add_argument(
38
+ "-f", '--config-file', dest="configFile", action='store', default=None,
39
+ help="Habery (keystore) configuration filename")
40
+ parser.add_argument(
41
+ "-i", "--incept-file", dest="inceptFile", action='store', default=None,
42
+ help="inception configuration filename")
43
+ parser.add_argument(
44
+ '-a', '--alias', required=True,
45
+ help='human readable alias for the new identifier prefix')
46
+ parser.add_argument(
47
+ '-s', '--salt', required=False,
48
+ help='qualified base64 salt for creating key pairs')
49
+ parser.add_argument(
50
+ '--passcode', dest="bran", default=None,
51
+ help='21 character encryption passcode for keystore (is not saved)')
52
+ parser.add_argument(
53
+ '-w', '--web-hook', action='store', required=True, default=None,
54
+ help='Webhook address for outbound notifications of credential issuance or revocation')
55
+ parser.add_argument(
56
+ '--auth', action="store", required=True,
57
+ help='AID or alias of authority for OOBIs and QVI credential issuer')
58
+ parser.add_argument(
59
+ "-r", "--retry-delay", default=10, type=int, action="store",
60
+ help="retry delay (in seconds) for failed web hook attempts")
61
+ parser.add_argument(
62
+ "-e", "--escrow-timeout", default=10, type=int, action="store",
63
+ help="timeout (in minutes) for escrowed events that have not been delivered to the web hook. Defaults to 10")
64
+ parser.add_argument(
65
+ "-l", "--loglevel", action="store", required=False, default=os.getenv("SALLY_LOG_LEVEL", "INFO"),
66
+ help="Set log level to DEBUG | INFO | WARNING | ERROR | CRITICAL. Default is CRITICAL")
67
+
68
+
69
+ logger = ogler.getLogger(log_name)
70
+
71
+ def launch(args, expire=0.0):
72
+ """Launch Sally vLEI credential presentation receiver service"""
73
+ # Logging config
74
+ set_log_level(args.loglevel, logger)
75
+
76
+ # Parse arguments
77
+ hook = args.web_hook
78
+ name = args.name
79
+ salt = args.salt
80
+ base = args.base
81
+ bran = args.bran
82
+ try:
83
+ http_port = int(args.http)
84
+ except ValueError:
85
+ raise ValueError(f"Invalid port number: {args.http}. Must be an integer.")
86
+ auth = args.auth
87
+ direct = args.direct
88
+
89
+ timeout = args.escrow_timeout
90
+ retry = args.retry_delay
91
+
92
+ alias = args.alias
93
+ config_file = args.configFile
94
+ config_dir = args.configDir
95
+
96
+ # Inception configuration for when Sally bootstraps itself
97
+ incept_file = args.inceptFile
98
+ incept_args = {
99
+ "name": name, "base": base, "alias": alias, "bran": bran,
100
+ "incept_file": incept_file, "config_dir": config_dir
101
+ }
102
+
103
+ hby = init_habery(name=name, base=base, bran=bran, config_file=config_file,
104
+ config_dir=config_dir, salt=salt)
105
+ hab = serving.incept_if_new(hby, alias, incept_args)
106
+
107
+ # setup doers
108
+ doers = serving.setupDoers(hby, hab, alias=alias, http_port=http_port, hook=hook, auth=auth,
109
+ timeout=timeout, retry=retry, direct=direct, incept_args=incept_args)
110
+ logger.info(f"Sally Server v{sally.__version__} listening on {http_port} with DB version {hby.db.version}")
111
+ serving.run_doers(doers)
112
+
113
+ def init_habery(name, base, bran, config_file, config_dir, salt):
114
+ """Initialize or reopen the Habery (keystore)"""
115
+ ks = keeping.Keeper(name=name, base=base, temp=False, reopen=True)
116
+ aeid = ks.gbls.get('aeid')
117
+
118
+ cf = None
119
+ if aeid is None:
120
+ if config_file is not None:
121
+ cf = configing.Configer(name=config_file, base=base, headDirPath=config_dir,
122
+ temp=False, reopen=True, clear=False)
123
+ habery_cfg = dict()
124
+ habery_cfg["salt"] = salt if salt else None # When None causes Habery to randomize salt
125
+ hby = habbing.Habery(name=name, base=base, bran=bran, cf=cf, ks=ks, **habery_cfg)
126
+ else:
127
+ ks.close() # existing.setupHby reopens, so close here to avoid LMDB conflict.
128
+ hby = existing.setupHby(name=name, base=base, bran=bran)
129
+
130
+ # Resolve OOBIs for the Habery and the new Hab
131
+ bootstrap_doist = doing.Doist(limit=30.0, tock=0.03125, real=True)
132
+ runner = BootstrapRunner(hby=hby, tymth=bootstrap_doist.tymen())
133
+ bootstrap_doist.do(doers=[runner])
134
+ return hby
@@ -0,0 +1,43 @@
1
+ # -*- encoding: utf-8 -*-
2
+ """
3
+ sally.app.cli.commands module
4
+ """
5
+ import argparse
6
+
7
+ from hio.base import doing
8
+ from keri.app import directing
9
+
10
+ import sally
11
+ from keri.app.cli.common import existing
12
+
13
+ parser = argparse.ArgumentParser(description='Print version of sally CLI')
14
+ parser.set_defaults(handler=lambda args: handler(args))
15
+ parser.add_argument('--name', '-n', help='keystore name and file location of KERI keystore', required=False,
16
+ default=None)
17
+ parser.add_argument('--base', '-b', help='additional optional prefix to file location of KERI keystore',
18
+ required=False, default="")
19
+ parser.add_argument('--passcode', '-p', help='22 character encryption passcode for keystore (is not saved)',
20
+ dest="bran", default=None) # passcode => bran
21
+
22
+
23
+ def handler(args):
24
+ kwa = dict(args=args)
25
+ doers = [doing.doify(version, **kwa)]
26
+ directing.runController(doers=doers, expire=0.0)
27
+
28
+
29
+ def version(tymth, tock=0.0, **opts):
30
+ """ Command line version handler
31
+ """
32
+ _ = (yield tock)
33
+
34
+ args = opts["args"]
35
+ name = args.name
36
+ base = args.base
37
+ bran = args.bran
38
+
39
+ print(f"Library version: {sally.__version__}")
40
+
41
+ if name is not None:
42
+ with existing.existingHby(name=name, base=base, bran=bran) as hby:
43
+ print(f"Database version: {hby.db.version}")
sally/app/cli/kli.py ADDED
@@ -0,0 +1,33 @@
1
+ # -*- encoding: utf-8 -*-
2
+ """
3
+ sally.app.commands module
4
+
5
+ """
6
+ import os
7
+
8
+ import multicommand
9
+
10
+ from sally.app.cli import commands
11
+
12
+
13
+ def main():
14
+ parser = multicommand.create_parser(commands)
15
+ args = parser.parse_args()
16
+
17
+ if not hasattr(args, 'handler'):
18
+ parser.print_help()
19
+ return
20
+
21
+ try:
22
+ args.handler(args)
23
+ except Exception as ex:
24
+ if os.getenv('DEBUG_KLI'):
25
+ import traceback
26
+ traceback.print_exc()
27
+ else:
28
+ print(f"ERR: {ex}")
29
+ return -1
30
+
31
+
32
+ if __name__ == "__main__":
33
+ main()
sally/app/logs.py ADDED
@@ -0,0 +1,33 @@
1
+ import copy
2
+ import logging
3
+
4
+
5
+ class TruncatedFormatter(logging.Formatter):
6
+ """
7
+ Formats log records with shorter module and function names and a right justified line number for readability.
8
+
9
+ Example:
10
+ "2025-09-05 13:59:29 [keria] INFO serving .runAgency - 145 The Agency is loaded and waiting for requests..."
11
+ """
12
+
13
+ def __init__(self, fmt=None, datefmt=None, style='%'):
14
+ super().__init__(fmt, datefmt, style)
15
+
16
+ def format(self, record):
17
+ """
18
+ Format the log record with truncated module and function names.
19
+ Ignores exceptions and logs an error if formatting fails.
20
+ """
21
+ record = copy.copy(record)
22
+
23
+ # Truncate module and funcName to first 'chars' characters
24
+ mod_chars = 10 # number of spaces to truncate to
25
+ fn_chars = 14 # number of spaces to truncate to
26
+ record.module = (record.module[:mod_chars] + ' ' * mod_chars)[:mod_chars]
27
+ record.funcName = (record.funcName[:fn_chars] + ' ' * fn_chars)[:fn_chars]
28
+ record.lineno = str(record.lineno).rjust(5) # Ensure line number is right-aligned
29
+ try:
30
+ return super().format(record)
31
+ except Exception as e:
32
+ logging.error(f'Error formatting log record: {e}')
33
+ raise e
sally/core/__init__.py ADDED
File without changes
sally/core/basing.py ADDED
@@ -0,0 +1,102 @@
1
+ # -*- encoding: utf-8 -*-
2
+ """
3
+ SALLY
4
+ sally.core.basing module
5
+
6
+ Database support
7
+ """
8
+ from keri.core import coring, serdering
9
+ from keri.db import dbing, subing
10
+
11
+ from sally import log_name, ogler
12
+
13
+ logger = ogler.getLogger(log_name)
14
+
15
+ class CueBaser(dbing.LMDBer):
16
+ """
17
+ Noter stores Notifications generated by the agent that are
18
+ intended to be read and dismissed by the controller of the agent.
19
+
20
+ """
21
+ TailDirPath = "sally/db"
22
+ AltTailDirPath = ".sally/db"
23
+ TempPrefix = "sally_db_"
24
+
25
+ def __init__(self, name="cb", headDirPath=None, reopen=True, **kwa):
26
+ """
27
+
28
+ Parameters:
29
+ headDirPath:
30
+ perm:
31
+ reopen:
32
+ kwa:
33
+ """
34
+ self.snd = None
35
+
36
+ self.iss = None
37
+ self.rev = None
38
+
39
+ self.recv = None
40
+ self.revk = None
41
+
42
+ self.ack = None
43
+
44
+ super(CueBaser, self).__init__(name=name, headDirPath=headDirPath, reopen=reopen, **kwa)
45
+
46
+ def reopen(self, **kwa):
47
+ """
48
+
49
+ :param kwa:
50
+ :return:
51
+ """
52
+ super(CueBaser, self).reopen(**kwa)
53
+
54
+ # Database of senders of the presentation or revocation messages
55
+ self.snd = subing.CesrSuber(db=self, subkey='snd.', klas=coring.Prefixer)
56
+
57
+ # Presentations waiting for the grant's embedded ACDC and TEL state to
58
+ # finish verification.
59
+ self.iss = subing.CesrSuber(db=self, subkey='iss.', klas=coring.Dater)
60
+ # revocations that are waiting for the TEL event to be received and processed
61
+ self.rev = subing.CesrSuber(db=self, subkey='rev.', klas=coring.Dater)
62
+
63
+ # presentations with resolved credentials that need to be sent to the hook
64
+ self.recv = subing.SerderSuber(db=self, subkey="recv", klas=serdering.SerderACDC)
65
+ # revocations whose TEL rev event has been resolved that need to be sent to the hook
66
+ self.revk = subing.SerderSuber(db=self, subkey="revk", klas=serdering.SerderACDC)
67
+
68
+ # presentations that have been sent to the hook that need to be ack'ed
69
+ self.ack = subing.SerderSuber(db=self, subkey="ack", klas=serdering.SerderACDC)
70
+
71
+ return self.env
72
+
73
+ def clearEscrows(self):
74
+ """
75
+ Clear all credential escrows. Useful in testing to avoid many unneeded log messages or force reprocessing of presentations.
76
+ """
77
+ self.iss.trim()
78
+ self.rev.trim()
79
+ self.recv.trim()
80
+ self.revk.trim()
81
+ self.ack.trim()
82
+ logger.info("Cleared iss and rev escrows")
83
+
84
+ def getCounts(self):
85
+ """
86
+ Get counts of each database for metrics monitoring
87
+ """
88
+ snd = len([said for (said,), prefixer in self.snd.getItemIter()])
89
+ iss = len([said for (said,), dater in self.iss.getItemIter()])
90
+ rev = len([said for (said,), dater in self.rev.getItemIter()])
91
+
92
+ recv = len([said for (said, dater_qb64), creder in self.recv.getItemIter()])
93
+ revk = len([said for (said, dater_qb64), creder in self.revk.getItemIter()])
94
+ ack = len([said for (said,), creder in self.ack.getItemIter()])
95
+ return {
96
+ 'senders': snd,
97
+ 'iss': iss,
98
+ 'rev': rev,
99
+ 'recv': recv,
100
+ 'revk': revk,
101
+ 'ack': ack
102
+ }
@@ -0,0 +1,59 @@
1
+ from hio.base import doing
2
+ from hio.help import decking
3
+ from keri.core import coring
4
+
5
+ from sally import ogler, log_name
6
+
7
+ logger = ogler.getLogger(log_name)
8
+
9
+
10
+ class TeveryCuery(doing.Doer):
11
+ """
12
+ Processes credential revocation cues and records when a given credential was revoked in the local cue database (CueBaser).
13
+ """
14
+
15
+ def __init__(self, cdb, reger, cues=None, **kwa):
16
+ """
17
+ Parameters:
18
+ cdb (CueBaser): instance of CueBaser database
19
+ reger (Reger): Stores ACDC / TEL events
20
+ cues (Deck): collection of events (cue) to process
21
+ """
22
+ self.cdb = cdb
23
+ self.reger = reger
24
+ self.cues = cues if cues is not None else decking.Deck()
25
+
26
+ super(TeveryCuery, self).__init__(**kwa)
27
+
28
+ def do(self, tymth, *, tock=0.0, **opts):
29
+ """
30
+ Iterates over the cues Deck and processes each ACDC revocation cue to save the ACDC sender and revocation time in the cue database.
31
+
32
+ Inherited Parameters:
33
+ tymth (function): closure for read only injection of cycle time in seconds
34
+ tock (float): cycle time in seconds
35
+ """
36
+ self.wind(tymth) # Set the clock with cycle time function
37
+ self.tock = tock # Set the cycle time of this doer
38
+ yield self.tock # allow priming of this doer to advance to this line
39
+
40
+ while True:
41
+ while self.cues:
42
+ cue = self.cues.popleft()
43
+ if cue['kin'] == "revoked":
44
+ serder = cue["serder"]
45
+ said = serder.ked["i"]
46
+ creder = self.reger.creds.get(said)
47
+ if creder is None:
48
+ logger.error(f"revocation received for unknown credential {said}")
49
+
50
+ prefixer = coring.Prefixer(qb64=creder.issuer)
51
+ saider = coring.Saider(qb64=said)
52
+ now = coring.Dater()
53
+
54
+ self.cdb.snd.pin(keys=(saider.qb64,), val=prefixer)
55
+ self.cdb.rev.pin(keys=(saider.qb64,), val=now)
56
+
57
+ yield self.tock
58
+
59
+ yield self.tock