Skip to content

Live moot sessions

gutenberg_kg.serve.moot_ops

Live moots for the Knowledge Press Forest: the worker holds each moot.

The forest starts a moot with moot_start, asks for each turn with moot_turn and plays it as it lands, and may end it with moot_stop. The worker keeps the :class:~gutenberg_kg.serve.entmoot.Moot between requests because a turn needs each book's passage pool, its cited passages and the rolling summary; sending those back and forth would be large, and a browser could send a doctored pool and pass the grounding check.

Everything here takes its search, synthesizer and clock as arguments, so it is tested without the handler, which opens stores and loads the embedder when it is imported (see :mod:gutenberg_kg.serve.scope).

Sessions live in memory: a worker restart ends every moot. At most :data:MAX_SESSIONS are held; a session idle for :data:IDLE_SECONDS is dropped, and when the table is full the least recently used one makes room.

MootSessions(client, synth, backend, is_book, voices, *, clock=time.monotonic)

The live moots one worker holds.

Parameters:

Name Type Description Default
client

Retrieval: anything with query(text, corpus=, k=) returning {"hits": [...]}, as WorkerClient does.

required
synth

The worker's text synthesizer.

required
backend str

The synthesizer's backend name, e.g. "omlx"; picks the default model as the CLI does.

required
is_book Callable[[str], bool]

Whether a key names one book or diary the worker serves.

required
voices dict[str, dict]

The author voice table, for each tree's voice.

required
clock Callable[[], float]

Seconds, monotonic; a seam for tests.

monotonic
Source code in src/gutenberg_kg/serve/moot_ops.py
87
88
89
90
91
92
93
94
95
96
97
98
99
def __init__(
    self,
    client,
    synth,
    backend: str,
    is_book: Callable[[str], bool],
    voices: dict[str, dict],
    *,
    clock: Callable[[], float] = time.monotonic,
) -> None:
    self.client, self.synth, self.backend = client, synth, backend
    self.is_book, self.voices, self.clock = is_book, voices, clock
    self.sessions: dict[str, _Session] = {}

handle(op, inp)

Answer one moot op.

Parameters:

Name Type Description Default
op str

One of :data:MOOT_OPS.

required
inp dict

The request's input.

required

Returns:

Type Description
dict

The op's result, or {"error": ...}.

Source code in src/gutenberg_kg/serve/moot_ops.py
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
def handle(self, op: str, inp: dict) -> dict:
    """Answer one moot op.

    :param op: One of :data:`MOOT_OPS`.
    :param inp: The request's ``input``.
    :returns: The op's result, or ``{"error": ...}``.
    """
    self._expire()
    if op == "moot_start":
        return self._start(inp)
    if op == "moot_turn":
        return self._turn(inp)
    if op == "moot_stop":
        return {"stopped": self.sessions.pop(str(inp.get("moot_id", "")), None) is not None}
    return {"error": f"unknown moot op {op!r}"}

authorized(inp, *, moot_token, handler_secret)

Whether a moot request may run.

MOOT_TOKEN exists so a browser never holds HANDLER_SECRET. When it is set, a moot op passes with that token; the handler secret also passes, for scripts. When neither is set, every moot op passes, as every other op does on a worker with no secret.

Parameters:

Name Type Description Default
inp dict

The request's input.

required
moot_token str

MOOT_TOKEN, or "".

required
handler_secret str

HANDLER_SECRET, or "".

required

Returns:

Type Description
bool

True when the request may run.

Source code in src/gutenberg_kg/serve/moot_ops.py
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
def authorized(inp: dict, *, moot_token: str, handler_secret: str) -> bool:
    """Whether a moot request may run.

    ``MOOT_TOKEN`` exists so a browser never holds ``HANDLER_SECRET``. When it
    is set, a moot op passes with that token; the handler secret also passes,
    for scripts. When neither is set, every moot op passes, as every other op
    does on a worker with no secret.

    :param inp: The request's ``input``.
    :param moot_token: ``MOOT_TOKEN``, or ``""``.
    :param handler_secret: ``HANDLER_SECRET``, or ``""``.
    :returns: ``True`` when the request may run.
    """
    if moot_token and hmac.compare_digest(str(inp.get("token", "")), moot_token):
        return True
    if handler_secret:
        return hmac.compare_digest(str(inp.get("secret", "")), handler_secret)
    return not moot_token