feat(event_envelope): close PARTIAL gaps — dichotomy wave
Oracle src/rune/event_envelope.py 303 lines / 8 fns: 5/8 -> 8/8
per scripts/dichotomy_audit.py (name-preserving match). The three
audit-named gaps land as Go surface in src/go/event-bus: ToDict
(to_dict), ToCanonicalDict (to_canonical_dict, incl. the loud
RuntimeError branch for un-bridged envelopes), TestEnvelope
(test_envelope), plus the canonical-bridge classmethod CreateEnvelope
(EventEnvelope.create) they depend on: event_type follows the CHANNELS
prior vocabulary (tool -> tool_result) with the prior-or-channel-or-
source-or-unknown fallback chain.
Parity repairs found on the way (all oracle-pinned):
- classifyAuthority/classifyUrgency were word-splitting: bare 'stop'
hit the amendment list (oracle: 'stop doing' is the multi-word
entry; 'Stop everything!' is emergency), 'stop,' and 'unstoppable'
misfired, and the low-urgency list matched 'when'/'you'/'can'/'no'/
'rush' as single words (oracle: 'when you can'/'no rush'; bare
'when' stays normal). Now \b<word>\b semantics with Unicode word
chars ('stopé' never matches).
- pyQuoteEvent had no ensure_ascii: unicode payloads hashed with raw
UTF-8 where the oracle hashes \uXXXX escapes — payload_hash
diverged on every unicode payload.
- createEnvelope now derives text from payload["text"] (oracle
signature; CLI folds --text in).
- minted ids/timestamps go through the no-clock/no-random seams
(EE_FROZEN_NOW + per-op uuid counter) — replayable under pins,
random uuid4-shaped ids in production.
Truth: scripts/gen_event_envelope_truth.py under the
scripts/ee_freeze_seams sitecustomize rig via the shared freeze_truth
harness (83-case envelope, frozen clock + deterministic uuid counter);
byte-identical across regens and freeze_truth --verify x2; embedded
VERBATIM in truth_envelope_test.go TestFrozenEnvelopeTruthReplay +
seam units (\b matcher, ensure_ascii escaping, event_type fallbacks,
loud bridge failure). Live parity 91/91 (scripts/
check_event_envelope_parity.py, PYTHON-json byte compare; negative
control detects divergence). gofmt/vet/go build clean; Python suite
green both pins (25 live + 25 RUNE_*_CORE=python). Dichotomy audit
5/8 PARTIAL -> 8/8 FULL.
This commit is contained in:
1 parent
870130ccc3
commit
fb1fd83466
8 files changed
+2922
-91
No files matched your search
@@ -0,0 +1,265 @@
|
||||
#!/usr/bin/env python3
|
||||
"""check_event_envelope_parity.py — live oracle-vs-Go parity sweep for
|
||||
the event_envelope gap closure (#2479, contract factory-gap-ee).
|
||||
|
||||
The frozen envelope (truth_envelope.json, 83 cases) pins the surface
|
||||
byte-identically. THIS script drives the LIVE Python oracle (under the
|
||||
scripts/ee_freeze_seams rig: frozen clock + deterministic uuid counter)
|
||||
and the Go binary (`op` mode, EE_FROZEN_NOW + per-op counter reset)
|
||||
across the same branches, comparing the PYTHON-json byte form of every
|
||||
result (separators ", "/": ", ensure_ascii, insertion order):
|
||||
|
||||
* classify_authority / classify_urgency — \b<word>\b regex semantics
|
||||
(multi-word "stop doing" vs bare "stop", punctuation adjacency,
|
||||
unicode word chars: "stopé" never matches, channel defaults).
|
||||
* create_envelope — legacy path: auto authority/urgency, explicit
|
||||
overrides, unicode + nested payloads.
|
||||
* EventEnvelope.create — canonical bridge: CHANNELS prior vocabulary,
|
||||
channel/source/unknown fallback chain, canonical asdict nesting.
|
||||
* to_dict — 9-key legacy dict with insertion-ordered payloads.
|
||||
* to_canonical_dict — schema_version-first key order,
|
||||
content-addressed payload_hash (ensure_ascii hashing), and the loud
|
||||
RuntimeError branch for un-bridged envelopes.
|
||||
* test_envelope — the in-module self-test's captured stdout.
|
||||
* CHANNELS / AUTHORITY_CLASSES — the constant tables.
|
||||
|
||||
Usage: PYTHONPATH=scripts/ee_freeze_seams:src python3 scripts/check_event_envelope_parity.py
|
||||
[--bin src/go/event-bus/event-bus]
|
||||
Exit 0 on 100% parity, 1 with the first divergences.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
REPO = Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(0, str(REPO / "src"))
|
||||
|
||||
FROZEN_NOW = "2026-10-14T11:22:33.123456+00:00"
|
||||
|
||||
import rune.event_envelope as ee # noqa: E402 (rig active via PYTHONPATH)
|
||||
|
||||
CLASSIFY_AUTHORITY = [
|
||||
("hello there", "conversation"),
|
||||
("Fix the failing test", "work"),
|
||||
("run the suite", "work"),
|
||||
("build it", "conversation"),
|
||||
("test everything", "conversation"),
|
||||
("create a file", "work"),
|
||||
("add the guard", "work"),
|
||||
("remove the shim", "work"),
|
||||
("update the docs", "work"),
|
||||
("change the port", "work"),
|
||||
("Stop everything!", "work"),
|
||||
("please cancel the order", "work"),
|
||||
("abort the run", "work"),
|
||||
("emergency shutdown", "work"),
|
||||
("halt", "work"),
|
||||
("Actually, don't modify the microkernel", "conversation"),
|
||||
("dont do that", "conversation"),
|
||||
("stop doing that", "work"),
|
||||
("use rust instead", "work"),
|
||||
("rather use go", "work"),
|
||||
("pause for a bit", "work"),
|
||||
("wait", "work"),
|
||||
("hold", "work"),
|
||||
("resume the work", "work"),
|
||||
("continue the build", "work"),
|
||||
("go", "work"),
|
||||
("how is it going", "work"),
|
||||
("what is the state", "work"),
|
||||
("status?", "work"),
|
||||
("progress report", "work"),
|
||||
("check the log", "work"),
|
||||
("hello", "work"),
|
||||
("hello", "conversation"),
|
||||
("hello", ""),
|
||||
("stop,", "work"),
|
||||
("unstoppable force", "work"),
|
||||
("stopit now", "work"),
|
||||
("", "work"),
|
||||
("", "conversation"),
|
||||
(" FIX it ", "work"),
|
||||
("café stop", "work"),
|
||||
("stopé now", "work"),
|
||||
("Let's BUILD it, then run tests", "work"),
|
||||
("what about the status, and check twice", "work"),
|
||||
]
|
||||
|
||||
CLASSIFY_URGENCY = [
|
||||
("anything", "emergency"),
|
||||
("urgent please", "directive"),
|
||||
("asap", "directive"),
|
||||
("immediately", "conversation"),
|
||||
("do it now", "directive"),
|
||||
("critical issue", "conversation"),
|
||||
("when you can", "directive"),
|
||||
("no rush", "directive"),
|
||||
("eventually", "conversation"),
|
||||
("sometime later", "conversation"),
|
||||
("when", "directive"),
|
||||
("rush", "directive"),
|
||||
("hello", "conversation"),
|
||||
("", "directive"),
|
||||
("NOW please", "directive"),
|
||||
("no rushing", "directive"), # \b boundary: "rushing" ≠ "rush"
|
||||
]
|
||||
|
||||
CREATE_ENVELOPE = [
|
||||
("user", "conversation", {"text": "hello"}),
|
||||
("user", "work", {"text": "Fix the failing test"}),
|
||||
("user", "work", {"text": "Stop everything!"}),
|
||||
("user", "conversation", {"text": "Actually, don't modify the microkernel"}),
|
||||
("user", "work", {"text": "when you can"}),
|
||||
("sensor", "sensor", {}),
|
||||
("user", "work", {"text": "run it"}, "status_query", "t-42", "low"),
|
||||
("user", "conversation", {"text": "hi", "extra": {"nested": [1, 2.5, None, True]}}),
|
||||
("user", "work", {"text": "café résumé 🎯", "z": 1, "a": 2}),
|
||||
("tool", "tool", {"result": "ok", "code": 0}),
|
||||
]
|
||||
|
||||
CREATE = [
|
||||
("user", "conversation", {"text": "hello"}),
|
||||
("tool", "tool", {"x": 1}),
|
||||
("user", "work", {"text": "Fix it"}),
|
||||
("system", "system", {}),
|
||||
("bg", "background", {}),
|
||||
("sensor", "sensor", {}),
|
||||
("user", "", {}),
|
||||
("", "", {}),
|
||||
("user", "unknown-channel", {"text": "hi"}),
|
||||
]
|
||||
|
||||
TO_DICT_STATES = [
|
||||
{"event_id": "evt_abc123def456", "timestamp": FROZEN_NOW,
|
||||
"source": "user", "channel": "conversation", "authority_class": "conversation",
|
||||
"task_id": None, "payload": {"text": "hi"}, "provenance": {}, "urgency": "normal"},
|
||||
{"event_id": "id-2", "timestamp": "2026-10-14T00:00:00+00:00",
|
||||
"source": "tool", "channel": "work", "authority_class": "directive",
|
||||
"task_id": "t-9", "payload": {"n": 1.5, "b": False, "nil": None, "list": [1, "two"]},
|
||||
"provenance": {"origin": "café", "chain": ["a", "b"]}, "urgency": "high"},
|
||||
{"event_id": "id-3", "timestamp": FROZEN_NOW,
|
||||
"source": "user", "channel": "", "authority_class": "",
|
||||
"task_id": None, "payload": {}, "provenance": {"k": "v"}, "urgency": "low"},
|
||||
]
|
||||
|
||||
TO_CANONICAL = [
|
||||
("user", "conversation", {"text": "hello"}),
|
||||
("tool", "tool", {"x": 1, "a": 2.0}),
|
||||
("user", "work", {"text": "café résumé", "z": 1, "a": [True, None]}),
|
||||
("system", "system", {}),
|
||||
("bg", "background", {"nested": {"deep": [{"k": "v"}, 7]}}),
|
||||
]
|
||||
|
||||
|
||||
def run_go(binpath, env, req):
|
||||
p = subprocess.run([str(binpath), "op"], input=json.dumps(req),
|
||||
capture_output=True, text=True, env=env)
|
||||
if p.returncode != 0:
|
||||
raise RuntimeError(f"go op {req.get('op')} exited {p.returncode}: "
|
||||
f"{p.stdout} {p.stderr}")
|
||||
return json.loads(p.stdout)["json"]
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--bin", default=str(REPO / "src/go/event-bus/event-bus"))
|
||||
args = ap.parse_args()
|
||||
binpath = Path(args.bin)
|
||||
if not binpath.exists():
|
||||
print(f"Go binary missing: {binpath} (go build -o event-bus . in src/go/event-bus)")
|
||||
return 1
|
||||
|
||||
go_env = dict(os.environ)
|
||||
go_env["EE_FROZEN_NOW"] = FROZEN_NOW
|
||||
go_env.pop("EE_UUID_BASE", None)
|
||||
|
||||
fails = total = 0
|
||||
|
||||
def check(name, oracle_json, go_json):
|
||||
nonlocal fails, total
|
||||
total += 1
|
||||
if oracle_json != go_json:
|
||||
fails += 1
|
||||
print(f"DIVERGE {name}:\n oracle: {oracle_json[:300]}\n"
|
||||
f" go: {go_json[:300]}")
|
||||
|
||||
# classify — public fns (no mints; live semantics)
|
||||
for text, channel in CLASSIFY_AUTHORITY:
|
||||
check(f"classify_authority:{text[:30]!r}",
|
||||
json.dumps(ee.classify_authority(text, channel)),
|
||||
run_go(binpath, go_env, {"op": "classify_authority",
|
||||
"text": text, "channel": channel}))
|
||||
for text, authority in CLASSIFY_URGENCY:
|
||||
check(f"classify_urgency:{text[:30]!r}",
|
||||
json.dumps(ee.classify_urgency(text, authority)),
|
||||
run_go(binpath, go_env, {"op": "classify_urgency",
|
||||
"text": text, "authority_class": authority}))
|
||||
|
||||
# create_envelope — rig seam (per-call mint reset) vs op (per-op reset)
|
||||
for row in CREATE_ENVELOPE:
|
||||
source, channel, payload = row[0], row[1], row[2]
|
||||
req = {"op": "create_envelope", "source": source, "channel": channel,
|
||||
"payload": payload}
|
||||
if len(row) > 3:
|
||||
req["authority_class"] = row[3]
|
||||
if len(row) > 4:
|
||||
req["task_id"] = row[4]
|
||||
if len(row) > 5:
|
||||
req["urgency"] = row[5]
|
||||
check(f"create_envelope:{source}/{channel}/{str(payload)[:30]}",
|
||||
json.dumps(ee.ee_create_envelope(*row)),
|
||||
run_go(binpath, go_env, req))
|
||||
|
||||
# EventEnvelope.create — canonical bridge
|
||||
for source, channel, payload in CREATE:
|
||||
check(f"create:{source}/{channel}",
|
||||
json.dumps(ee.ee_create(source, channel, payload)),
|
||||
run_go(binpath, go_env, {"op": "create", "source": source,
|
||||
"channel": channel, "payload": payload}))
|
||||
|
||||
# to_dict — explicit states (insertion-ordered payloads byte-compared)
|
||||
for state in TO_DICT_STATES:
|
||||
full = dict(state)
|
||||
full["_canonical"] = None
|
||||
check(f"to_dict:{state['event_id']}",
|
||||
json.dumps(ee.ee_to_dict(full)),
|
||||
run_go(binpath, go_env, {"op": "to_dict", "state": full}))
|
||||
|
||||
# to_canonical_dict — bridged
|
||||
for source, channel, payload in TO_CANONICAL:
|
||||
check(f"to_canonical_dict:{source}/{channel}/{str(payload)[:30]}",
|
||||
json.dumps(ee.ee_to_canonical_dict(source, channel, payload)),
|
||||
run_go(binpath, go_env, {"op": "to_canonical_dict", "source": source,
|
||||
"channel": channel, "payload": payload}))
|
||||
|
||||
# to_canonical_dict — unbridged loud failure
|
||||
state = dict(TO_DICT_STATES[0])
|
||||
state["_canonical"] = None
|
||||
check("to_canonical_dict:unbridged",
|
||||
json.dumps(ee.ee_to_canonical_dict_unbridged(state)),
|
||||
run_go(binpath, go_env, {"op": "to_canonical_dict_unbridged",
|
||||
"state": state}))
|
||||
|
||||
# test_envelope — captured stdout
|
||||
check("test_envelope",
|
||||
json.dumps(ee.ee_test_envelope()),
|
||||
run_go(binpath, go_env, {"op": "test_envelope"}))
|
||||
|
||||
# constant tables
|
||||
check("channels",
|
||||
json.dumps(ee.ee_channels()),
|
||||
run_go(binpath, go_env, {"op": "channels"}))
|
||||
check("authority_classes",
|
||||
json.dumps(ee.ee_authority_classes()),
|
||||
run_go(binpath, go_env, {"op": "authority_classes"}))
|
||||
|
||||
print(f"parity: {total - fails}/{total} match")
|
||||
return 1 if fails else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,127 @@
|
||||
"""ee_freeze_seams — sitecustomize rig for freezing event_envelope truth.
|
||||
|
||||
Auto-imported on interpreter start (PYTHONPATH=scripts/ee_freeze_seams:src)
|
||||
by BOTH the truth generator (scripts/gen_event_envelope_truth.py) and the
|
||||
shared harness's --verify mode, so frozen truth is byte-reproducible in any
|
||||
process (dichotomy wave #2479 gap port, contract factory-gap-ee):
|
||||
|
||||
* datetime.datetime is FROZEN to EE_FROZEN_NOW — the oracle stamps
|
||||
timestamps at TWO clock sites the shared harness's module pin cannot
|
||||
both reach: the legacy default_factory
|
||||
(datetime.now(timezone.utc).isoformat()) and the canonical bridge
|
||||
(rune.orchestration.records._now, bound import). The class patch
|
||||
below runs before either module imports.
|
||||
* uuid.uuid4 is a deterministic counter (ci_freeze_seams precedent) —
|
||||
records.new_id mints "evt_" + uuid4().hex[:12] and the legacy
|
||||
default_factory mints str(uuid4()). The counter RESETS per seam
|
||||
helper call so a case's ids depend only on how many mints happen
|
||||
inside it.
|
||||
* The module seams (ee_create, ee_create_envelope, ee_to_dict,
|
||||
ee_to_canonical_dict, ee_to_canonical_dict_unbridged,
|
||||
ee_test_envelope) are bound onto rune.event_envelope so the shared
|
||||
harness's getattr(module, fn) can freeze method/classmethod surface:
|
||||
wrappers run the REAL oracle fns and return JSON-safe asdicts (or
|
||||
the captured RuntimeError message / stdout bytes for the
|
||||
loud-failure and print paths).
|
||||
"""
|
||||
|
||||
import contextlib # noqa: E402
|
||||
import io # noqa: E402
|
||||
import itertools # noqa: E402
|
||||
import os # noqa: E402
|
||||
import sys # noqa: E402
|
||||
import uuid as _uuid # noqa: E402
|
||||
from dataclasses import asdict # noqa: E402
|
||||
|
||||
_REPO = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
sys.path.insert(0, os.path.join(_REPO, "scripts"))
|
||||
sys.path.insert(0, os.path.join(_REPO, "src"))
|
||||
|
||||
from freeze_rigs import pin_clock # noqa: E402
|
||||
|
||||
# EE_FROZEN_NOW: the pinned instant every clock site reports.
|
||||
FROZEN_NOW = "2026-10-14T11:22:33.123456+00:00"
|
||||
pin_clock(FROZEN_NOW)
|
||||
|
||||
_MINT = itertools.count(1)
|
||||
|
||||
|
||||
class _DetUUID:
|
||||
"""uuid4() stand-in whose .hex (records.new_id) and str() (legacy
|
||||
default_factory) are pure functions of the counter."""
|
||||
|
||||
def __init__(self, n: int):
|
||||
self.hex = f"{n:032x}"
|
||||
self.int = int(self.hex, 16)
|
||||
|
||||
def __str__(self):
|
||||
h = self.hex
|
||||
return f"{h[:8]}-{h[8:12]}-{h[12:16]}-{h[16:20]}-{h[20:]}"
|
||||
|
||||
|
||||
def _reset_mint():
|
||||
global _MINT
|
||||
_MINT = itertools.count(1)
|
||||
|
||||
|
||||
_uuid.uuid4 = lambda: _DetUUID(next(_MINT)) # type: ignore[assignment]
|
||||
|
||||
import rune.event_envelope as _ee # noqa: E402 (after the clock + uuid pins)
|
||||
|
||||
|
||||
def ee_channels():
|
||||
return _ee.CHANNELS
|
||||
|
||||
|
||||
def ee_authority_classes():
|
||||
return _ee.AUTHORITY_CLASSES
|
||||
|
||||
|
||||
def ee_create(source, channel, payload):
|
||||
_reset_mint()
|
||||
return asdict(_ee.EventEnvelope.create(source, channel, payload))
|
||||
|
||||
|
||||
def ee_create_envelope(source, channel, payload, authority_class=None,
|
||||
task_id=None, urgency=None):
|
||||
_reset_mint()
|
||||
return asdict(_ee.create_envelope(source, channel, payload,
|
||||
authority_class, task_id, urgency))
|
||||
|
||||
|
||||
def ee_to_dict(state):
|
||||
# state carries EVERY legacy field explicitly — no default mints, so
|
||||
# the row is deterministic without a counter reset.
|
||||
return _ee.EventEnvelope(**state).to_dict()
|
||||
|
||||
|
||||
def ee_to_canonical_dict(source, channel, payload):
|
||||
_reset_mint()
|
||||
return _ee.EventEnvelope.create(source, channel, payload).to_canonical_dict()
|
||||
|
||||
|
||||
def ee_to_canonical_dict_unbridged(state):
|
||||
env = _ee.EventEnvelope(**state)
|
||||
try:
|
||||
env.to_canonical_dict()
|
||||
return {"error": None}
|
||||
except RuntimeError as e:
|
||||
return {"error": "RuntimeError", "message": str(e)}
|
||||
|
||||
|
||||
def ee_test_envelope():
|
||||
_reset_mint()
|
||||
buf = io.StringIO()
|
||||
with contextlib.redirect_stdout(buf):
|
||||
_ee.test_envelope()
|
||||
return buf.getvalue()
|
||||
|
||||
|
||||
_ee.ee_channels = ee_channels
|
||||
_ee.ee_authority_classes = ee_authority_classes
|
||||
_ee.ee_create = ee_create
|
||||
_ee.ee_create_envelope = ee_create_envelope
|
||||
_ee.ee_to_dict = ee_to_dict
|
||||
_ee.ee_to_canonical_dict = ee_to_canonical_dict
|
||||
_ee.ee_to_canonical_dict_unbridged = ee_to_canonical_dict_unbridged
|
||||
_ee.ee_test_envelope = ee_test_envelope
|
||||
@@ -0,0 +1,171 @@
|
||||
#!/usr/bin/env python3
|
||||
"""gen_event_envelope_truth.py — shared-harness truth battery for the
|
||||
event_envelope gap closure (#2479, contract factory-gap-ee).
|
||||
|
||||
Uses scripts/freeze_truth.py (the ONE truth law) under the
|
||||
scripts/ee_freeze_seams sitecustomize rig (PYTHONPATH carries BOTH) to
|
||||
freeze src/rune/event_envelope.py behavior for the Go port at
|
||||
src/go/event-bus:
|
||||
|
||||
* classify_authority / classify_urgency — the \\b<word>\\b regex
|
||||
semantics incl. the multi-word amendment list ("stop doing" is NOT
|
||||
bare "stop"), punctuation adjacency ("stop," still matches), and the
|
||||
channel-default fallbacks.
|
||||
* create_envelope — the legacy direct-constructor path (auto authority
|
||||
+ urgency from payload["text"], explicit overrides, minted
|
||||
event_id/timestamp under the deterministic uuid + frozen clock).
|
||||
* EventEnvelope.create — the canonical-bridge classmethod: event_type
|
||||
follows the CHANNELS prior vocabulary (tool -> tool_result),
|
||||
channel-not-in-CHANNELS fallback chain, and the nested canonical
|
||||
record rides in asdict form (_canonical).
|
||||
* to_dict — the 9-key legacy dict.
|
||||
* to_canonical_dict — schema_version-1 dict with content-addressed
|
||||
payload_hash (key order: schema_version first, payload_hash in
|
||||
place), incl. the loud RuntimeError branch for un-bridged
|
||||
envelopes (message-only contract).
|
||||
* test_envelope — the in-module self-test's exact stdout bytes
|
||||
(captured under the rig's deterministic ids).
|
||||
* CHANNELS / AUTHORITY_CLASSES — the constant tables.
|
||||
|
||||
Verify (byte-identical law):
|
||||
PYTHONPATH=scripts/ee_freeze_seams:src \\
|
||||
python3 scripts/freeze_truth.py --verify src/go/event-bus/truth_envelope.json \\
|
||||
--module rune.event_envelope
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
REPO = Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(0, str(REPO / "src"))
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
|
||||
FROZEN_NOW = "2026-10-14T11:22:33.123456+00:00"
|
||||
os.environ["RUNE_FROZEN_NOW"] = FROZEN_NOW
|
||||
|
||||
from freeze_truth import freeze, write_envelope # noqa: E402
|
||||
|
||||
|
||||
def main():
|
||||
cases = [
|
||||
# ── constant tables ────────────────────────────────────────────
|
||||
("ee_channels", (), {}),
|
||||
("ee_authority_classes", (), {}),
|
||||
# ── classify_authority — regex \b semantics ────────────────────
|
||||
("classify_authority", ("hello there", "conversation"), {}),
|
||||
("classify_authority", ("Fix the failing test", "work"), {}), # directive
|
||||
("classify_authority", ("run the suite", "work"), {}), # directive
|
||||
("classify_authority", ("build it", "conversation"), {}), # directive
|
||||
("classify_authority", ("test everything", "conversation"), {}), # directive
|
||||
("classify_authority", ("create a file", "work"), {}), # directive
|
||||
("classify_authority", ("add the guard", "work"), {}), # directive
|
||||
("classify_authority", ("remove the shim", "work"), {}), # directive
|
||||
("classify_authority", ("update the docs", "work"), {}), # directive
|
||||
("classify_authority", ("change the port", "work"), {}), # directive
|
||||
("classify_authority", ("Stop everything!", "work"), {}), # emergency
|
||||
("classify_authority", ("please cancel the order", "work"), {}), # emergency
|
||||
("classify_authority", ("abort the run", "work"), {}), # emergency
|
||||
("classify_authority", ("emergency shutdown", "work"), {}), # emergency
|
||||
("classify_authority", ("halt", "work"), {}), # emergency
|
||||
# task amendments BEFORE emergency — multi-word lists
|
||||
("classify_authority", ("Actually, don't modify the microkernel", "conversation"), {}),
|
||||
("classify_authority", ("dont do that", "conversation"), {}), # dont (no apostrophe)
|
||||
("classify_authority", ("stop doing that", "work"), {}), # "stop doing" ≠ bare stop
|
||||
("classify_authority", ("use rust instead", "work"), {}), # instead
|
||||
("classify_authority", ("rather use go", "work"), {}), # rather
|
||||
# pause / resume
|
||||
("classify_authority", ("pause for a bit", "work"), {}), # pause
|
||||
("classify_authority", ("wait", "work"), {}), # wait
|
||||
("classify_authority", ("hold", "work"), {}), # hold
|
||||
("classify_authority", ("resume the work", "work"), {}), # resume
|
||||
("classify_authority", ("continue the build", "work"), {}), # continue
|
||||
("classify_authority", ("go", "work"), {}), # go
|
||||
# status queries
|
||||
("classify_authority", ("how is it going", "work"), {}), # how
|
||||
("classify_authority", ("what is the state", "work"), {}), # what
|
||||
("classify_authority", ("status?", "work"), {}), # status
|
||||
("classify_authority", ("progress report", "work"), {}), # progress
|
||||
("classify_authority", ("check the log", "work"), {}), # check
|
||||
# channel defaults + boundary/adjacency quirks
|
||||
("classify_authority", ("hello", "work"), {}), # work default → directive
|
||||
("classify_authority", ("hello", "conversation"), {}), # conversation default
|
||||
("classify_authority", ("hello", ""), {}), # non-work default
|
||||
("classify_authority", ("stop,", "work"), {}), # punctuation-adjacent → emergency
|
||||
("classify_authority", ("unstoppable force", "work"), {}), # \b boundary → no match
|
||||
("classify_authority", ("stopit now", "work"), {}), # no boundary → resume (now? no—
|
||||
# ^ "stopit" fails \bstop\b; no other list hits → work default directive
|
||||
("classify_authority", ("", "work"), {}), # empty text → work default
|
||||
("classify_authority", ("", "conversation"), {}), # empty → conversation
|
||||
("classify_authority", (" FIX it ", "work"), {}), # lower().strip() + \b spacing
|
||||
("classify_authority", ("café stop", "work"), {}), # unicode text, ascii word
|
||||
("classify_authority", ("stopé now", "work"), {}), # é is a word char: no \b after stop
|
||||
# ── classify_urgency ──────────────────────────────────────────
|
||||
("classify_urgency", ("anything", "emergency"), {}), # critical via class
|
||||
("classify_urgency", ("urgent please", "directive"), {}), # high
|
||||
("classify_urgency", ("asap", "directive"), {}), # high
|
||||
("classify_urgency", ("immediately", "conversation"), {}), # high
|
||||
("classify_urgency", ("do it now", "directive"), {}), # high
|
||||
("classify_urgency", ("critical issue", "conversation"), {}), # high
|
||||
("classify_urgency", ("when you can", "directive"), {}), # low (multi-word)
|
||||
("classify_urgency", ("no rush", "directive"), {}), # low (multi-word)
|
||||
("classify_urgency", ("eventually", "conversation"), {}), # low
|
||||
("classify_urgency", ("sometime later", "conversation"), {}), # low
|
||||
("classify_urgency", ("when", "directive"), {}), # bare "when" → normal (multi-word quirk)
|
||||
("classify_urgency", ("rush", "directive"), {}), # bare "rush" → normal
|
||||
("classify_urgency", ("hello", "conversation"), {}), # normal
|
||||
("classify_urgency", ("", "directive"), {}), # normal
|
||||
# ── create_envelope — legacy path (auto + explicit) ───────────
|
||||
("ee_create_envelope", ("user", "conversation", {"text": "hello"}), {}),
|
||||
("ee_create_envelope", ("user", "work", {"text": "Fix the failing test"}), {}),
|
||||
("ee_create_envelope", ("user", "work", {"text": "Stop everything!"}), {}),
|
||||
("ee_create_envelope", ("user", "conversation", {"text": "Actually, don't modify the microkernel"}), {}),
|
||||
("ee_create_envelope", ("user", "work", {"text": "when you can"}), {}),
|
||||
("ee_create_envelope", ("sensor", "sensor", {}), {}), # no text key → defaults
|
||||
("ee_create_envelope", ("user", "work", {"text": "run it"}, "status_query", "t-42", "low"), {}),
|
||||
("ee_create_envelope", ("user", "conversation", {"text": "hi", "extra": {"nested": [1, 2.5, None, True]}}), {}),
|
||||
# ── EventEnvelope.create — canonical bridge ───────────────────
|
||||
("ee_create", ("user", "conversation", {"text": "hello"}), {}),
|
||||
("ee_create", ("tool", "tool", {"x": 1}), {}), # prior: tool_result
|
||||
("ee_create", ("user", "work", {"text": "Fix it"}), {}), # prior: directive
|
||||
("ee_create", ("system", "system", {}), {}), # prior: system_event
|
||||
("ee_create", ("bg", "background", {}), {}), # prior: background
|
||||
("ee_create", ("sensor", "sensor", {}), {}), # prior: sensor
|
||||
("ee_create", ("user", "", {}), {}), # channel "" → source fallback
|
||||
("ee_create", ("", "", {}), {}), # everything empty → unknown
|
||||
("ee_create", ("user", "unknown-channel", {"text": "hi"}), {}), # channel not in CHANNELS
|
||||
# ── to_dict — 9-key legacy dict ───────────────────────────────
|
||||
("ee_to_dict", ({"event_id": "evt_abc123def456",
|
||||
"timestamp": "2026-10-14T11:22:33.123456+00:00",
|
||||
"source": "user", "channel": "conversation",
|
||||
"authority_class": "conversation", "task_id": None,
|
||||
"payload": {"text": "hi"}, "provenance": {},
|
||||
"urgency": "normal", "_canonical": None},), {}),
|
||||
("ee_to_dict", ({"event_id": "id-2", "timestamp": "2026-10-14T00:00:00+00:00",
|
||||
"source": "tool", "channel": "work",
|
||||
"authority_class": "directive", "task_id": "t-9",
|
||||
"payload": {"n": 1.5, "b": False, "nil": None, "list": [1, "two"]},
|
||||
"provenance": {"origin": "café", "chain": ["a", "b"]},
|
||||
"urgency": "high", "_canonical": None},), {}),
|
||||
# ── to_canonical_dict — bridged + loud failure ────────────────
|
||||
("ee_to_canonical_dict", ("user", "conversation", {"text": "hello"}), {}),
|
||||
("ee_to_canonical_dict", ("tool", "tool", {"x": 1, "a": 2.0}), {}), # integral float in hash corpus
|
||||
("ee_to_canonical_dict", ("user", "work", {"text": "café résumé", "z": 1, "a": [True, None]}), {}),
|
||||
("ee_to_canonical_dict", ("system", "system", {}), {}), # empty payload hash
|
||||
("ee_to_canonical_dict_unbridged", ({"event_id": "legacy-1",
|
||||
"timestamp": "2026-10-14T11:22:33.123456+00:00",
|
||||
"source": "user", "channel": "conversation",
|
||||
"authority_class": "", "task_id": None,
|
||||
"payload": {}, "provenance": {},
|
||||
"urgency": "normal", "_canonical": None},), {}),
|
||||
# ── test_envelope — captured stdout ───────────────────────────
|
||||
("ee_test_envelope", (), {}),
|
||||
]
|
||||
truth = freeze("rune.event_envelope", cases, oracle_module="rune.event_envelope")
|
||||
dest = REPO / "src/go/event-bus/truth_envelope.json"
|
||||
write_envelope(FROZEN_NOW, truth, dest)
|
||||
print(f"wrote {len(truth)} cases -> {dest}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+28
-8
@@ -85,6 +85,19 @@ func writeEventJSON(sb *strings.Builder, v interface{}) {
|
||||
writeEventJSON(sb, x[k])
|
||||
}
|
||||
sb.WriteString("}")
|
||||
case *ordObj:
|
||||
// NOT sorted: json.dumps preserves INSERTION order unless
|
||||
// sort_keys is set; to_dict byte parity needs the input order.
|
||||
sb.WriteString("{")
|
||||
for i, k := range x.keys {
|
||||
if i > 0 {
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
sb.WriteString(pyQuoteEvent(k))
|
||||
sb.WriteString(": ")
|
||||
writeEventJSON(sb, x.vals[k])
|
||||
}
|
||||
sb.WriteString("}")
|
||||
default:
|
||||
b, _ := json.Marshal(x)
|
||||
sb.Write(b)
|
||||
@@ -148,15 +161,16 @@ func pyQuoteEvent(s string) string {
|
||||
case '\t':
|
||||
sb.WriteString("\\t")
|
||||
default:
|
||||
if r < 0x20 {
|
||||
sb.WriteString("\\u")
|
||||
const hexdig = "0123456789abcdef"
|
||||
sb.WriteByte('0')
|
||||
sb.WriteByte('0')
|
||||
sb.WriteByte(hexdig[(r>>4)&0xf])
|
||||
sb.WriteByte(hexdig[r&0xf])
|
||||
} else {
|
||||
// ensure_ascii=True: everything outside printable ASCII is
|
||||
// \uXXXX (lowercase hex; astral runes as surrogate pairs).
|
||||
if r >= 0x20 && r < 0x7f {
|
||||
sb.WriteRune(r)
|
||||
} else if r < 0x10000 {
|
||||
sb.WriteString(fmtPyUEscape(r))
|
||||
} else {
|
||||
r -= 0x10000
|
||||
sb.WriteString(fmtPyUEscape(0xd800 + (r >> 10)))
|
||||
sb.WriteString(fmtPyUEscape(0xdc00 + (r & 0x3ff)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -164,6 +178,12 @@ func pyQuoteEvent(s string) string {
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func fmtPyUEscape(r rune) string {
|
||||
const hexdig = "0123456789abcdef"
|
||||
return string([]byte{'\\', 'u',
|
||||
hexdig[(r>>12)&0xf], hexdig[(r>>8)&0xf], hexdig[(r>>4)&0xf], hexdig[r&0xf]})
|
||||
}
|
||||
|
||||
// AddEvent — append an event to a JSONL stream (parity with the Python
|
||||
// event_bus.py append path). The hash IS the content hash.
|
||||
func AddEvent(eventJSON string) string {
|
||||
|
||||
@@ -0,0 +1,544 @@
|
||||
package main
|
||||
|
||||
// envelope.go — event_envelope.py port (dichotomy gap close, factory-gap-ee).
|
||||
// The three audit-named gaps land here name-preserving: ToDict (to_dict),
|
||||
// ToCanonicalDict (to_canonical_dict, incl. the loud RuntimeError branch),
|
||||
// TestEnvelope (test_envelope), plus the canonical-bridge classmethod
|
||||
// CreateEnvelope (EventEnvelope.create) they depend on. classifyAuthority /
|
||||
// classifyUrgency / createEnvelope move to the oracle's regex semantics:
|
||||
// Python matches \b<re.escape(word)>\b against the lowercased text — word
|
||||
// BOUNDARIES, not word splitting — with Unicode word chars (é is \w), so
|
||||
// "stop," fires, "unstoppable" does not, "stopé" does not, and the
|
||||
// multi-word amendment "stop doing" is NOT bare "stop".
|
||||
//
|
||||
// Determinism seams (no-clock/no-random law): EE_FROZEN_NOW pins the
|
||||
// timestamp, the uuid counter (reset per op, mirroring the ee_freeze_seams
|
||||
// rig) pins minted ids — the battery and parity replay byte-identical.
|
||||
|
||||
import (
|
||||
cryptorand "crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// ── constant tables (CHANNELS / AUTHORITY_CLASSES) ────────────────────────
|
||||
|
||||
// ordKV — an *ordObj from ordered key/value pairs (insertion order is
|
||||
// the serialization order).
|
||||
func ordKV(pairs ...interface{}) *ordObj {
|
||||
o := newOrdObj()
|
||||
for i := 0; i+1 < len(pairs); i += 2 {
|
||||
o.set(pairs[i].(string), pairs[i+1])
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
var eeChannels = map[string]interface{}{
|
||||
"conversation": ordKV("prior", "conversation", "tools_allowed", false, "verification_expected", false),
|
||||
"work": ordKV("prior", "directive", "tools_allowed", true, "verification_expected", true),
|
||||
"system": ordKV("prior", "system_event", "tools_allowed", false, "verification_expected", false),
|
||||
"tool": ordKV("prior", "tool_result", "tools_allowed", false, "verification_expected", false),
|
||||
"background": ordKV("prior", "background", "tools_allowed", false, "verification_expected", false),
|
||||
"sensor": ordKV("prior", "sensor", "tools_allowed", false, "verification_expected", false),
|
||||
}
|
||||
|
||||
var eeAuthorityClasses = map[string]interface{}{
|
||||
"conversation": ordKV("priority", 1, "can_amend_task", false),
|
||||
"status_query": ordKV("priority", 2, "can_amend_task", false),
|
||||
"directive": ordKV("priority", 3, "can_amend_task", true),
|
||||
"task_amendment": ordKV("priority", 4, "can_amend_task", true),
|
||||
"pause": ordKV("priority", 5, "can_amend_task", true),
|
||||
"resume": ordKV("priority", 5, "can_amend_task", true),
|
||||
"cancel": ordKV("priority", 6, "can_amend_task", true),
|
||||
"emergency": ordKV("priority", 7, "can_amend_task", true),
|
||||
"system_event": ordKV("priority", 1, "can_amend_task", false),
|
||||
}
|
||||
|
||||
// ── Python \b<word>\b semantics ───────────────────────────────────────────
|
||||
|
||||
// pyIsWordChar — Python re \w for str patterns (Unicode): letters, digits
|
||||
// (all numeric categories), underscore.
|
||||
func pyIsWordChar(r rune) bool {
|
||||
return r == '_' || unicode.IsLetter(r) || unicode.IsNumber(r)
|
||||
}
|
||||
|
||||
// pyWordMatch — \b<word>\b against text: an occurrence of word whose
|
||||
// neighboring runes are not word chars (string edges are boundaries).
|
||||
func pyWordMatch(text, word string) bool {
|
||||
t, w := []rune(text), []rune(word)
|
||||
if len(w) == 0 || len(w) > len(t) {
|
||||
return false
|
||||
}
|
||||
for i := 0; i+len(w) <= len(t); i++ {
|
||||
if string(t[i:i+len(w)]) != word {
|
||||
continue
|
||||
}
|
||||
leftOK := i == 0 || !pyIsWordChar(t[i-1])
|
||||
end := i + len(w)
|
||||
rightOK := end == len(t) || !pyIsWordChar(t[end])
|
||||
if leftOK && rightOK {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func anyWordMatch(text string, words []string) bool {
|
||||
for _, w := range words {
|
||||
if pyWordMatch(text, w) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ── classify (oracle regex semantics) ─────────────────────────────────────
|
||||
|
||||
var (
|
||||
amendmentWords = []string{"don't", "dont", "stop doing", "actually", "instead", "rather"}
|
||||
emergencyWords = []string{"stop", "cancel", "abort", "emergency", "halt"}
|
||||
pauseWords = []string{"pause", "wait", "hold"}
|
||||
resumeWords = []string{"resume", "continue", "go"}
|
||||
directiveWords = []string{"fix", "run", "build", "test", "create", "add", "remove", "update", "change"}
|
||||
statusWords = []string{"how", "what", "status", "progress", "check"}
|
||||
urgencyHigh = []string{"urgent", "asap", "immediately", "now", "critical"}
|
||||
urgencyLow = []string{"when you can", "no rush", "eventually", "sometime"}
|
||||
)
|
||||
|
||||
func classifyAuthority(text, channel string) string {
|
||||
lower := strings.ToLower(strings.TrimSpace(text))
|
||||
// Task amendments BEFORE emergency ("stop doing that" ≠ bare "stop").
|
||||
if anyWordMatch(lower, amendmentWords) {
|
||||
return "task_amendment"
|
||||
}
|
||||
if anyWordMatch(lower, emergencyWords) {
|
||||
return "emergency"
|
||||
}
|
||||
if anyWordMatch(lower, pauseWords) {
|
||||
return "pause"
|
||||
}
|
||||
if anyWordMatch(lower, resumeWords) {
|
||||
return "resume"
|
||||
}
|
||||
if anyWordMatch(lower, directiveWords) {
|
||||
return "directive"
|
||||
}
|
||||
if anyWordMatch(lower, statusWords) {
|
||||
return "status_query"
|
||||
}
|
||||
if channel == "work" {
|
||||
return "directive"
|
||||
}
|
||||
return "conversation"
|
||||
}
|
||||
|
||||
func classifyUrgency(text, authorityClass string) string {
|
||||
lower := strings.ToLower(strings.TrimSpace(text))
|
||||
if authorityClass == "emergency" {
|
||||
return "critical"
|
||||
}
|
||||
if anyWordMatch(lower, urgencyHigh) {
|
||||
return "high"
|
||||
}
|
||||
if anyWordMatch(lower, urgencyLow) {
|
||||
return "low"
|
||||
}
|
||||
return "normal"
|
||||
}
|
||||
|
||||
// ── deterministic id / clock seams ────────────────────────────────────────
|
||||
|
||||
var eeUUIDNext int64
|
||||
|
||||
// resetEEUUID — per-op counter reset (the rig's _reset_mint law: a case's
|
||||
// ids depend only on the mints that happen inside it).
|
||||
func resetEEUUID() {
|
||||
base := int64(1)
|
||||
if v := os.Getenv("EE_UUID_BASE"); v != "" {
|
||||
fmt.Sscanf(v, "%d", &base)
|
||||
}
|
||||
eeUUIDNext = base - 1 // eeDetHex increments BEFORE use (count(1) yields 1 first)
|
||||
}
|
||||
|
||||
// eeDetHex — the uuid4() stand-in. Under the determinism pins (battery +
|
||||
// parity set EE_FROZEN_NOW / EE_UUID_BASE) it is the rig's counter;
|
||||
// production paths mint a random 32-hex uuid like the oracle's uuid4().
|
||||
func eeDetHex() string {
|
||||
eeUUIDNext++
|
||||
if os.Getenv("EE_FROZEN_NOW") == "" && os.Getenv("EE_UUID_BASE") == "" {
|
||||
b := make([]byte, 16)
|
||||
if _, err := cryptorand.Read(b); err != nil {
|
||||
return fmt.Sprintf("%032x", eeUUIDNext) // unreachable in practice
|
||||
}
|
||||
return hex.EncodeToString(b)
|
||||
}
|
||||
return fmt.Sprintf("%032x", eeUUIDNext)
|
||||
}
|
||||
|
||||
// eeMintEventID — records.new_id: "evt_" + uuid4().hex[:12].
|
||||
func eeMintEventID() string {
|
||||
return "evt_" + eeDetHex()[:12]
|
||||
}
|
||||
|
||||
// eeMintLegacyID — str(uuid4()): the dashed 8-4-4-4-12 form.
|
||||
func eeMintLegacyID() string {
|
||||
h := eeDetHex()
|
||||
return h[:8] + "-" + h[8:12] + "-" + h[12:16] + "-" + h[16:20] + "-" + h[20:]
|
||||
}
|
||||
|
||||
// eeNowISO — datetime.now(timezone.utc).isoformat(): +00:00 offset, the
|
||||
// microsecond fraction only when nonzero.
|
||||
func eeNowISO() string {
|
||||
if v := os.Getenv("EE_FROZEN_NOW"); v != "" {
|
||||
return v
|
||||
}
|
||||
t := time.Now().UTC()
|
||||
if t.Nanosecond()/1000 == 0 {
|
||||
return t.Format("2006-01-02T15:04:05+00:00")
|
||||
}
|
||||
return t.Format("2006-01-02T15:04:05.000000+00:00")
|
||||
}
|
||||
|
||||
// ── the legacy envelope (event_envelope.py EventEnvelope) ─────────────────
|
||||
|
||||
// LegacyEventEnvelope — the legacy dataclass shape. Payload/Provenance stay
|
||||
// interface{} so the op path can carry insertion-ordered payloads
|
||||
// (*ordObj) for json.dumps byte parity.
|
||||
type LegacyEventEnvelope struct {
|
||||
EventID string
|
||||
Timestamp string
|
||||
Source string
|
||||
Channel string
|
||||
AuthorityClass string
|
||||
TaskID *string
|
||||
Payload interface{}
|
||||
Provenance interface{}
|
||||
Urgency string
|
||||
Canonical *EventEnvelope // _canonical bridge; nil = unbridged
|
||||
}
|
||||
|
||||
func payloadAsMap(v interface{}) map[string]interface{} {
|
||||
switch x := v.(type) {
|
||||
case map[string]interface{}:
|
||||
return x
|
||||
case *ordObj:
|
||||
return x.vals
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func payloadText(v interface{}) string {
|
||||
if m := payloadAsMap(v); m != nil {
|
||||
s, _ := m["text"].(string)
|
||||
return s
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func strPtr(s string) *string { return &s }
|
||||
|
||||
// createEnvelope — event_envelope.py create_envelope: the legacy
|
||||
// direct-constructor path (auto authority + urgency from payload["text"]).
|
||||
func createEnvelope(source, channel string, payload interface{},
|
||||
authorityClass, taskID, urgency *string) LegacyEventEnvelope {
|
||||
text := payloadText(payload)
|
||||
if authorityClass == nil {
|
||||
authorityClass = strPtr(classifyAuthority(text, channel))
|
||||
}
|
||||
if urgency == nil {
|
||||
urgency = strPtr(classifyUrgency(text, *authorityClass))
|
||||
}
|
||||
if payload == nil {
|
||||
payload = map[string]interface{}{}
|
||||
}
|
||||
return LegacyEventEnvelope{
|
||||
EventID: eeMintLegacyID(),
|
||||
Timestamp: eeNowISO(),
|
||||
Source: source,
|
||||
Channel: channel,
|
||||
AuthorityClass: *authorityClass,
|
||||
TaskID: taskID,
|
||||
Payload: payload,
|
||||
Provenance: map[string]interface{}{},
|
||||
Urgency: *urgency,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateEnvelope — EventEnvelope.create classmethod: the canonical record
|
||||
// is built first, the legacy wrapper derived from it (schema migration
|
||||
// shim; one dialect, not two).
|
||||
func CreateEnvelope(source, channel string, payload interface{}) LegacyEventEnvelope {
|
||||
// event_type = CHANNELS.get(channel, {}).get("prior") or channel or
|
||||
// source or "unknown"
|
||||
eventType := channel
|
||||
if ch, ok := eeChannels[channel].(*ordObj); ok {
|
||||
if p, ok := ch.vals["prior"].(string); ok && p != "" {
|
||||
eventType = p
|
||||
}
|
||||
}
|
||||
if eventType == "" {
|
||||
eventType = source
|
||||
}
|
||||
if eventType == "" {
|
||||
eventType = "unknown"
|
||||
}
|
||||
if payload == nil {
|
||||
payload = map[string]interface{}{}
|
||||
}
|
||||
canonical := &EventEnvelope{
|
||||
EventID: eeMintEventID(),
|
||||
EventType: eventType,
|
||||
PartitionKey: channel,
|
||||
Timestamp: eeNowISO(),
|
||||
}
|
||||
return LegacyEventEnvelope{
|
||||
EventID: canonical.EventID,
|
||||
Timestamp: canonical.Timestamp,
|
||||
Source: source,
|
||||
Channel: channel,
|
||||
TaskID: nil,
|
||||
Payload: payload,
|
||||
Provenance: map[string]interface{}{},
|
||||
Urgency: "normal",
|
||||
Canonical: canonical,
|
||||
}
|
||||
}
|
||||
|
||||
// ToDict — the 9-key legacy dict.
|
||||
func (e *LegacyEventEnvelope) ToDict() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"event_id": e.EventID,
|
||||
"timestamp": e.Timestamp,
|
||||
"source": e.Source,
|
||||
"channel": e.Channel,
|
||||
"authority_class": e.AuthorityClass,
|
||||
"task_id": nullableString(e.TaskID),
|
||||
"payload": payloadValue(e.Payload),
|
||||
"provenance": payloadValue(e.Provenance),
|
||||
"urgency": e.Urgency,
|
||||
}
|
||||
}
|
||||
|
||||
func nullableString(p *string) interface{} {
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
return *p
|
||||
}
|
||||
|
||||
func payloadValue(v interface{}) interface{} {
|
||||
if v == nil {
|
||||
return map[string]interface{}{}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// canonicalBridgeUnavailable — the loud-failure contract (X2): direct-
|
||||
// constructed envelopes have NO canonical bridge; the message is the
|
||||
// contract (str(e) strips the exception type).
|
||||
const canonicalBridgeUnavailable = "canonical bridge unavailable: this envelope was not created via EventEnvelope.create(); use create(source, channel, payload) for the canonical-backed path"
|
||||
|
||||
// ToCanonicalDict — schema_version-1 canonical dict with the
|
||||
// content-addressed payload_hash (key order mirrors asdict: schema_version
|
||||
// first, payload_hash in place).
|
||||
func (e *LegacyEventEnvelope) ToCanonicalDict() (map[string]interface{}, error) {
|
||||
if e.Canonical == nil {
|
||||
return nil, fmt.Errorf("%s", canonicalBridgeUnavailable)
|
||||
}
|
||||
c := e.Canonical
|
||||
var parentID interface{}
|
||||
if c.ParentID != "" {
|
||||
parentID = c.ParentID
|
||||
}
|
||||
return map[string]interface{}{
|
||||
"schema_version": 1,
|
||||
"event_type": c.EventType,
|
||||
"partition_key": c.PartitionKey,
|
||||
"event_id": c.EventID,
|
||||
"parent_id": parentID,
|
||||
"actor_id": c.ActorID,
|
||||
"device_id": c.DeviceID,
|
||||
"authorization": c.Authorization,
|
||||
"payload_hash": ContentHashEvent(payloadAsMap(e.Payload)),
|
||||
"timestamp": c.Timestamp,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// TestEnvelope — event_envelope.py test_envelope: the in-module self-test
|
||||
// (prints its report, asserts the four classes). Returns the captured
|
||||
// report exactly as the oracle prints it.
|
||||
func TestEnvelope() (string, error) {
|
||||
resetEEUUID()
|
||||
var sb strings.Builder
|
||||
sb.WriteString("Testing Event Envelope...\n")
|
||||
cases := []struct {
|
||||
label, text, channel, wantAuthority, wantUrgency string
|
||||
}{
|
||||
{"Greeting", "hello", "conversation", "conversation", "normal"},
|
||||
{"Directive", "Fix the failing test", "work", "directive", "normal"},
|
||||
{"Emergency", "Stop everything!", "work", "emergency", "critical"},
|
||||
{"Amendment", "Actually, don't modify the microkernel", "conversation", "task_amendment", ""},
|
||||
}
|
||||
for _, c := range cases {
|
||||
env := createEnvelope("user", c.channel,
|
||||
map[string]interface{}{"text": c.text}, nil, nil, nil)
|
||||
sb.WriteString(fmt.Sprintf("%s: authority=%s, urgency=%s\n",
|
||||
c.label, env.AuthorityClass, env.Urgency))
|
||||
if env.AuthorityClass != c.wantAuthority {
|
||||
return sb.String(), fmt.Errorf("%s: authority %q != %q",
|
||||
c.label, env.AuthorityClass, c.wantAuthority)
|
||||
}
|
||||
if c.wantUrgency != "" && env.Urgency != c.wantUrgency {
|
||||
return sb.String(), fmt.Errorf("%s: urgency %q != %q",
|
||||
c.label, env.Urgency, c.wantUrgency)
|
||||
}
|
||||
}
|
||||
sb.WriteString("✅ All tests passed!\n")
|
||||
return sb.String(), nil
|
||||
}
|
||||
|
||||
// ── asdict + json.dumps byte-parity serializers ───────────────────────────
|
||||
|
||||
// asdictMap — dataclasses.asdict of the legacy envelope (_canonical rides
|
||||
// along as its own asdict; None bridge → nil).
|
||||
func asdictMap(e *LegacyEventEnvelope) map[string]interface{} {
|
||||
var canonical interface{}
|
||||
if e.Canonical != nil {
|
||||
canonical = canonicalAsdict(e.Canonical)
|
||||
}
|
||||
return map[string]interface{}{
|
||||
"event_id": e.EventID,
|
||||
"timestamp": e.Timestamp,
|
||||
"source": e.Source,
|
||||
"channel": e.Channel,
|
||||
"authority_class": e.AuthorityClass,
|
||||
"task_id": nullableString(e.TaskID),
|
||||
"payload": payloadValue(e.Payload),
|
||||
"provenance": payloadValue(e.Provenance),
|
||||
"urgency": e.Urgency,
|
||||
"_canonical": canonical,
|
||||
}
|
||||
}
|
||||
|
||||
// canonicalAsdict — OrchestrationRecord field order: base fields first
|
||||
// (schema_version), then the subclass's.
|
||||
func canonicalAsdict(c *EventEnvelope) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"schema_version": 1,
|
||||
"event_type": c.EventType,
|
||||
"partition_key": c.PartitionKey,
|
||||
"event_id": c.EventID,
|
||||
"parent_id": nil, // create() never binds a parent
|
||||
"actor_id": c.ActorID,
|
||||
"device_id": c.DeviceID,
|
||||
"authorization": c.Authorization,
|
||||
"payload_hash": c.PayloadHash,
|
||||
"timestamp": c.Timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
// writeOrderedKV — "\"key\": " + value via the pyDumps writer.
|
||||
func writeOrderedKV(sb *strings.Builder, key string, val interface{}) {
|
||||
sb.WriteString(pyQuoteEvent(key))
|
||||
sb.WriteString(": ")
|
||||
writeEventJSON(sb, val)
|
||||
}
|
||||
|
||||
var legacyAsdictKeys = append(append([]string(nil), legacyDictKeys...), "_canonical")
|
||||
|
||||
// asdictJSON — json.dumps(dataclasses.asdict(envelope)) byte parity:
|
||||
// dataclass field order (the nested canonical keeps ITS field order —
|
||||
// schema_version first), payload/provenance keep insertion order.
|
||||
func asdictJSON(e *LegacyEventEnvelope) string {
|
||||
var sb strings.Builder
|
||||
sb.WriteString("{")
|
||||
for i, k := range legacyAsdictKeys {
|
||||
if i > 0 {
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
if k == "_canonical" && e.Canonical != nil {
|
||||
sb.WriteString(pyQuoteEvent(k))
|
||||
sb.WriteString(": ")
|
||||
writeOrderedObj(&sb, canonicalDictKeys, func(kk string) interface{} {
|
||||
return canonicalAsdict(e.Canonical)[kk]
|
||||
})
|
||||
continue
|
||||
}
|
||||
writeOrderedKV(&sb, k, e.ToDict()[k])
|
||||
}
|
||||
sb.WriteString("}")
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// writeOrderedObj — {k1: v1, k2: v2, ...} in the SLICE order (json.dumps
|
||||
// insertion order; NOT sorted).
|
||||
func writeOrderedObj(sb *strings.Builder, keys []string, get func(string) interface{}) {
|
||||
sb.WriteString("{")
|
||||
for i, k := range keys {
|
||||
if i > 0 {
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
writeOrderedKV(sb, k, get(k))
|
||||
}
|
||||
sb.WriteString("}")
|
||||
}
|
||||
|
||||
var legacyDictKeys = []string{"event_id", "timestamp", "source", "channel",
|
||||
"authority_class", "task_id", "payload", "provenance", "urgency"}
|
||||
|
||||
// toDictJSON — json.dumps(envelope.to_dict()) byte parity (Python default
|
||||
// separators, insertion order — payload/provenance keep THEIR order).
|
||||
func toDictJSON(e *LegacyEventEnvelope) string {
|
||||
var sb strings.Builder
|
||||
writeOrderedObj(&sb, legacyDictKeys, func(k string) interface{} {
|
||||
return e.ToDict()[k]
|
||||
})
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
var canonicalDictKeys = []string{"schema_version", "event_type", "partition_key",
|
||||
"event_id", "parent_id", "actor_id", "device_id", "authorization",
|
||||
"payload_hash", "timestamp"}
|
||||
|
||||
// toCanonicalDictJSON — json.dumps(to_canonical_dict()) byte parity.
|
||||
func toCanonicalDictJSON(e *LegacyEventEnvelope) (string, error) {
|
||||
d, err := e.ToCanonicalDict()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var sb strings.Builder
|
||||
writeOrderedObj(&sb, canonicalDictKeys, func(k string) interface{} {
|
||||
return d[k]
|
||||
})
|
||||
return sb.String(), nil
|
||||
}
|
||||
|
||||
// ── ordObj — insertion-ordered JSON object (byte parity for payloads) ─────
|
||||
|
||||
type ordObj struct {
|
||||
keys []string
|
||||
vals map[string]interface{}
|
||||
}
|
||||
|
||||
func newOrdObj() *ordObj {
|
||||
return &ordObj{vals: map[string]interface{}{}}
|
||||
}
|
||||
|
||||
func (o *ordObj) set(k string, v interface{}) {
|
||||
if _, exists := o.vals[k]; !exists {
|
||||
o.keys = append(o.keys, k)
|
||||
}
|
||||
o.vals[k] = v
|
||||
}
|
||||
|
||||
// sortedKeys — sort_keys=True view (the hash corpus).
|
||||
func (o *ordObj) sortedKeys() []string {
|
||||
keys := append([]string(nil), o.keys...)
|
||||
sort.Strings(keys)
|
||||
return keys
|
||||
}
|
||||
+201
-83
@@ -229,7 +229,45 @@ func main() {
|
||||
}
|
||||
}
|
||||
}
|
||||
out, _ := json.MarshalIndent(createEnvelope(source, channel, text, payload), "", " ")
|
||||
if payload == nil {
|
||||
payload = map[string]interface{}{}
|
||||
}
|
||||
if text != "" {
|
||||
payload["text"] = text
|
||||
}
|
||||
env := createEnvelope(source, channel, payload, nil, nil, nil)
|
||||
out, _ := json.MarshalIndent(env.ToDict(), "", " ")
|
||||
fmt.Println(string(out))
|
||||
|
||||
case "op":
|
||||
// op — JSON-in/JSON-out parity surface (dichotomy gap close):
|
||||
// the freeze/parity harnesses drive every ported fn through
|
||||
// here under the EE_FROZEN_NOW / EE_UUID_BASE pins.
|
||||
dec := json.NewDecoder(os.Stdin)
|
||||
dec.UseNumber()
|
||||
v, err := decodeOrderedValue(dec)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "event-bus op: bad request: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
req, ok := v.(*ordObj)
|
||||
if !ok {
|
||||
fmt.Fprintf(os.Stderr, "event-bus op: request must be an object\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
pyJSON, err := runOp(req)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "event-bus op: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
var semantic interface{}
|
||||
dec2 := json.NewDecoder(strings.NewReader(pyJSON))
|
||||
dec2.UseNumber()
|
||||
dec2.Decode(&semantic)
|
||||
out, _ := json.Marshal(map[string]interface{}{
|
||||
"json": pyJSON,
|
||||
"result": semantic,
|
||||
})
|
||||
fmt.Println(string(out))
|
||||
|
||||
default:
|
||||
@@ -238,104 +276,184 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
// ── event envelope operations (W4: event_envelope port) ──────────────────
|
||||
// ── op mode — the parity dispatch (envelope.go carries the port) ──────────
|
||||
|
||||
type EnvelopeRequest struct {
|
||||
Source string `json:"source"`
|
||||
Channel string `json:"channel"`
|
||||
Payload map[string]interface{} `json:"payload"`
|
||||
Text string `json:"text"`
|
||||
// decodeOrderedValue / decodeOrderedFrom — token walk preserving object
|
||||
// key order (*ordObj) and number literals (json.Number via UseNumber).
|
||||
func decodeOrderedValue(dec *json.Decoder) (interface{}, error) {
|
||||
tok, err := dec.Token()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return decodeOrderedFrom(dec, tok)
|
||||
}
|
||||
|
||||
func classifyAuthority(text, channel string) string {
|
||||
lower := strings.ToLower(strings.TrimSpace(text))
|
||||
words := strings.Fields(lower)
|
||||
|
||||
hasAny := func(words []string, targets ...string) bool {
|
||||
for _, w := range words {
|
||||
for _, t := range targets {
|
||||
if w == t {
|
||||
return true
|
||||
func decodeOrderedFrom(dec *json.Decoder, tok json.Token) (interface{}, error) {
|
||||
if d, ok := tok.(json.Delim); ok {
|
||||
switch d {
|
||||
case '{':
|
||||
o := newOrdObj()
|
||||
for {
|
||||
kt, err := dec.Token()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if dd, ok := kt.(json.Delim); ok && dd == '}' {
|
||||
return o, nil
|
||||
}
|
||||
key, _ := kt.(string)
|
||||
val, err := decodeOrderedValue(dec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
o.set(key, val)
|
||||
}
|
||||
case '[':
|
||||
arr := []interface{}{}
|
||||
for {
|
||||
kt, err := dec.Token()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if dd, ok := kt.(json.Delim); ok && dd == ']' {
|
||||
return arr, nil
|
||||
}
|
||||
val, err := decodeOrderedFrom(dec, kt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
arr = append(arr, val)
|
||||
}
|
||||
}
|
||||
return false
|
||||
return nil, fmt.Errorf("unexpected delimiter %v", d)
|
||||
}
|
||||
|
||||
// Task amendments first (before emergency)
|
||||
if hasAny(words, "don't", "dont", "stop", "doing", "actually", "instead", "rather") {
|
||||
return "task_amendment"
|
||||
}
|
||||
if hasAny(words, "stop", "cancel", "abort", "emergency", "halt") {
|
||||
return "emergency"
|
||||
}
|
||||
if hasAny(words, "pause", "wait", "hold") {
|
||||
return "pause"
|
||||
}
|
||||
if hasAny(words, "resume", "continue", "go") {
|
||||
return "resume"
|
||||
}
|
||||
if hasAny(words, "fix", "run", "build", "test", "create", "add", "remove", "update", "change") {
|
||||
return "directive"
|
||||
}
|
||||
if hasAny(words, "how", "what", "status", "progress", "check") {
|
||||
return "status_query"
|
||||
}
|
||||
if channel == "work" {
|
||||
return "directive"
|
||||
}
|
||||
return "conversation"
|
||||
return tok, nil
|
||||
}
|
||||
|
||||
func classifyUrgency(text, authorityClass string) string {
|
||||
lower := strings.ToLower(strings.TrimSpace(text))
|
||||
words := strings.Fields(lower)
|
||||
func opGet(o *ordObj, key string) (interface{}, bool) {
|
||||
v, ok := o.vals[key]
|
||||
return v, ok
|
||||
}
|
||||
|
||||
hasAny := func(targets ...string) bool {
|
||||
for _, w := range words {
|
||||
for _, t := range targets {
|
||||
if w == t {
|
||||
return true
|
||||
func opStr(o *ordObj, key string) string {
|
||||
s, _ := o.vals[key].(string)
|
||||
return s
|
||||
}
|
||||
|
||||
func optStrPtr(o *ordObj, key string) *string {
|
||||
v, ok := opGet(o, key)
|
||||
if !ok || v == nil {
|
||||
return nil
|
||||
}
|
||||
s, _ := v.(string)
|
||||
return &s
|
||||
}
|
||||
|
||||
// legacyStateKeys — the ee_to_dict / ee_to_canonical_dict_unbridged state
|
||||
// dicts arrive in dataclass field order; values map onto the struct.
|
||||
func legacyFromState(state interface{}) *LegacyEventEnvelope {
|
||||
m := payloadAsMap(state)
|
||||
if m == nil {
|
||||
return &LegacyEventEnvelope{}
|
||||
}
|
||||
taskID := (*string)(nil)
|
||||
if v, ok := m["task_id"].(string); ok {
|
||||
taskID = &v
|
||||
}
|
||||
var canonical *EventEnvelope
|
||||
return &LegacyEventEnvelope{
|
||||
EventID: m["event_id"].(string),
|
||||
Timestamp: m["timestamp"].(string),
|
||||
Source: m["source"].(string),
|
||||
Channel: m["channel"].(string),
|
||||
AuthorityClass: m["authority_class"].(string),
|
||||
TaskID: taskID,
|
||||
Payload: m["payload"],
|
||||
Provenance: m["provenance"],
|
||||
Urgency: m["urgency"].(string),
|
||||
Canonical: canonical,
|
||||
}
|
||||
}
|
||||
|
||||
// runOp — one parity op; returns the PYTHON-json byte form (separators,
|
||||
// ensure_ascii, insertion order). The uuid counter resets per op,
|
||||
// mirroring the ee_freeze_seams rig.
|
||||
func runOp(req *ordObj) (string, error) {
|
||||
resetEEUUID()
|
||||
switch opStr(req, "op") {
|
||||
case "classify_authority":
|
||||
return pyDumpsValue(classifyAuthority(opStr(req, "text"), opStr(req, "channel"))), nil
|
||||
case "classify_urgency":
|
||||
return pyDumpsValue(classifyUrgency(opStr(req, "text"), opStr(req, "authority_class"))), nil
|
||||
case "create_envelope":
|
||||
payload, _ := opGet(req, "payload")
|
||||
env := createEnvelope(opStr(req, "source"), opStr(req, "channel"), payload,
|
||||
optStrPtr(req, "authority_class"), optStrPtr(req, "task_id"),
|
||||
optStrPtr(req, "urgency"))
|
||||
return asdictJSON(&env), nil
|
||||
case "create":
|
||||
payload, _ := opGet(req, "payload")
|
||||
env := CreateEnvelope(opStr(req, "source"), opStr(req, "channel"), payload)
|
||||
return asdictJSON(&env), nil
|
||||
case "to_dict":
|
||||
env := legacyFromState(mustArg(req, "state"))
|
||||
return toDictJSON(env), nil
|
||||
case "to_canonical_dict":
|
||||
payload, _ := opGet(req, "payload")
|
||||
env := CreateEnvelope(opStr(req, "source"), opStr(req, "channel"), payload)
|
||||
return toCanonicalDictJSON(&env)
|
||||
case "to_canonical_dict_unbridged":
|
||||
env := legacyFromState(mustArg(req, "state"))
|
||||
if _, err := env.ToCanonicalDict(); err != nil {
|
||||
var sb strings.Builder
|
||||
writeOrderedObj(&sb, []string{"error", "message"}, func(k string) interface{} {
|
||||
if k == "error" {
|
||||
return "RuntimeError"
|
||||
}
|
||||
}
|
||||
return canonicalBridgeUnavailable
|
||||
})
|
||||
return sb.String(), nil
|
||||
}
|
||||
return false
|
||||
return "", fmt.Errorf("unbridged state unexpectedly bridged")
|
||||
case "test_envelope":
|
||||
out, err := TestEnvelope()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return pyDumpsValue(out), nil
|
||||
case "channels":
|
||||
return pyDumpsValue(orderedTable(eeChannelKeys, func(k string) interface{} { return eeChannels[k] })), nil
|
||||
case "authority_classes":
|
||||
return pyDumpsValue(orderedTable(eeAuthorityKeys, func(k string) interface{} { return eeAuthorityClasses[k] })), nil
|
||||
}
|
||||
|
||||
if authorityClass == "emergency" {
|
||||
return "critical"
|
||||
}
|
||||
if hasAny("urgent", "asap", "immediately", "now", "critical") {
|
||||
return "high"
|
||||
}
|
||||
if hasAny("when", "you", "can", "no", "rush", "eventually", "sometime") {
|
||||
return "low"
|
||||
}
|
||||
return "normal"
|
||||
return "", fmt.Errorf("unknown op: %q", opStr(req, "op"))
|
||||
}
|
||||
|
||||
func createEnvelope(source, channel, text string, payload map[string]interface{}) map[string]interface{} {
|
||||
authority := classifyAuthority(text, channel)
|
||||
urgency := classifyUrgency(text, authority)
|
||||
env := EventEnvelope{
|
||||
EventID: newID("evt"),
|
||||
EventType: channel,
|
||||
PartitionKey: source,
|
||||
Timestamp: nowISO(),
|
||||
}
|
||||
if payload != nil {
|
||||
env.PayloadHash = contentHash(payload)
|
||||
}
|
||||
return map[string]interface{}{
|
||||
"event_id": env.EventID,
|
||||
"timestamp": env.Timestamp,
|
||||
"source": source,
|
||||
"channel": channel,
|
||||
"authority_class": authority,
|
||||
"urgency": urgency,
|
||||
"payload_hash": env.PayloadHash,
|
||||
"payload": payload,
|
||||
func mustArg(req *ordObj, key string) interface{} {
|
||||
v, _ := opGet(req, key)
|
||||
return v
|
||||
}
|
||||
|
||||
// eeChannelKeys / eeAuthorityKeys — the tables iterate in the ORACLE's
|
||||
// definition order (json.dumps insertion-order law).
|
||||
var eeChannelKeys = []string{"conversation", "work", "system", "tool", "background", "sensor"}
|
||||
var eeAuthorityKeys = []string{"conversation", "status_query", "directive",
|
||||
"task_amendment", "pause", "resume", "cancel", "emergency", "system_event"}
|
||||
|
||||
func pyDumpsValue(v interface{}) string {
|
||||
var sb strings.Builder
|
||||
writeEventJSON(&sb, v)
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// orderedTable — an *ordObj built from a fixed key order (table constants
|
||||
// serialize in the oracle's definition order).
|
||||
func orderedTable(keys []string, get func(string) interface{}) *ordObj {
|
||||
o := newOrdObj()
|
||||
for _, k := range keys {
|
||||
o.set(k, get(k))
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,264 @@
|
||||
package main
|
||||
|
||||
// truth_envelope_test.go — the event_envelope parity gate (dichotomy gap
|
||||
// close, factory-gap-ee). Replays every case from truth_envelope.json
|
||||
// (the shared freeze_truth.py envelope: frozen clock + deterministic uuid
|
||||
// counter, 83 Python-oracle cases WITH inputs, generated by
|
||||
// scripts/gen_event_envelope_truth.py under scripts/ee_freeze_seams)
|
||||
// VERBATIM through the ported Go functions: the constant tables,
|
||||
// classifyAuthority/classifyUrgency (\b<word>\b regex semantics incl.
|
||||
// the multi-word amendment list, punctuation adjacency, unicode word
|
||||
// chars), createEnvelope (legacy path, auto + explicit), CreateEnvelope
|
||||
// (the canonical-bridge classmethod with the CHANNELS prior vocabulary
|
||||
// and the channel/source/unknown fallback chain), ToDict (9-key legacy
|
||||
// dict), ToCanonicalDict (schema_version-first key order,
|
||||
// content-addressed payload_hash incl. ensure_ascii unicode hashing,
|
||||
// and the loud RuntimeError branch), and TestEnvelope (captured stdout).
|
||||
// A divergence fails the port, not the oracle.
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
//go:embed truth_envelope.json
|
||||
var frozenEnvelopeTruthJSON string
|
||||
|
||||
type envelopeCase struct {
|
||||
Fn string `json:"fn"`
|
||||
Args []any `json:"args"`
|
||||
Kwargs map[string]any `json:"kwargs"`
|
||||
Expected json.RawMessage `json:"expected"`
|
||||
}
|
||||
|
||||
func canonDecT(t *testing.T, s string) any {
|
||||
t.Helper()
|
||||
var out any
|
||||
dec := json.NewDecoder(strings.NewReader(s))
|
||||
dec.UseNumber()
|
||||
if err := dec.Decode(&out); err != nil {
|
||||
t.Fatalf("decode actual %q: %v", s, err)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func canonRawT(t *testing.T, raw json.RawMessage) any {
|
||||
t.Helper()
|
||||
var out any
|
||||
dec := json.NewDecoder(strings.NewReader(string(raw)))
|
||||
dec.UseNumber()
|
||||
if err := dec.Decode(&out); err != nil {
|
||||
t.Fatalf("decode expected %q: %v", raw, err)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// batteryPayload — re-decode a case arg ORDERED (the outer reader lost
|
||||
// key order) so payload insertion order and number literals ride through
|
||||
// pyDumpsValue byte-identically.
|
||||
func batteryPayload(t *testing.T, v any) any {
|
||||
t.Helper()
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
t.Fatalf("re-encode arg: %v", err)
|
||||
}
|
||||
dec := json.NewDecoder(strings.NewReader(string(b)))
|
||||
dec.UseNumber()
|
||||
out, err := decodeOrderedValue(dec)
|
||||
if err != nil {
|
||||
t.Fatalf("ordered decode %s: %v", b, err)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func argString(t *testing.T, v any, what string) string {
|
||||
t.Helper()
|
||||
s, ok := v.(string)
|
||||
if !ok {
|
||||
t.Fatalf("expected string %s, got %v (%T)", what, v, v)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func optArgString(args []any, i int) *string {
|
||||
if i >= len(args) || args[i] == nil {
|
||||
return nil
|
||||
}
|
||||
s, _ := args[i].(string)
|
||||
return &s
|
||||
}
|
||||
|
||||
// runEnvelopeCase — dispatch one frozen case; returns the PYTHON-json
|
||||
// byte form of the actual (same renderer the op surface uses).
|
||||
func runEnvelopeCase(t *testing.T, c envelopeCase) string {
|
||||
t.Helper()
|
||||
resetEEUUID()
|
||||
switch c.Fn {
|
||||
case "ee_channels":
|
||||
return pyDumpsValue(orderedTable(eeChannelKeys, func(k string) interface{} { return eeChannels[k] }))
|
||||
case "ee_authority_classes":
|
||||
return pyDumpsValue(orderedTable(eeAuthorityKeys, func(k string) interface{} { return eeAuthorityClasses[k] }))
|
||||
case "classify_authority":
|
||||
return pyDumpsValue(classifyAuthority(
|
||||
argString(t, c.Args[0], "text"), argString(t, c.Args[1], "channel")))
|
||||
case "classify_urgency":
|
||||
return pyDumpsValue(classifyUrgency(
|
||||
argString(t, c.Args[0], "text"), argString(t, c.Args[1], "authority_class")))
|
||||
case "ee_create_envelope":
|
||||
env := createEnvelope(
|
||||
argString(t, c.Args[0], "source"), argString(t, c.Args[1], "channel"),
|
||||
batteryPayload(t, c.Args[2]),
|
||||
optArgString(c.Args, 3), optArgString(c.Args, 4), optArgString(c.Args, 5))
|
||||
return asdictJSON(&env)
|
||||
case "ee_create":
|
||||
env := CreateEnvelope(
|
||||
argString(t, c.Args[0], "source"), argString(t, c.Args[1], "channel"),
|
||||
batteryPayload(t, c.Args[2]))
|
||||
return asdictJSON(&env)
|
||||
case "ee_to_dict":
|
||||
env := legacyFromState(batteryPayload(t, c.Args[0]))
|
||||
return toDictJSON(env)
|
||||
case "ee_to_canonical_dict":
|
||||
env := CreateEnvelope(
|
||||
argString(t, c.Args[0], "source"), argString(t, c.Args[1], "channel"),
|
||||
batteryPayload(t, c.Args[2]))
|
||||
out, err := toCanonicalDictJSON(&env)
|
||||
if err != nil {
|
||||
t.Fatalf("to_canonical_dict: %v", err)
|
||||
}
|
||||
return out
|
||||
case "ee_to_canonical_dict_unbridged":
|
||||
env := legacyFromState(batteryPayload(t, c.Args[0]))
|
||||
if _, err := env.ToCanonicalDict(); err == nil {
|
||||
t.Fatalf("unbridged state unexpectedly bridged")
|
||||
}
|
||||
var sb strings.Builder
|
||||
writeOrderedObj(&sb, []string{"error", "message"}, func(k string) interface{} {
|
||||
if k == "error" {
|
||||
return "RuntimeError"
|
||||
}
|
||||
return canonicalBridgeUnavailable
|
||||
})
|
||||
return sb.String()
|
||||
case "ee_test_envelope":
|
||||
out, err := TestEnvelope()
|
||||
if err != nil {
|
||||
t.Fatalf("test_envelope: %v", err)
|
||||
}
|
||||
return pyDumpsValue(out)
|
||||
}
|
||||
t.Fatalf("unknown frozen fn %q", c.Fn)
|
||||
return ""
|
||||
}
|
||||
|
||||
func TestFrozenEnvelopeTruthReplay(t *testing.T) {
|
||||
var doc struct {
|
||||
FrozenNow string `json:"frozen_now"`
|
||||
Cases []envelopeCase `json:"cases"`
|
||||
}
|
||||
dec := json.NewDecoder(strings.NewReader(frozenEnvelopeTruthJSON))
|
||||
dec.UseNumber()
|
||||
if err := dec.Decode(&doc); err != nil {
|
||||
t.Fatalf("decode truth_envelope.json: %v", err)
|
||||
}
|
||||
t.Setenv("EE_FROZEN_NOW", doc.FrozenNow)
|
||||
|
||||
for i, c := range doc.Cases {
|
||||
got := runEnvelopeCase(t, c)
|
||||
want := canonRawT(t, c.Expected)
|
||||
if !reflect.DeepEqual(canonDecT(t, got), want) {
|
||||
t.Errorf("case %d fn=%s:\n got %v\nwant %v", i, c.Fn,
|
||||
canonDecT(t, got), want)
|
||||
}
|
||||
}
|
||||
if len(doc.Cases) != 83 {
|
||||
t.Fatalf("expected 83 frozen cases, found %d", len(doc.Cases))
|
||||
}
|
||||
}
|
||||
|
||||
// TestWordMatchSemantics — the \b matcher units the frozen corpus pins
|
||||
// only through classify: unicode word chars, punctuation adjacency,
|
||||
// string edges, empty words.
|
||||
func TestWordMatchSemantics(t *testing.T) {
|
||||
cases := []struct {
|
||||
text, word string
|
||||
want bool
|
||||
}{
|
||||
{"stop,", "stop", true},
|
||||
{"unstoppable", "stop", false},
|
||||
{"stopé now", "stop", false}, // é is a word char: no boundary
|
||||
{"café stop", "stop", true},
|
||||
{"stop doing that", "stop doing", true},
|
||||
{"stop", "stop", true},
|
||||
{"a-stop", "stop", true}, // '-' is not a word char
|
||||
{"a_stop", "stop", false},
|
||||
{"don't do it", "don't", true},
|
||||
{"", "stop", false},
|
||||
{"anything", "", false},
|
||||
{"STOP", "stop", false}, // case-sensitive core; callers lowercase
|
||||
}
|
||||
for _, c := range cases {
|
||||
if got := pyWordMatch(c.text, c.word); got != c.want {
|
||||
t.Errorf("pyWordMatch(%q, %q) = %v, want %v", c.text, c.word, got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestPyQuoteEnsureAscii — json.dumps escaping: DEL, non-ASCII, astral
|
||||
// surrogate pairs, control chars.
|
||||
func TestPyQuoteEnsureAscii(t *testing.T) {
|
||||
cases := map[string]string{
|
||||
"\x7f": "\"\\u007f\"",
|
||||
"é": "\"\\u00e9\"",
|
||||
"\U0001F600": "\"\\ud83d\\ude00\"",
|
||||
"aéb": "\"a\\u00e9b\"",
|
||||
"tab\t": "\"tab\\t\"",
|
||||
}
|
||||
for in, want := range cases {
|
||||
if got := pyQuoteEvent(in); got != want {
|
||||
t.Errorf("pyQuoteEvent(%q) = %s, want %s", in, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestCreateEventTypeFallback — CHANNELS.get(channel, {}).get("prior")
|
||||
// or channel or source or "unknown", pinned one branch per frozen row.
|
||||
func TestCreateEventTypeFallback(t *testing.T) {
|
||||
t.Setenv("EE_FROZEN_NOW", "2026-10-14T11:22:33.123456+00:00")
|
||||
cases := []struct{ source, channel, want string }{
|
||||
{"user", "conversation", "conversation"}, // prior
|
||||
{"tool", "tool", "tool_result"}, // prior remap
|
||||
{"user", "", "user"}, // channel empty → source
|
||||
{"", "", "unknown"}, // everything empty
|
||||
{"user", "unknown-channel", "unknown-channel"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
resetEEUUID()
|
||||
env := CreateEnvelope(c.source, c.channel, nil)
|
||||
if env.Canonical.EventType != c.want {
|
||||
t.Errorf("create(%q,%q) event_type = %q, want %q",
|
||||
c.source, c.channel, env.Canonical.EventType, c.want)
|
||||
}
|
||||
if env.EventID != env.Canonical.EventID {
|
||||
t.Errorf("legacy id %q != canonical id %q", env.EventID, env.Canonical.EventID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestUnbridgedLoudFailure — the RuntimeError branch's exact message.
|
||||
func TestUnbridgedLoudFailure(t *testing.T) {
|
||||
env := LegacyEventEnvelope{}
|
||||
_, err := env.ToCanonicalDict()
|
||||
if err == nil || err.Error() != canonicalBridgeUnavailable {
|
||||
t.Fatalf("want loud bridge error, got %v", err)
|
||||
}
|
||||
if !strings.Contains(err.Error(), "canonical bridge unavailable") {
|
||||
t.Fatal("message contract broken")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user