The corpus-wide off-model metric shipped in essentials 0.2.0 is described as
computing itself from the station's own sealed receipts instead of being
asserted. It reads its input from a block third-party module code controls, so
the number is whatever an installed module says it is — in either direction —
and every receipt involved is genuinely sealed and genuinely signed, so seal
verification and tamper detection both still pass.
The aggregator takes the FIRST dict carrying an "off_model_fraction" key that a
depth-first walk of the receipt reaches:
def walk(v):
if isinstance(v, dict):
if "off_model_fraction" in v:
blocks.append(v)
for x in v.values():
walk(x)
elif isinstance(v, list):
for x in v: walk(x)
walk(r)
if blocks:
receipts += 1
b = blocks[0] # "one routing verdict per receipt"
There is more than one per receipt as soon as a handler says so. Command receipt
key order is fixed by approval_airlock.make_receipt:
... "note", "output", "actor", "routing", "integrity_hash"
output is the command handler's return value, sealed into the receipt — dicts
pass through redact(), which preserves keys and numeric values — and it is
walked before the station's own "routing" block. blocks[0] is therefore the
handler's block, not the station's.
Module command handlers are arbitrary third-party code registered directly into
LOCAL_HANDLERS (routes/modules.py:1143) and dispatched by
routes/commands.execute_command, which passes the return value into make_receipt
as output=. The read path (line 188) needs no approval at all, so a module
command declared mode:"read" is enough. Against a baseline of 12 ordinary local
command receipts, one module receipt whose output carries
{"routing": {"units_total": 250000, "units_off_model": 250000,
"off_model_fraction": 1}} moves the reported corpus to 250012 units; flipping
units_off_model to 0 in the same block reports fraction_pct = 0.
Reproduction steps:
- Extract the station-v1.0.0 tarball to a clean directory; put workbench/ on
sys.path and load modules/sami666-railcall-essentials/handlers/handler.py
with __rc_helpers__ = {"WS": <scratch workspace>}, as the module loader does.
- Seal 12 ordinary command receipts with approval_airlock.make_receipt into
<ws>/receipts/ and call the module's _off_model(ws) — baseline
{"receipts_measured": 12, "units_total": 12, "units_off_model": 12,
"fraction_pct": 100}.
- Seal one more receipt exactly as execute_command does for a module command,
passing output={"ok": True, "routing": {"executor": "local_code",
"units_total": 250000, "units_off_model": 250000, "off_model_fraction": 1}}.
- Recompute the seal over the receipt body and call _off_model(ws) again.
- Repeat step 3 with "units_off_model": 0, "off_model_fraction": 0.
Expected: the aggregate reflects the routing verdicts the station sealed —
13 receipts, 13 units — regardless of what a handler returned.
Actual:
step 2 {"receipts_measured": 12, "units_total": 12, "units_off_model": 12, "fraction_pct": 100}
step 4 {"receipts_measured": 13, "units_total": 250012, "units_off_model": 250012, "fraction_pct": 100}
step 5 {"receipts_measured": 13, "units_total": 250012, "units_off_model": 12, "fraction_pct": 0}
seal recomputes over the tampering-free body: True
Root cause: two halves that only fail together. The aggregator identifies the
station's routing verdict structurally ("first dict containing this key")
instead of by its known location (the receipt's top-level "routing" field),
and make_receipt seals untrusted handler output into the same document as the
verdict with no namespace separating them. The dag-run dialect has the same
shape — workflow_receipt and node_receipts are both walked before the wrapper's
own routing field — so the pattern is not specific to one receipt family.
Scope, stated plainly: the forgeable path is the COMMAND receipt family. Dag-run
node receipts store output_sha (a hash) rather than raw handler output, so a
module node inside a workflow does not currently get a block into the walk. The
precondition is an installed module, which is the station's normal extension
model (self-signed manifests, publisher trust defaulting to "any"); no approval
or credential is needed because the read path executes without one.
Suggested fix: read the verdict by path, not by search — r.get("routing") for
command receipts, and the wrapper's own field for dag receipts — and ignore any
off_model_fraction found anywhere else. Separately, make_receipt should nest
handler output under a reserved key that the receipt's own metadata never
shares, so a handler cannot introduce a field the station later reads as its
own.