build(agent): jabba#56a767 iteration
This commit is contained in:
parent
2fa9850cea
commit
c6336b1edd
|
|
@ -20,6 +20,10 @@ New modules added in this iteration:
|
|||
probability-of-violation (PoV) and an entropy summary. Includes serialize()
|
||||
for compact fingerprints. Unit tests in tests/test_belief.py exercise
|
||||
deterministic behavior and compact serialization.
|
||||
- src/guardrail_space/capsule.py: Poetic Situation Capsule generator that
|
||||
emits a tiny 128-512B human+machine readable capsule (round,plan_hash,PoV,
|
||||
energy_gap,verdict,human_summary). Tests in tests/test_capsule.py cover
|
||||
deterministic generation and size constraints.
|
||||
|
||||
Testing
|
||||
- Run `./test.sh` to execute unit tests and build a sdist/wheel with `python3 -m build`.
|
||||
|
|
|
|||
|
|
@ -9,5 +9,9 @@ What is included in this change
|
|||
- Unit tests under `tests/` exercising contract evaluation and engine behavior
|
||||
- Packaging metadata (`pyproject.toml`, `setup.cfg`) so `python3 -m build` works
|
||||
- `test.sh` which runs the test-suite and builds a sdist/wheel
|
||||
|
||||
This is a focused increment toward the full GuardRail.Space vision (WASM runner, ROS shims, fuzz harness, etc.). Next steps can extend the DSL, add serialization (YAML/DSL), shadow runner integration, and the situation-summary artifacts.
|
||||
|
||||
Added in this iteration:
|
||||
- `src/guardrail_space/capsule.py`: Poetic Situation Capsule generator for tiny
|
||||
(128-512B) human+machine readable summaries useful in low-bandwidth operator
|
||||
triage. See tests/test_capsule.py for deterministic behavior and size checks.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,81 @@
|
|||
import json
|
||||
import hashlib
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def _choose_template(plan_hash: str) -> str:
|
||||
# Deterministically pick one of a few brief templates based on hash
|
||||
templates = [
|
||||
"Round {round}: plan {plan} — PoV {pov:.2f}, gap {gap:.2f} — verdict: {verdict}.",
|
||||
"Capsule: r{round} {plan} | PoV={pov:.2f} gap={gap:.2f} -> {verdict}",
|
||||
"[{verdict}] plan={plan} r={round} pov={pov:.2f} gap={gap:.2f}",
|
||||
]
|
||||
h = int(hashlib.sha256(plan_hash.encode("utf-8")).hexdigest(), 16)
|
||||
return templates[h % len(templates)]
|
||||
|
||||
|
||||
def make_capsule(
|
||||
plan_hash: str,
|
||||
pov: float,
|
||||
energy_gap: float,
|
||||
verdict_code: str,
|
||||
round_number: int = 0,
|
||||
human_summary: Optional[str] = None,
|
||||
min_size: int = 128,
|
||||
max_size: int = 512,
|
||||
) -> bytes:
|
||||
"""Create a compact Situation Capsule (machine + human readable).
|
||||
|
||||
Returns a deterministic byte blob between min_size and max_size bytes.
|
||||
|
||||
Fields included (compact):
|
||||
- round (int), plan_hash (short), pov (float), energy_gap (float), verdict (str)
|
||||
- human (1-2 sentence summary). If human_summary is not provided a small
|
||||
template is chosen deterministically from the plan_hash.
|
||||
|
||||
The output is a UTF-8 JSON object. To ensure a compact fixed-size blob we
|
||||
trim or pad the human summary. Padding uses spaces so the capsule remains
|
||||
human-readable when printed.
|
||||
"""
|
||||
short_hash = hashlib.sha256(plan_hash.encode("utf-8")).hexdigest()[:12]
|
||||
|
||||
if human_summary is None:
|
||||
tpl = _choose_template(plan_hash)
|
||||
human_summary = tpl.format(round=round_number, plan=short_hash, pov=pov, gap=energy_gap, verdict=verdict_code)
|
||||
|
||||
# Ensure human summary is a single short sentence (truncate if necessary)
|
||||
human_summary = " ".join(human_summary.strip().split())
|
||||
|
||||
payload = {
|
||||
"r": int(round_number),
|
||||
"p": short_hash,
|
||||
"v": round(float(pov), 4),
|
||||
"g": round(float(energy_gap), 4),
|
||||
"c": verdict_code,
|
||||
"h": human_summary,
|
||||
}
|
||||
|
||||
blob = json.dumps(payload, separators=(",", ":") , ensure_ascii=False).encode("utf-8")
|
||||
|
||||
# If too long, truncate human summary and re-encode until fits
|
||||
if len(blob) > max_size:
|
||||
# remove human summary progressively
|
||||
h = payload["h"]
|
||||
while len(blob) > max_size and len(h) > 0:
|
||||
h = h[:-1]
|
||||
payload["h"] = h
|
||||
blob = json.dumps(payload, separators=(",", ":") , ensure_ascii=False).encode("utf-8")
|
||||
|
||||
# If too short, pad the human summary to reach min_size (but never exceed max_size)
|
||||
if len(blob) < min_size:
|
||||
pad_needed = min_size - len(blob)
|
||||
# simple space padding appended to human summary (keeps it readable)
|
||||
h = payload["h"] + (" " * pad_needed)
|
||||
payload["h"] = h
|
||||
blob = json.dumps(payload, separators=(",", ":") , ensure_ascii=False).encode("utf-8")
|
||||
|
||||
# Final safety: if still out of bounds, clamp to max_size by truncating bytes
|
||||
if len(blob) > max_size:
|
||||
blob = blob[:max_size]
|
||||
|
||||
return blob
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
from guardrail_space.capsule import make_capsule
|
||||
import json
|
||||
|
||||
|
||||
def test_capsule_size_and_fields_default():
|
||||
blob = make_capsule(plan_hash="plan-123", pov=0.75, energy_gap=1.5, verdict_code="warn", round_number=3)
|
||||
assert isinstance(blob, (bytes, bytearray))
|
||||
assert 128 <= len(blob) <= 512
|
||||
|
||||
j = json.loads(blob.decode("utf-8", errors="ignore"))
|
||||
# machine fields
|
||||
assert j.get("r") == 3
|
||||
assert "p" in j and isinstance(j["p"], str)
|
||||
assert abs(j.get("v") - 0.75) < 1e-6
|
||||
assert abs(j.get("g") - 1.5) < 1e-6
|
||||
assert j.get("c") == "warn"
|
||||
# human summary exists
|
||||
assert isinstance(j.get("h"), str) and len(j.get("h")) > 0
|
||||
|
||||
|
||||
def test_capsule_user_summary_truncation_and_determinism():
|
||||
# long user-provided summary should be truncated/padded deterministically
|
||||
long_summary = "This is a very long human summary. " * 20
|
||||
b1 = make_capsule(plan_hash="plan-abc", pov=0.1, energy_gap=0.0, verdict_code="ok", human_summary=long_summary)
|
||||
b2 = make_capsule(plan_hash="plan-abc", pov=0.1, energy_gap=0.0, verdict_code="ok", human_summary=long_summary)
|
||||
assert b1 == b2
|
||||
assert 128 <= len(b1) <= 512
|
||||
Loading…
Reference in New Issue