Spaces:
Running
Running
nemotron(signed-corpus): add missing ingest/corpus/verify modules (race fix)
Browse filesDirect HF commit of the three modules that the prior commit raced with the GitHub->HF backend mirror. Dockerfile COPY already references them. ADDITIVE; honest SAMPLE corpus; CC BY 4.0 NVIDIA attribution.
- szl_nemo_verify.py +116 -0
- szl_nemotron_corpus.py +406 -0
- szl_nemotron_ingest.py +365 -0
szl_nemo_verify.py
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 3 |
+
# © 2026 Lutar, Stephen P. — SZL Holdings · ORCID 0009-0001-0110-4173 · Doctrine v11/v12
|
| 4 |
+
# Authored by the NEMOTRON SIGNED-TRAJECTORY build team. Co-Authored-By: Perplexity Computer Agent.
|
| 5 |
+
"""
|
| 6 |
+
szl_nemo_verify — standalone verifier for the SZL-Nemo signed-trajectory corpus.
|
| 7 |
+
|
| 8 |
+
Anyone can run this against a downloaded corpus JSONL to independently check:
|
| 9 |
+
1. CONTENT INTEGRITY — recompute each step's sha256 step_hash and compare.
|
| 10 |
+
2. SIGNATURE — if a step carries a DSSE signature, verify it against the
|
| 11 |
+
published SZLHOLDINGS cosign public key (cosign.pub).
|
| 12 |
+
|
| 13 |
+
USAGE:
|
| 14 |
+
python szl_nemo_verify.py path/to/corpus.jsonl
|
| 15 |
+
cat corpus.jsonl | python szl_nemo_verify.py -
|
| 16 |
+
|
| 17 |
+
EXIT CODE: 0 if every present signature verifies AND every hash matches; else 1.
|
| 18 |
+
|
| 19 |
+
HONEST: when the corpus was emitted in an environment without the private signing
|
| 20 |
+
key, the receipts are UNSIGNED (signatures: []) — this verifier reports that
|
| 21 |
+
transparently and does NOT treat "unsigned" as a pass of the signature check. The
|
| 22 |
+
hash check still applies and proves content integrity / tamper-evidence.
|
| 23 |
+
|
| 24 |
+
Prefers the shipped szl_trajectory_sign + szl_dsse modules when importable (full
|
| 25 |
+
DSSE verification). Falls back to a self-contained hash-only check if they are not
|
| 26 |
+
on the path, so the script still runs standalone. NO network required.
|
| 27 |
+
"""
|
| 28 |
+
from __future__ import annotations
|
| 29 |
+
|
| 30 |
+
import hashlib
|
| 31 |
+
import json
|
| 32 |
+
import sys
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def _canon(obj) -> bytes:
|
| 36 |
+
return json.dumps(obj, sort_keys=True, separators=(",", ":"),
|
| 37 |
+
ensure_ascii=False).encode("utf-8")
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def _step_hash(action, observation, restraint_verdict: str) -> str:
|
| 41 |
+
body = _canon({"action": action, "observation": observation,
|
| 42 |
+
"restraint_verdict": restraint_verdict})
|
| 43 |
+
return "sha256:" + hashlib.sha256(body).hexdigest()
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def _verify_full(text: str):
|
| 47 |
+
"""Full verification using the shipped modules (DSSE + hash)."""
|
| 48 |
+
import szl_trajectory_sign as sts # type: ignore
|
| 49 |
+
return sts.verify_jsonl(text)
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def _verify_hash_only(text: str):
|
| 53 |
+
"""Self-contained hash-only fallback (no DSSE module on path)."""
|
| 54 |
+
results = []
|
| 55 |
+
for ln in text.splitlines():
|
| 56 |
+
ln = ln.strip()
|
| 57 |
+
if not ln:
|
| 58 |
+
continue
|
| 59 |
+
try:
|
| 60 |
+
d = json.loads(ln)
|
| 61 |
+
except Exception as exc:
|
| 62 |
+
results.append({"parse_error": str(exc)})
|
| 63 |
+
continue
|
| 64 |
+
recomputed = _step_hash(d.get("action"), d.get("observation", ""),
|
| 65 |
+
d.get("restraint_verdict", "ALLOW"))
|
| 66 |
+
env = d.get("signature") or {}
|
| 67 |
+
sigs = env.get("signatures") or []
|
| 68 |
+
results.append({
|
| 69 |
+
"trajectory_id": d.get("trajectory_id"),
|
| 70 |
+
"step": d.get("step"),
|
| 71 |
+
"hash_ok": recomputed == d.get("step_hash"),
|
| 72 |
+
"signed": bool(sigs),
|
| 73 |
+
"sig_ok": False, # cannot DSSE-verify without the module
|
| 74 |
+
})
|
| 75 |
+
total = len(results)
|
| 76 |
+
hash_ok = sum(1 for r in results if r.get("hash_ok"))
|
| 77 |
+
signed = sum(1 for r in results if r.get("signed"))
|
| 78 |
+
return {
|
| 79 |
+
"total_steps": total, "hash_ok": hash_ok, "signed": signed, "sig_ok": 0,
|
| 80 |
+
"all_hash_ok": hash_ok == total and total > 0,
|
| 81 |
+
"all_sig_ok": False, "results": results,
|
| 82 |
+
"note": "hash-only fallback (szl_dsse not importable; signatures not checked)",
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
def main(argv) -> int:
|
| 87 |
+
if len(argv) < 2:
|
| 88 |
+
print(__doc__)
|
| 89 |
+
return 2
|
| 90 |
+
src = argv[1]
|
| 91 |
+
text = sys.stdin.read() if src == "-" else open(src, encoding="utf-8").read()
|
| 92 |
+
try:
|
| 93 |
+
res = _verify_full(text)
|
| 94 |
+
mode = "full (DSSE + hash)"
|
| 95 |
+
except Exception:
|
| 96 |
+
res = _verify_hash_only(text)
|
| 97 |
+
mode = "hash-only fallback"
|
| 98 |
+
summary = {k: res[k] for k in ("total_steps", "hash_ok", "signed", "sig_ok",
|
| 99 |
+
"all_hash_ok", "all_sig_ok") if k in res}
|
| 100 |
+
summary["verify_mode"] = mode
|
| 101 |
+
print(json.dumps(summary, indent=2))
|
| 102 |
+
# PASS iff every hash matches AND (no signatures present OR all verify).
|
| 103 |
+
hashes_ok = res.get("all_hash_ok", False)
|
| 104 |
+
sig_present = res.get("signed", 0) > 0
|
| 105 |
+
sigs_ok = res.get("sig_ok", 0) == res.get("signed", 0)
|
| 106 |
+
ok = hashes_ok and (not sig_present or sigs_ok)
|
| 107 |
+
print("RESULT:", "PASS" if ok else "FAIL")
|
| 108 |
+
if sig_present and not sigs_ok:
|
| 109 |
+
print(" (signatures present but not all verified)")
|
| 110 |
+
if not sig_present:
|
| 111 |
+
print(" (no signatures present — UNSIGNED corpus; hash integrity only)")
|
| 112 |
+
return 0 if ok else 1
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
if __name__ == "__main__":
|
| 116 |
+
sys.exit(main(sys.argv))
|
szl_nemotron_corpus.py
ADDED
|
@@ -0,0 +1,406 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
# © 2026 Lutar, Stephen P. — SZL Holdings · ORCID 0009-0001-0110-4173 · Doctrine v11/v12
|
| 3 |
+
# Authored by the NEMOTRON SIGNED-TRAJECTORY build team. Co-Authored-By: Perplexity Computer Agent.
|
| 4 |
+
"""
|
| 5 |
+
szl_nemotron_corpus — served SIGNED-CORPUS tab + verify/stats endpoints for a11oy.
|
| 6 |
+
|
| 7 |
+
WHAT THIS SERVES (honest framing — read before extending):
|
| 8 |
+
A single, self-contained "Signed Corpus" surface that lets anyone:
|
| 9 |
+
1. SEE sample DSSE-signed agent-trajectory receipts (one JSONL line per
|
| 10 |
+
agent step: action / observation / restraint_verdict / step_hash /
|
| 11 |
+
signature), mapped from nvidia/Nemotron-Agentic-v1 (CC BY 4.0, NVIDIA).
|
| 12 |
+
2. RUN a signature/integrity check IN-BROWSER against any pasted JSONL,
|
| 13 |
+
calling POST /api/a11oy/v1/nemo/verify (server-side szl_dsse verify).
|
| 14 |
+
3. READ honest corpus stats (counts, verdict/pattern mix, signing status).
|
| 15 |
+
|
| 16 |
+
Routes (all registered BEFORE the SPA catch-all so explicit paths win):
|
| 17 |
+
GET /signed-corpus -> premium HTML tab
|
| 18 |
+
GET /api/a11oy/v1/nemo/stats -> corpus stats JSON
|
| 19 |
+
GET /api/a11oy/v1/nemo/sample -> the sample signed JSONL (text)
|
| 20 |
+
POST /api/a11oy/v1/nemo/verify -> {jsonl} -> aggregate verify JSON
|
| 21 |
+
|
| 22 |
+
A small idempotent nav-injection middleware appends a floating "Signed Corpus"
|
| 23 |
+
link to every served HTML surface (so the tab is reachable from the console
|
| 24 |
+
without editing the React SPA). Additive, never rewrites page content.
|
| 25 |
+
|
| 26 |
+
HONEST LABELS (Doctrine gates — never weaken):
|
| 27 |
+
- "QLoRA-ready corpus; training = ROADMAP (needs 2x80GB GPU)."
|
| 28 |
+
- DATASET property, NOT a model claim. Not an Ultra reproduction. Not from
|
| 29 |
+
scratch. Trust is never 100%. Data labeled LIVE / SAMPLE / MODELED.
|
| 30 |
+
- CC BY 4.0 attribution to NVIDIA is shown on the page and in /stats.
|
| 31 |
+
- Real ECDSA-P256 signatures only when SZL_COSIGN_PRIVATE_KEY_PEM is present;
|
| 32 |
+
else honest UNSIGNED — the verifier reports this transparently.
|
| 33 |
+
|
| 34 |
+
ADDITIVE · FastAPI + szl_trajectory_sign + szl_nemotron_ingest · 0 runtime CDN.
|
| 35 |
+
"""
|
| 36 |
+
from __future__ import annotations
|
| 37 |
+
|
| 38 |
+
import html as _html
|
| 39 |
+
import json
|
| 40 |
+
from typing import Any, Dict, Optional
|
| 41 |
+
|
| 42 |
+
from fastapi import FastAPI, Request
|
| 43 |
+
from fastapi.responses import HTMLResponse, JSONResponse, PlainTextResponse
|
| 44 |
+
|
| 45 |
+
import szl_trajectory_sign as sts
|
| 46 |
+
import szl_nemotron_ingest as ingest
|
| 47 |
+
|
| 48 |
+
LEAN_SHA = "c7c0ba17"
|
| 49 |
+
DOCTRINE = {"version": "v11/v12", "counts": "749/14/163", "lambda": "Conjecture 1"}
|
| 50 |
+
|
| 51 |
+
# Build the SAMPLE corpus ONCE at import (CPU-only, deterministic content).
|
| 52 |
+
try:
|
| 53 |
+
_CORPUS = ingest.build_sample_corpus(label="SAMPLE")
|
| 54 |
+
except Exception as _exc: # never crash registration
|
| 55 |
+
_CORPUS = {"trajectories": [], "jsonl": "", "stats": {
|
| 56 |
+
"label": "SAMPLE", "trajectory_count": 0, "total_steps": 0,
|
| 57 |
+
"verdict_counts": {}, "pattern_counts": {},
|
| 58 |
+
"signing_available": False, "attribution": ingest.ATTRIBUTION,
|
| 59 |
+
"build_error": f"{type(_exc).__name__}: {_exc}"}}
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def _stats() -> Dict[str, Any]:
|
| 63 |
+
st = dict(_CORPUS["stats"])
|
| 64 |
+
# Verify the shipped sample so /stats reports honest live integrity numbers.
|
| 65 |
+
v = sts.verify_jsonl(_CORPUS["jsonl"])
|
| 66 |
+
st["verify"] = {k: v[k] for k in
|
| 67 |
+
("total_steps", "hash_ok", "signed", "sig_ok",
|
| 68 |
+
"all_hash_ok", "all_sig_ok")}
|
| 69 |
+
st["doctrine"] = DOCTRINE
|
| 70 |
+
st["step_schema"] = sts.STEP_SCHEMA
|
| 71 |
+
st["payload_type"] = sts.STEP_PAYLOAD_TYPE
|
| 72 |
+
st["source_total_samples"] = ingest.SOURCE_TOTAL
|
| 73 |
+
st["honesty"] = (
|
| 74 |
+
"QLoRA-ready signed-trajectory corpus (DATASET property, not a model claim). "
|
| 75 |
+
"Actual QLoRA/GRPO training = ROADMAP (needs 2x80GB GPU). Not an Ultra "
|
| 76 |
+
"reproduction; not trained from scratch. SAMPLE shown is a representative "
|
| 77 |
+
"schema-faithful mapping of nvidia/Nemotron-Agentic-v1 (CC BY 4.0, NVIDIA); "
|
| 78 |
+
"the full 335,122 samples are re-derivable locally from the open source."
|
| 79 |
+
)
|
| 80 |
+
return st
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
# --------------------------------------------------------------------------- #
|
| 84 |
+
# HTML tab
|
| 85 |
+
# --------------------------------------------------------------------------- #
|
| 86 |
+
def _sample_cards() -> str:
|
| 87 |
+
cards = []
|
| 88 |
+
for traj in _CORPUS["trajectories"]:
|
| 89 |
+
prov = traj["provenance"]
|
| 90 |
+
steps = traj["steps"]
|
| 91 |
+
rows = []
|
| 92 |
+
for s in steps:
|
| 93 |
+
act = s.get("action")
|
| 94 |
+
act_str = json.dumps(act) if not isinstance(act, str) else act
|
| 95 |
+
if len(act_str) > 140:
|
| 96 |
+
act_str = act_str[:140] + "…"
|
| 97 |
+
obs = s.get("observation") or ""
|
| 98 |
+
if len(obs) > 100:
|
| 99 |
+
obs = obs[:100] + "…"
|
| 100 |
+
verdict = s.get("restraint_verdict", "ALLOW")
|
| 101 |
+
vclass = {"ALLOW": "ok", "HOLD": "warn", "MONITOR": "exp",
|
| 102 |
+
"DECLINE": "bad"}.get(verdict, "exp")
|
| 103 |
+
corr = " ⟲ correction" if s.get("is_correction") else ""
|
| 104 |
+
rows.append(f"""
|
| 105 |
+
<tr>
|
| 106 |
+
<td class="num">{s.get('step')}</td>
|
| 107 |
+
<td><span class="pat">{_html.escape(s.get('pattern',''))}</span>{corr}</td>
|
| 108 |
+
<td class="role">{_html.escape(s.get('role',''))}</td>
|
| 109 |
+
<td class="act"><code>{_html.escape(act_str)}</code></td>
|
| 110 |
+
<td class="obs">{_html.escape(obs)}</td>
|
| 111 |
+
<td><span class="badge {vclass}">{_html.escape(verdict)}</span></td>
|
| 112 |
+
<td class="hash"><code>{_html.escape((s.get('step_hash') or '')[:18])}…</code></td>
|
| 113 |
+
</tr>""")
|
| 114 |
+
signed_badge = ('<span class="badge ok">SIGNED</span>'
|
| 115 |
+
if prov.get("all_signed")
|
| 116 |
+
else '<span class="badge warn">UNSIGNED (no key in env — honest)</span>')
|
| 117 |
+
cards.append(f"""
|
| 118 |
+
<article class="tcard">
|
| 119 |
+
<header>
|
| 120 |
+
<h3>trajectory {_html.escape(prov.get('trajectory_id','')[:8])}…</h3>
|
| 121 |
+
<span class="label">{_html.escape(prov.get('label','SAMPLE'))}</span>
|
| 122 |
+
{signed_badge}
|
| 123 |
+
</header>
|
| 124 |
+
<p class="task">📋 {_html.escape(str(prov.get('task',''))[:160])}</p>
|
| 125 |
+
<p class="meta">source: <b>{_html.escape(prov.get('source',''))}</b> ·
|
| 126 |
+
steps: {prov.get('total_steps')} · corrections: {prov.get('corrections')} ·
|
| 127 |
+
reasoning: {_html.escape(str(prov.get('reasoning_mode','')))} ·
|
| 128 |
+
used_in: {_html.escape(json.dumps(prov.get('used_in',[])))}</p>
|
| 129 |
+
<table class="steps">
|
| 130 |
+
<thead><tr><th>#</th><th>pattern</th><th>role</th><th>action</th>
|
| 131 |
+
<th>observation</th><th>verdict</th><th>step_hash</th></tr></thead>
|
| 132 |
+
<tbody>{''.join(rows)}</tbody>
|
| 133 |
+
</table>
|
| 134 |
+
</article>""")
|
| 135 |
+
return "\n".join(cards)
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
def _page_html(ns: str = "a11oy") -> str:
|
| 139 |
+
st = _stats()
|
| 140 |
+
attr = st["attribution"]
|
| 141 |
+
vc = st.get("verdict_counts", {})
|
| 142 |
+
pc = st.get("pattern_counts", {})
|
| 143 |
+
verify = st.get("verify", {})
|
| 144 |
+
sample_jsonl = _CORPUS["jsonl"]
|
| 145 |
+
chips = "".join(
|
| 146 |
+
f'<span class="chip">{_html.escape(k)}: <b>{v}</b></span>'
|
| 147 |
+
for k, v in {**vc, **{f"pat:{p}": n for p, n in pc.items()}}.items())
|
| 148 |
+
return f"""<!doctype html>
|
| 149 |
+
<html lang="en"><head>
|
| 150 |
+
<meta charset="UTF-8"/><meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
| 151 |
+
<title>A11oy — Signed Corpus (SZL-Nemo)</title>
|
| 152 |
+
<style>
|
| 153 |
+
:root {{ --bg:#0a0f1e; --panel:#111a2e; --ink:#e8eef7; --muted:#8aa0bd;
|
| 154 |
+
--indigo:#4d8fcc; --terra:#c8643c; --gold:#d8a23c; --ok:#3fae7a;
|
| 155 |
+
--warn:#c8893c; --bad:#cc5a5a; --exp:#9fb0e6; }}
|
| 156 |
+
* {{ box-sizing:border-box; }}
|
| 157 |
+
body {{ margin:0; font-family:ui-sans-serif,system-ui,Segoe UI,Roboto,sans-serif;
|
| 158 |
+
background:radial-gradient(1200px 600px at 70% -10%, #16223c, var(--bg)); color:var(--ink); }}
|
| 159 |
+
.wrap {{ max-width:1180px; margin:0 auto; padding:2.5rem 1.25rem 4rem; }}
|
| 160 |
+
.plaque {{ font-family:ui-monospace,monospace; font-size:.72rem; letter-spacing:.12em;
|
| 161 |
+
color:var(--muted); text-transform:uppercase; }}
|
| 162 |
+
.plaque b {{ color:var(--gold); }}
|
| 163 |
+
h1 {{ font-size:clamp(1.8rem,4vw,2.6rem); margin:.4rem 0 .2rem; }}
|
| 164 |
+
h1 .accent {{ color:var(--terra); }}
|
| 165 |
+
.sub {{ color:var(--muted); max-width:74ch; line-height:1.55; }}
|
| 166 |
+
.honest {{ background:rgba(200,137,60,.10); border:1px solid rgba(200,137,60,.35);
|
| 167 |
+
border-radius:10px; padding:.8rem 1rem; margin:1.1rem 0; color:#e7c98f;
|
| 168 |
+
font-size:.85rem; line-height:1.5; }}
|
| 169 |
+
.attr {{ background:rgba(77,143,204,.08); border:1px solid rgba(77,143,204,.3);
|
| 170 |
+
border-radius:10px; padding:.7rem 1rem; margin:.8rem 0; font-size:.8rem; color:#bcd; }}
|
| 171 |
+
.attr a {{ color:var(--indigo); }}
|
| 172 |
+
.chips {{ display:flex; flex-wrap:wrap; gap:.45rem; margin:1rem 0; }}
|
| 173 |
+
.chip {{ background:var(--panel); border:1px solid #21304d; border-radius:999px;
|
| 174 |
+
padding:.3rem .7rem; font-size:.74rem; color:var(--muted); }}
|
| 175 |
+
.chip b {{ color:var(--ink); }}
|
| 176 |
+
.bignum {{ display:flex; gap:1.4rem; flex-wrap:wrap; margin:1.2rem 0; }}
|
| 177 |
+
.bignum .n {{ font-size:2rem; font-weight:800; color:var(--gold); line-height:1; }}
|
| 178 |
+
.bignum .l {{ font-size:.72rem; color:var(--muted); text-transform:uppercase; letter-spacing:.08em; }}
|
| 179 |
+
.tcard {{ background:var(--panel); border:1px solid #21304d; border-radius:12px;
|
| 180 |
+
padding:1.1rem 1.15rem; margin:1rem 0; box-shadow:0 18px 40px -28px #000; }}
|
| 181 |
+
.tcard header {{ display:flex; align-items:center; gap:.6rem; flex-wrap:wrap; }}
|
| 182 |
+
.tcard h3 {{ margin:0; font-family:ui-monospace,monospace; color:var(--indigo); font-size:1rem; }}
|
| 183 |
+
.label {{ font-size:.62rem; padding:.18rem .5rem; border-radius:999px; background:rgba(216,162,60,.16);
|
| 184 |
+
color:var(--gold); border:1px solid rgba(216,162,60,.4); letter-spacing:.08em; }}
|
| 185 |
+
.task {{ color:var(--ink); font-size:.86rem; margin:.6rem 0 .2rem; }}
|
| 186 |
+
.meta {{ color:var(--muted); font-size:.76rem; margin:.1rem 0 .7rem; }}
|
| 187 |
+
table.steps {{ width:100%; border-collapse:collapse; font-size:.74rem; }}
|
| 188 |
+
table.steps th {{ text-align:left; color:var(--muted); font-weight:600; border-bottom:1px solid #21304d;
|
| 189 |
+
padding:.3rem .4rem; text-transform:uppercase; font-size:.64rem; letter-spacing:.06em; }}
|
| 190 |
+
table.steps td {{ padding:.32rem .4rem; border-bottom:1px solid #16213a; vertical-align:top; }}
|
| 191 |
+
td.num {{ color:var(--muted); }} td.act code {{ color:#bcd; word-break:break-word; }}
|
| 192 |
+
td.obs {{ color:var(--muted); }} td.hash code {{ color:var(--gold); font-size:.68rem; }}
|
| 193 |
+
.pat {{ color:var(--indigo); font-weight:600; }} .role {{ color:var(--muted); }}
|
| 194 |
+
.badge {{ font-size:.6rem; padding:.16rem .45rem; border-radius:999px; letter-spacing:.05em; }}
|
| 195 |
+
.badge.ok {{ background:rgba(63,174,122,.16); color:var(--ok); border:1px solid rgba(63,174,122,.4); }}
|
| 196 |
+
.badge.warn {{ background:rgba(200,137,60,.16); color:var(--warn); border:1px solid rgba(200,137,60,.4); }}
|
| 197 |
+
.badge.exp {{ background:rgba(120,140,210,.16); color:var(--exp); border:1px solid rgba(120,140,210,.45); }}
|
| 198 |
+
.badge.bad {{ background:rgba(204,90,90,.16); color:var(--bad); border:1px solid rgba(204,90,90,.45); }}
|
| 199 |
+
h2 {{ margin-top:2.2rem; font-size:1.25rem; }}
|
| 200 |
+
textarea {{ width:100%; min-height:140px; background:#070c17; border:1px solid #1a2742; border-radius:8px;
|
| 201 |
+
color:#bcd; font-family:ui-monospace,monospace; font-size:.72rem; padding:.7rem; }}
|
| 202 |
+
button.verify {{ background:var(--terra); color:#0a0f1e; border:0; border-radius:8px; padding:.5rem .9rem;
|
| 203 |
+
font-weight:700; cursor:pointer; font-size:.85rem; margin-top:.6rem; }}
|
| 204 |
+
button.verify:hover {{ filter:brightness(1.08); }}
|
| 205 |
+
pre.out {{ background:#070c17; border:1px solid #1a2742; border-radius:8px; padding:.7rem; margin-top:.7rem;
|
| 206 |
+
font-size:.74rem; color:#bcd; overflow:auto; max-height:300px; white-space:pre-wrap; }}
|
| 207 |
+
a.back {{ color:var(--muted); text-decoration:none; font-size:.85rem; }}
|
| 208 |
+
code.ep {{ color:var(--gold); }}
|
| 209 |
+
</style></head>
|
| 210 |
+
<body>
|
| 211 |
+
<main class="wrap" id="main">
|
| 212 |
+
<div class="plaque">SZL HOLDINGS / A11OY / SZL-NEMO / DOCTRINE <b>{DOCTRINE['version']} · LOCKED</b>
|
| 213 |
+
/ {DOCTRINE['counts']} / Λ = CONJECTURE 1 / SLSA L1 (L2/L3 ROADMAP)</div>
|
| 214 |
+
<h1>The <span class="accent">signed corpus</span> is verifiable.</h1>
|
| 215 |
+
<p class="sub">A cryptographically-signed, provenance-attested agent-trajectory corpus.
|
| 216 |
+
Each JSONL line is one agent step — <code>action</code>, <code>observation</code>,
|
| 217 |
+
<code>restraint_verdict</code>, <code>step_hash</code>, <code>signature</code> — emitted by
|
| 218 |
+
instrumenting the SZL ReAct + Reflexion + Restraint + Auto-Review loop. Anyone can verify
|
| 219 |
+
every signature below. <a class="back" href="/">← back to console</a></p>
|
| 220 |
+
|
| 221 |
+
<div class="honest">⚖️ <b>Honest scope:</b> {_html.escape(st['honesty'])}</div>
|
| 222 |
+
|
| 223 |
+
<div class="attr">📚 <b>Attribution (CC BY 4.0):</b> sample trajectories are a schema-faithful
|
| 224 |
+
mapping of <a href="{_html.escape(attr['url'])}" target="_blank" rel="noopener">{_html.escape(attr['source_dataset'])}</a>
|
| 225 |
+
© {_html.escape(attr['source_owner'])}, licensed {_html.escape(attr['license_full'])}.
|
| 226 |
+
Source totals: {attr['subsets']['interactive_agent']:,} interactive_agent +
|
| 227 |
+
{attr['subsets']['tool_calling']:,} tool_calling = {attr['total_source_samples']:,} samples.
|
| 228 |
+
Attribution is required and does not imply endorsement.</div>
|
| 229 |
+
|
| 230 |
+
<div class="bignum">
|
| 231 |
+
<div><div class="n">{st['trajectory_count']}</div><div class="l">sample trajectories</div></div>
|
| 232 |
+
<div><div class="n">{st['total_steps']}</div><div class="l">signed steps</div></div>
|
| 233 |
+
<div><div class="n">{verify.get('hash_ok',0)}/{verify.get('total_steps',0)}</div><div class="l">hash-verified</div></div>
|
| 234 |
+
<div><div class="n">{'YES' if st['signing_available'] else 'NO'}</div><div class="l">private key in env</div></div>
|
| 235 |
+
<div><div class="n">{ingest.SOURCE_TOTAL:,}</div><div class="l">source samples (CC BY 4.0)</div></div>
|
| 236 |
+
</div>
|
| 237 |
+
<div class="chips">{chips}</div>
|
| 238 |
+
|
| 239 |
+
<h2>Sample signed trajectories</h2>
|
| 240 |
+
{_sample_cards()}
|
| 241 |
+
|
| 242 |
+
<h2>Verify it yourself</h2>
|
| 243 |
+
<p class="sub">Paste signed-trajectory JSONL (or use the loaded sample) and run the integrity +
|
| 244 |
+
signature check. It calls <code class="ep">POST /api/{ns}/v1/nemo/verify</code> server-side,
|
| 245 |
+
which recomputes each <code>step_hash</code> and verifies each DSSE envelope against the
|
| 246 |
+
published <code>cosign.pub</code>. Honest: with no private key in this environment the receipts
|
| 247 |
+
are UNSIGNED and the verifier says so — it never fabricates a pass.</p>
|
| 248 |
+
<textarea id="jsonl"></textarea>
|
| 249 |
+
<div>
|
| 250 |
+
<button class="verify" id="run">Verify signatures & hashes</button>
|
| 251 |
+
<button class="verify" id="load" style="background:var(--indigo);">Load sample JSONL</button>
|
| 252 |
+
</div>
|
| 253 |
+
<pre class="out" id="out" hidden></pre>
|
| 254 |
+
</main>
|
| 255 |
+
<script>
|
| 256 |
+
const NS = {json.dumps(ns)};
|
| 257 |
+
const SAMPLE = {json.dumps(sample_jsonl)};
|
| 258 |
+
document.getElementById('load').addEventListener('click', function() {{
|
| 259 |
+
document.getElementById('jsonl').value = SAMPLE;
|
| 260 |
+
}});
|
| 261 |
+
document.getElementById('run').addEventListener('click', async function() {{
|
| 262 |
+
const out = document.getElementById('out');
|
| 263 |
+
const jsonl = document.getElementById('jsonl').value || SAMPLE;
|
| 264 |
+
out.hidden = false; out.textContent = 'verifying …';
|
| 265 |
+
try {{
|
| 266 |
+
const r = await fetch('/api/' + NS + '/v1/nemo/verify', {{
|
| 267 |
+
method: 'POST', headers: {{ 'Content-Type': 'application/json' }},
|
| 268 |
+
body: JSON.stringify({{ jsonl: jsonl }})
|
| 269 |
+
}});
|
| 270 |
+
const t = await r.text();
|
| 271 |
+
try {{ out.textContent = '[' + r.status + '] ' + JSON.stringify(JSON.parse(t), null, 2); }}
|
| 272 |
+
catch (e) {{ out.textContent = '[' + r.status + '] ' + t.slice(0, 1200); }}
|
| 273 |
+
}} catch (e) {{ out.textContent = 'error: ' + e; }}
|
| 274 |
+
}});
|
| 275 |
+
</script>
|
| 276 |
+
</body></html>"""
|
| 277 |
+
|
| 278 |
+
|
| 279 |
+
# --------------------------------------------------------------------------- #
|
| 280 |
+
# Nav-injection middleware (idempotent; appends a floating link to every HTML
|
| 281 |
+
# surface so the tab is reachable from the console without editing the SPA).
|
| 282 |
+
# --------------------------------------------------------------------------- #
|
| 283 |
+
def _install_nav_injector(app: FastAPI) -> bool:
|
| 284 |
+
try:
|
| 285 |
+
from starlette.middleware.base import BaseHTTPMiddleware
|
| 286 |
+
from starlette.responses import Response as _Resp
|
| 287 |
+
except Exception:
|
| 288 |
+
return False
|
| 289 |
+
|
| 290 |
+
_MARKER = b"szl-nemo-corpus-nav"
|
| 291 |
+
_TAG = (
|
| 292 |
+
b'<a id="szl-nemo-corpus-nav" href="/signed-corpus" '
|
| 293 |
+
b'style="position:fixed;right:14px;bottom:14px;z-index:99998;'
|
| 294 |
+
b'background:#c8643c;color:#0a0f1e;font:600 12px ui-sans-serif,system-ui;'
|
| 295 |
+
b'padding:8px 12px;border-radius:999px;text-decoration:none;'
|
| 296 |
+
b'box-shadow:0 6px 18px -6px #000;" '
|
| 297 |
+
b'title="DSSE-signed agent-trajectory corpus (SZL-Nemo)">'
|
| 298 |
+
b'\xe2\x97\x88 Signed Corpus</a>'
|
| 299 |
+
)
|
| 300 |
+
|
| 301 |
+
class _NemoNavInjector(BaseHTTPMiddleware):
|
| 302 |
+
async def dispatch(self, request: Request, call_next): # noqa: ANN001
|
| 303 |
+
resp = await call_next(request)
|
| 304 |
+
try:
|
| 305 |
+
ct = (resp.headers.get("content-type") or "").lower()
|
| 306 |
+
if "text/html" not in ct:
|
| 307 |
+
return resp
|
| 308 |
+
p = request.url.path
|
| 309 |
+
if (p.startswith("/vendor/") or p.startswith("/api/")
|
| 310 |
+
or p.startswith("/assets/") or p == "/signed-corpus"):
|
| 311 |
+
return resp
|
| 312 |
+
body = b""
|
| 313 |
+
async for chunk in resp.body_iterator:
|
| 314 |
+
body += chunk if isinstance(chunk, (bytes, bytearray)) \
|
| 315 |
+
else str(chunk).encode()
|
| 316 |
+
if _MARKER in body: # idempotent
|
| 317 |
+
new_body = body
|
| 318 |
+
elif b"</body>" in body:
|
| 319 |
+
new_body = body.replace(b"</body>", _TAG + b"</body>", 1)
|
| 320 |
+
elif b"</html>" in body:
|
| 321 |
+
new_body = body.replace(b"</html>", _TAG + b"</html>", 1)
|
| 322 |
+
else:
|
| 323 |
+
new_body = body + _TAG
|
| 324 |
+
headers = dict(resp.headers)
|
| 325 |
+
headers.pop("content-length", None)
|
| 326 |
+
return _Resp(content=new_body, status_code=resp.status_code,
|
| 327 |
+
headers=headers, media_type="text/html")
|
| 328 |
+
except Exception:
|
| 329 |
+
return resp
|
| 330 |
+
|
| 331 |
+
app.add_middleware(_NemoNavInjector)
|
| 332 |
+
return True
|
| 333 |
+
|
| 334 |
+
|
| 335 |
+
# --------------------------------------------------------------------------- #
|
| 336 |
+
# Registration
|
| 337 |
+
# --------------------------------------------------------------------------- #
|
| 338 |
+
def register(app: FastAPI, ns: str = "a11oy") -> str:
|
| 339 |
+
"""Mount the Signed-Corpus tab + JSON/verify endpoints + nav injector.
|
| 340 |
+
|
| 341 |
+
ADDITIVE — explicit routes registered before the SPA catch-all; the nav
|
| 342 |
+
injector is idempotent and never rewrites page content. try/except in
|
| 343 |
+
serve.py guarantees a missing dep can never take down the Space.
|
| 344 |
+
"""
|
| 345 |
+
|
| 346 |
+
@app.get("/signed-corpus", include_in_schema=False)
|
| 347 |
+
async def signed_corpus_page() -> HTMLResponse: # noqa: ANN202
|
| 348 |
+
return HTMLResponse(_page_html(ns))
|
| 349 |
+
|
| 350 |
+
# Alias under the namespace, mirroring the other genius tabs.
|
| 351 |
+
@app.get(f"/{ns}/signed-corpus", include_in_schema=False)
|
| 352 |
+
async def signed_corpus_page_ns() -> HTMLResponse: # noqa: ANN202
|
| 353 |
+
return HTMLResponse(_page_html(ns))
|
| 354 |
+
|
| 355 |
+
@app.get(f"/api/{ns}/v1/nemo/stats", include_in_schema=False)
|
| 356 |
+
async def nemo_stats() -> JSONResponse: # noqa: ANN202
|
| 357 |
+
return JSONResponse(_stats())
|
| 358 |
+
|
| 359 |
+
@app.get(f"/api/{ns}/v1/nemo/sample", include_in_schema=False)
|
| 360 |
+
async def nemo_sample() -> PlainTextResponse: # noqa: ANN202
|
| 361 |
+
return PlainTextResponse(_CORPUS["jsonl"], media_type="application/x-ndjson")
|
| 362 |
+
|
| 363 |
+
@app.post(f"/api/{ns}/v1/nemo/verify", include_in_schema=False)
|
| 364 |
+
async def nemo_verify(request: Request) -> JSONResponse: # noqa: ANN202
|
| 365 |
+
try:
|
| 366 |
+
payload = await request.json()
|
| 367 |
+
except Exception:
|
| 368 |
+
payload = {}
|
| 369 |
+
jsonl = ""
|
| 370 |
+
if isinstance(payload, dict):
|
| 371 |
+
jsonl = payload.get("jsonl") or payload.get("text") or ""
|
| 372 |
+
if not isinstance(jsonl, str) or not jsonl.strip():
|
| 373 |
+
jsonl = _CORPUS["jsonl"] # default: verify the shipped sample
|
| 374 |
+
result = sts.verify_jsonl(jsonl)
|
| 375 |
+
result["honesty"] = (
|
| 376 |
+
"Integrity check recomputes each step_hash and verifies each DSSE "
|
| 377 |
+
"envelope against the published cosign.pub. 'signed' counts receipts "
|
| 378 |
+
"carrying a real signature; with no private key in this environment "
|
| 379 |
+
"receipts are honestly UNSIGNED (signed=0) — never fabricated."
|
| 380 |
+
)
|
| 381 |
+
result["doctrine"] = DOCTRINE
|
| 382 |
+
return JSONResponse(result)
|
| 383 |
+
|
| 384 |
+
nav_ok = _install_nav_injector(app)
|
| 385 |
+
return (f"nemotron signed-corpus mounted: GET /signed-corpus + "
|
| 386 |
+
f"/api/{ns}/v1/nemo/{{stats,sample,verify}} ; nav-injector={nav_ok}")
|
| 387 |
+
|
| 388 |
+
|
| 389 |
+
if __name__ == "__main__":
|
| 390 |
+
# Pure self-check: render page + verify endpoints without a live server.
|
| 391 |
+
st = _stats()
|
| 392 |
+
page = _page_html("a11oy")
|
| 393 |
+
assert "signed corpus" in page.lower()
|
| 394 |
+
assert "CC BY 4.0" in page or "cc-by-4.0" in page.lower()
|
| 395 |
+
assert "ROADMAP" in page
|
| 396 |
+
assert st["trajectory_count"] >= 1
|
| 397 |
+
print(json.dumps({
|
| 398 |
+
"page_bytes": len(page),
|
| 399 |
+
"trajectory_count": st["trajectory_count"],
|
| 400 |
+
"total_steps": st["total_steps"],
|
| 401 |
+
"verify": st["verify"],
|
| 402 |
+
"verdict_counts": st["verdict_counts"],
|
| 403 |
+
"pattern_counts": st["pattern_counts"],
|
| 404 |
+
"signing_available": st["signing_available"],
|
| 405 |
+
"attribution_license": st["attribution"]["license"],
|
| 406 |
+
}, indent=2))
|
szl_nemotron_ingest.py
ADDED
|
@@ -0,0 +1,365 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
# © 2026 Lutar, Stephen P. — SZL Holdings · ORCID 0009-0001-0110-4173 · Doctrine v11/v12
|
| 3 |
+
# Authored by the NEMOTRON SIGNED-TRAJECTORY build team. Co-Authored-By: Perplexity Computer Agent.
|
| 4 |
+
"""
|
| 5 |
+
szl_nemotron_ingest — map nvidia/Nemotron-Agentic-v1 into the SZL trajectory schema.
|
| 6 |
+
|
| 7 |
+
WHAT THIS IS (honest framing):
|
| 8 |
+
A pure-CPU mapper that takes rows from NVIDIA's open agentic dataset
|
| 9 |
+
(nvidia/Nemotron-Agentic-v1, 335,122 samples, CC BY 4.0) and re-expresses
|
| 10 |
+
each multi-turn conversation as an SZL trajectory: one signed-step receipt
|
| 11 |
+
per agent/tool turn (see szl_trajectory_sign). The output is QLoRA-ready and
|
| 12 |
+
every step is independently signature-verifiable.
|
| 13 |
+
|
| 14 |
+
ATTRIBUTION (CC BY 4.0 — REQUIRED, never strip):
|
| 15 |
+
Source dataset : nvidia/Nemotron-Agentic-v1
|
| 16 |
+
Owner : NVIDIA Corporation
|
| 17 |
+
License : Creative Commons Attribution 4.0 International (CC BY 4.0)
|
| 18 |
+
Subsets : interactive_agent (19,028) + tool_calling (316,094) = 335,122
|
| 19 |
+
URL : https://huggingface.co/datasets/nvidia/Nemotron-Agentic-v1
|
| 20 |
+
Each emitted trajectory carries source="nvidia", verified=False, signed-status
|
| 21 |
+
per the signer, label="SAMPLE", and the full attribution block above. We do NOT
|
| 22 |
+
redistribute NVIDIA's raw rows here; we ship a representative SAMPLE mapping and
|
| 23 |
+
the mapper so the full 335k can be re-derived locally from the open source.
|
| 24 |
+
|
| 25 |
+
WHAT THIS IS *NOT*:
|
| 26 |
+
- Not a model. Not a training run. Not an Ultra reproduction. The signed corpus
|
| 27 |
+
is a DATASET property. Actual QLoRA/GRPO training = ROADMAP (FORGE order,
|
| 28 |
+
needs >=2x80GB GPU).
|
| 29 |
+
- We do not claim NVIDIA endorses SZL. CC BY 4.0 attribution != endorsement.
|
| 30 |
+
|
| 31 |
+
SOURCE ROW SCHEMA (from the HF dataset card / preview):
|
| 32 |
+
{
|
| 33 |
+
"uuid": str,
|
| 34 |
+
"messages": [ {role, content, [tool_calls], [tool_call_id], ...}, ... ],
|
| 35 |
+
"license": "cc-by-4.0",
|
| 36 |
+
"used_in": ["nano_v3", ...],
|
| 37 |
+
"tools": [ {"type":"function","function":{name,description,parameters}}, ... ],
|
| 38 |
+
"reasoning": "on" | "off"
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
MAPPING (Nemotron message -> SZL trajectory step):
|
| 42 |
+
role "assistant" with tool_calls -> step {role:assistant, pattern:ReAct,
|
| 43 |
+
action: the tool_call(s), restraint_verdict via _verdict_for()}
|
| 44 |
+
role "tool" -> step {role:tool, observation: content}
|
| 45 |
+
role "assistant" plain text -> step {role:assistant, action: text,
|
| 46 |
+
pattern: Reflexion if it looks like a self-correction else AutoReview}
|
| 47 |
+
role "system"/"user" -> recorded as trajectory context (not a
|
| 48 |
+
signed agent action), surfaced in provenance.
|
| 49 |
+
|
| 50 |
+
ADDITIVE · stdlib + szl_trajectory_sign only · CPU-only · no network required.
|
| 51 |
+
"""
|
| 52 |
+
from __future__ import annotations
|
| 53 |
+
|
| 54 |
+
import json
|
| 55 |
+
import os
|
| 56 |
+
from typing import Any, Dict, Iterable, List, Optional
|
| 57 |
+
|
| 58 |
+
import szl_trajectory_sign as sts
|
| 59 |
+
|
| 60 |
+
SOURCE_DATASET = "nvidia/Nemotron-Agentic-v1"
|
| 61 |
+
SOURCE_OWNER = "NVIDIA Corporation"
|
| 62 |
+
SOURCE_LICENSE = "CC BY 4.0"
|
| 63 |
+
SOURCE_LICENSE_FULL = "Creative Commons Attribution 4.0 International (CC BY 4.0)"
|
| 64 |
+
SOURCE_URL = "https://huggingface.co/datasets/nvidia/Nemotron-Agentic-v1"
|
| 65 |
+
SOURCE_SUBSETS = {"interactive_agent": 19028, "tool_calling": 316094}
|
| 66 |
+
SOURCE_TOTAL = 335122
|
| 67 |
+
|
| 68 |
+
ATTRIBUTION = {
|
| 69 |
+
"source_dataset": SOURCE_DATASET,
|
| 70 |
+
"source_owner": SOURCE_OWNER,
|
| 71 |
+
"license": SOURCE_LICENSE,
|
| 72 |
+
"license_full": SOURCE_LICENSE_FULL,
|
| 73 |
+
"url": SOURCE_URL,
|
| 74 |
+
"subsets": SOURCE_SUBSETS,
|
| 75 |
+
"total_source_samples": SOURCE_TOTAL,
|
| 76 |
+
"note": (
|
| 77 |
+
"Re-expressed into the SZL trajectory schema under CC BY 4.0. Attribution to "
|
| 78 |
+
"NVIDIA is required and does not imply endorsement. Raw NVIDIA rows are not "
|
| 79 |
+
"redistributed here; this is a representative SAMPLE mapping plus the mapper."
|
| 80 |
+
),
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
# Reflexion cue words: an assistant turn that reads like a self-correction.
|
| 84 |
+
_REFLEXION_CUES = (
|
| 85 |
+
"let me reconsider", "i was wrong", "correction", "actually,", "on second thought",
|
| 86 |
+
"let me retry", "that was incorrect", "i made an error", "let me fix",
|
| 87 |
+
)
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
def _verdict_for(msg: Dict[str, Any], tools_present: bool) -> str:
|
| 91 |
+
"""Conservative Restraint verdict for a mapped Nemotron turn.
|
| 92 |
+
|
| 93 |
+
Honest defaults ("never engage on doubt"): a tool call with arguments is
|
| 94 |
+
ALLOW; an assistant turn that declines/escalates -> HOLD/DECLINE; ambiguous
|
| 95 |
+
tool environments -> MONITOR.
|
| 96 |
+
"""
|
| 97 |
+
content = (msg.get("content") or "")
|
| 98 |
+
low = content.lower() if isinstance(content, str) else ""
|
| 99 |
+
if any(w in low for w in ("cannot", "i'm sorry, but", "not able to", "decline",
|
| 100 |
+
"against policy", "i can't")):
|
| 101 |
+
return "DECLINE"
|
| 102 |
+
if any(w in low for w in ("need to verify", "please confirm", "let me check",
|
| 103 |
+
"i need more information", "could you clarify")):
|
| 104 |
+
return "HOLD"
|
| 105 |
+
if msg.get("tool_calls"):
|
| 106 |
+
return "ALLOW"
|
| 107 |
+
if tools_present and msg.get("role") == "assistant" and not low.strip():
|
| 108 |
+
return "MONITOR"
|
| 109 |
+
return "ALLOW"
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
def _pattern_for(msg: Dict[str, Any], prev_assistant_text: str) -> str:
|
| 113 |
+
role = msg.get("role")
|
| 114 |
+
content = msg.get("content") or ""
|
| 115 |
+
low = content.lower() if isinstance(content, str) else ""
|
| 116 |
+
# A self-correcting turn is Reflexion even if it also calls a tool (the
|
| 117 |
+
# backtrack is the salient property for the trajectory corpus).
|
| 118 |
+
if role == "assistant" and any(c in low for c in _REFLEXION_CUES):
|
| 119 |
+
return "Reflexion"
|
| 120 |
+
if msg.get("tool_calls"):
|
| 121 |
+
return "ReAct"
|
| 122 |
+
if role == "assistant":
|
| 123 |
+
return "AutoReview"
|
| 124 |
+
return "ReAct"
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
def _action_of(msg: Dict[str, Any]) -> Any:
|
| 128 |
+
"""Extract the agent action: tool_calls if present, else the text content."""
|
| 129 |
+
tcs = msg.get("tool_calls")
|
| 130 |
+
if tcs:
|
| 131 |
+
# Normalise to {name, arguments} pairs (OpenAI-style function tool_calls).
|
| 132 |
+
out = []
|
| 133 |
+
for tc in tcs:
|
| 134 |
+
fn = (tc or {}).get("function", {}) if isinstance(tc, dict) else {}
|
| 135 |
+
out.append({"name": fn.get("name"), "arguments": fn.get("arguments")})
|
| 136 |
+
return {"tool_calls": out}
|
| 137 |
+
return msg.get("content", "")
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
def map_row(row: Dict[str, Any], *, label: str = "SAMPLE",
|
| 141 |
+
environment: str = "nemotron-agentic") -> Dict[str, Any]:
|
| 142 |
+
"""Map ONE Nemotron-Agentic-v1 row to a sealed, DSSE-signed SZL trajectory.
|
| 143 |
+
|
| 144 |
+
Returns the seal() dict: {"provenance", "steps", "jsonl"} with the attribution
|
| 145 |
+
and source UUID embedded in provenance.extra.
|
| 146 |
+
"""
|
| 147 |
+
uuid_src = row.get("uuid") or ""
|
| 148 |
+
messages: List[Dict[str, Any]] = row.get("messages") or []
|
| 149 |
+
tools = row.get("tools") or []
|
| 150 |
+
tools_present = bool(tools)
|
| 151 |
+
reasoning = row.get("reasoning", "off")
|
| 152 |
+
used_in = row.get("used_in") or []
|
| 153 |
+
|
| 154 |
+
# Reconstruct a short task descriptor from the first user turn.
|
| 155 |
+
task = ""
|
| 156 |
+
for m in messages:
|
| 157 |
+
if m.get("role") == "user":
|
| 158 |
+
c = m.get("content") or ""
|
| 159 |
+
task = (c[:160] + "…") if isinstance(c, str) and len(c) > 160 else c
|
| 160 |
+
break
|
| 161 |
+
|
| 162 |
+
t = sts.SignedTrajectory(task=task or f"nemotron:{uuid_src[:8]}",
|
| 163 |
+
environment=environment, label=label)
|
| 164 |
+
last_assistant_idx: Optional[int] = None
|
| 165 |
+
last_assistant_text = ""
|
| 166 |
+
context_turns: List[Dict[str, Any]] = []
|
| 167 |
+
|
| 168 |
+
for m in messages:
|
| 169 |
+
role = m.get("role")
|
| 170 |
+
if role in ("system", "user"):
|
| 171 |
+
# Context, not a signed agent action — preserved in provenance.
|
| 172 |
+
context_turns.append({"role": role,
|
| 173 |
+
"content": (m.get("content") or "")[:500]})
|
| 174 |
+
continue
|
| 175 |
+
if role == "tool":
|
| 176 |
+
# Observation turn: attach as a tool observation step.
|
| 177 |
+
t.add(action={"tool_result_for": m.get("tool_call_id")},
|
| 178 |
+
observation=m.get("content", ""), role="tool",
|
| 179 |
+
pattern="ReAct", restraint_verdict="MONITOR")
|
| 180 |
+
continue
|
| 181 |
+
if role == "assistant":
|
| 182 |
+
pattern = _pattern_for(m, last_assistant_text)
|
| 183 |
+
verdict = _verdict_for(m, tools_present)
|
| 184 |
+
is_corr = pattern == "Reflexion"
|
| 185 |
+
t.add(action=_action_of(m), observation="", role="assistant",
|
| 186 |
+
pattern=pattern, restraint_verdict=verdict,
|
| 187 |
+
is_correction=is_corr,
|
| 188 |
+
correction_of=last_assistant_idx if is_corr else None,
|
| 189 |
+
tool_calls=([{"function": (tc or {}).get("function", {})}
|
| 190 |
+
for tc in (m.get("tool_calls") or [])] or None))
|
| 191 |
+
last_assistant_idx = len(t.steps) - 1
|
| 192 |
+
if isinstance(m.get("content"), str):
|
| 193 |
+
last_assistant_text = m["content"]
|
| 194 |
+
|
| 195 |
+
sealed = t.seal(outcome="mapped")
|
| 196 |
+
sealed["provenance"]["source"] = "nvidia"
|
| 197 |
+
sealed["provenance"]["verified"] = False
|
| 198 |
+
sealed["provenance"]["reasoning_mode"] = reasoning
|
| 199 |
+
sealed["provenance"]["used_in"] = used_in
|
| 200 |
+
sealed["provenance"]["source_uuid"] = uuid_src
|
| 201 |
+
sealed["provenance"]["context_turns"] = context_turns
|
| 202 |
+
sealed["provenance"]["tools_count"] = len(tools)
|
| 203 |
+
sealed["provenance"]["attribution"] = ATTRIBUTION
|
| 204 |
+
return sealed
|
| 205 |
+
|
| 206 |
+
|
| 207 |
+
def map_rows(rows: Iterable[Dict[str, Any]], *, label: str = "SAMPLE",
|
| 208 |
+
environment: str = "nemotron-agentic") -> List[Dict[str, Any]]:
|
| 209 |
+
return [map_row(r, label=label, environment=environment) for r in rows]
|
| 210 |
+
|
| 211 |
+
|
| 212 |
+
# --------------------------------------------------------------------------- #
|
| 213 |
+
# Representative SAMPLE rows (faithful to the published schema; NOT NVIDIA's
|
| 214 |
+
# verbatim rows — see ATTRIBUTION.note). Used to ship a demo corpus CPU-only.
|
| 215 |
+
# --------------------------------------------------------------------------- #
|
| 216 |
+
def sample_rows() -> List[Dict[str, Any]]:
|
| 217 |
+
"""A handful of schema-faithful representative rows (tool_calling +
|
| 218 |
+
interactive_agent flavours), so the served demo has real content offline."""
|
| 219 |
+
return [
|
| 220 |
+
{
|
| 221 |
+
"uuid": "ff6ab2b0-7551-468c-8606-a7c3b0c75b68",
|
| 222 |
+
"license": "cc-by-4.0",
|
| 223 |
+
"used_in": ["nano_v3"],
|
| 224 |
+
"reasoning": "on",
|
| 225 |
+
"tools": [{"type": "function", "function": {
|
| 226 |
+
"name": "place_order",
|
| 227 |
+
"description": "Create a new food delivery order",
|
| 228 |
+
"parameters": {"type": "object", "properties": {
|
| 229 |
+
"user_id": {"type": "string"},
|
| 230 |
+
"restaurant_id": {"type": "string"}}}}}],
|
| 231 |
+
"messages": [
|
| 232 |
+
{"role": "system", "content": "You are a customer service agent. "
|
| 233 |
+
"Follow the <policy>…</policy>. You may message the user or call a tool."},
|
| 234 |
+
{"role": "user", "content": "Order me a large pepperoni pizza from Tony's."},
|
| 235 |
+
{"role": "assistant", "content": "Let me check the menu first.",
|
| 236 |
+
"tool_calls": [{"type": "function", "function": {
|
| 237 |
+
"name": "get_menu", "arguments": "{\"restaurant_id\":\"tonys\"}"}}]},
|
| 238 |
+
{"role": "tool", "tool_call_id": "c1",
|
| 239 |
+
"content": "{\"items\":[{\"id\":\"p_lg\",\"name\":\"Large Pepperoni\"}]}"},
|
| 240 |
+
{"role": "assistant", "content": "I need to verify your delivery address "
|
| 241 |
+
"before placing the order. Could you confirm it?"},
|
| 242 |
+
{"role": "user", "content": "123 Main St, 10588."},
|
| 243 |
+
{"role": "assistant", "content": "Placing your order now.",
|
| 244 |
+
"tool_calls": [{"type": "function", "function": {
|
| 245 |
+
"name": "place_order",
|
| 246 |
+
"arguments": "{\"user_id\":\"u1\",\"restaurant_id\":\"tonys\"}"}}]},
|
| 247 |
+
{"role": "tool", "tool_call_id": "c2",
|
| 248 |
+
"content": "{\"order_id\":\"o_99\",\"status\":\"confirmed\"}"},
|
| 249 |
+
{"role": "assistant", "content": "Done — order o_99 is confirmed."},
|
| 250 |
+
],
|
| 251 |
+
},
|
| 252 |
+
{
|
| 253 |
+
"uuid": "a1c9d2e3-0000-4aaa-bbbb-1234567890ab",
|
| 254 |
+
"license": "cc-by-4.0",
|
| 255 |
+
"used_in": ["super_v3"],
|
| 256 |
+
"reasoning": "on",
|
| 257 |
+
"tools": [{"type": "function", "function": {
|
| 258 |
+
"name": "search_flights",
|
| 259 |
+
"description": "Search available flights",
|
| 260 |
+
"parameters": {"type": "object", "properties": {
|
| 261 |
+
"origin": {"type": "string"}, "dest": {"type": "string"}}}}}],
|
| 262 |
+
"messages": [
|
| 263 |
+
{"role": "system", "content": "You are a travel agent. Obey policy."},
|
| 264 |
+
{"role": "user", "content": "Find me a flight JFK to LAX tomorrow."},
|
| 265 |
+
{"role": "assistant", "content": "Searching flights.",
|
| 266 |
+
"tool_calls": [{"type": "function", "function": {
|
| 267 |
+
"name": "search_flights",
|
| 268 |
+
"arguments": "{\"origin\":\"JFK\",\"dest\":\"LAZ\"}"}}]},
|
| 269 |
+
{"role": "tool", "tool_call_id": "c1",
|
| 270 |
+
"content": "{\"error\":\"unknown airport LAZ\"}"},
|
| 271 |
+
{"role": "assistant", "content": "Correction: I mistyped the airport "
|
| 272 |
+
"code. Let me retry with LAX.",
|
| 273 |
+
"tool_calls": [{"type": "function", "function": {
|
| 274 |
+
"name": "search_flights",
|
| 275 |
+
"arguments": "{\"origin\":\"JFK\",\"dest\":\"LAX\"}"}}]},
|
| 276 |
+
{"role": "tool", "tool_call_id": "c2",
|
| 277 |
+
"content": "{\"flights\":[{\"id\":\"f1\",\"dep\":\"08:00\"}]}"},
|
| 278 |
+
{"role": "assistant", "content": "Found flight f1 departing 08:00."},
|
| 279 |
+
],
|
| 280 |
+
},
|
| 281 |
+
{
|
| 282 |
+
"uuid": "b2d0e4f5-1111-4ccc-dddd-0987654321fe",
|
| 283 |
+
"license": "cc-by-4.0",
|
| 284 |
+
"used_in": ["ultra_v3"],
|
| 285 |
+
"reasoning": "off",
|
| 286 |
+
"tools": [{"type": "function", "function": {
|
| 287 |
+
"name": "issue_refund",
|
| 288 |
+
"description": "Issue a refund subject to policy tier",
|
| 289 |
+
"parameters": {"type": "object", "properties": {
|
| 290 |
+
"order_id": {"type": "string"},
|
| 291 |
+
"refund_tier": {"type": "string", "enum": ["full", "partial"]}}}}}],
|
| 292 |
+
"messages": [
|
| 293 |
+
{"role": "system", "content": "Refund agent. Enforce refund policy strictly."},
|
| 294 |
+
{"role": "user", "content": "I want a full refund on order o_77, no receipt."},
|
| 295 |
+
{"role": "assistant", "content": "I'm sorry, but I cannot issue a full "
|
| 296 |
+
"refund without proof of purchase under policy. I can escalate to a "
|
| 297 |
+
"human reviewer if you'd like."},
|
| 298 |
+
],
|
| 299 |
+
},
|
| 300 |
+
]
|
| 301 |
+
|
| 302 |
+
|
| 303 |
+
# --------------------------------------------------------------------------- #
|
| 304 |
+
# Optional: load real NVIDIA rows from a local JSONL (if a user has downloaded
|
| 305 |
+
# the dataset). Network-free; only reads what's on disk.
|
| 306 |
+
# --------------------------------------------------------------------------- #
|
| 307 |
+
def load_local_jsonl(path: str, limit: Optional[int] = None) -> List[Dict[str, Any]]:
|
| 308 |
+
rows: List[Dict[str, Any]] = []
|
| 309 |
+
if not os.path.exists(path):
|
| 310 |
+
return rows
|
| 311 |
+
with open(path, "r", encoding="utf-8") as fh:
|
| 312 |
+
for i, line in enumerate(fh):
|
| 313 |
+
if limit is not None and i >= limit:
|
| 314 |
+
break
|
| 315 |
+
line = line.strip()
|
| 316 |
+
if line:
|
| 317 |
+
try:
|
| 318 |
+
rows.append(json.loads(line))
|
| 319 |
+
except Exception:
|
| 320 |
+
continue
|
| 321 |
+
return rows
|
| 322 |
+
|
| 323 |
+
|
| 324 |
+
def build_sample_corpus(label: str = "SAMPLE") -> Dict[str, Any]:
|
| 325 |
+
"""Map the representative sample rows; return {trajectories, jsonl, stats}."""
|
| 326 |
+
sealed = map_rows(sample_rows(), label=label)
|
| 327 |
+
all_steps_jsonl: List[str] = []
|
| 328 |
+
for s in sealed:
|
| 329 |
+
if s["jsonl"]:
|
| 330 |
+
all_steps_jsonl.append(s["jsonl"])
|
| 331 |
+
jsonl = "\n".join(all_steps_jsonl)
|
| 332 |
+
total_steps = sum(s["provenance"]["total_steps"] for s in sealed)
|
| 333 |
+
verdict_counts: Dict[str, int] = {}
|
| 334 |
+
pattern_counts: Dict[str, int] = {}
|
| 335 |
+
for s in sealed:
|
| 336 |
+
for st in s["steps"]:
|
| 337 |
+
verdict_counts[st["restraint_verdict"]] = \
|
| 338 |
+
verdict_counts.get(st["restraint_verdict"], 0) + 1
|
| 339 |
+
pattern_counts[st["pattern"]] = pattern_counts.get(st["pattern"], 0) + 1
|
| 340 |
+
return {
|
| 341 |
+
"trajectories": sealed,
|
| 342 |
+
"jsonl": jsonl,
|
| 343 |
+
"stats": {
|
| 344 |
+
"label": label,
|
| 345 |
+
"trajectory_count": len(sealed),
|
| 346 |
+
"total_steps": total_steps,
|
| 347 |
+
"verdict_counts": verdict_counts,
|
| 348 |
+
"pattern_counts": pattern_counts,
|
| 349 |
+
"signing_available": sts.signing_available(),
|
| 350 |
+
"attribution": ATTRIBUTION,
|
| 351 |
+
},
|
| 352 |
+
}
|
| 353 |
+
|
| 354 |
+
|
| 355 |
+
if __name__ == "__main__":
|
| 356 |
+
corpus = build_sample_corpus()
|
| 357 |
+
v = sts.verify_jsonl(corpus["jsonl"])
|
| 358 |
+
print(json.dumps({
|
| 359 |
+
"trajectories": corpus["stats"]["trajectory_count"],
|
| 360 |
+
"total_steps": corpus["stats"]["total_steps"],
|
| 361 |
+
"verdict_counts": corpus["stats"]["verdict_counts"],
|
| 362 |
+
"pattern_counts": corpus["stats"]["pattern_counts"],
|
| 363 |
+
"verify": {k: v[k] for k in ("total_steps", "all_hash_ok", "signed", "sig_ok")},
|
| 364 |
+
"attribution_license": corpus["stats"]["attribution"]["license"],
|
| 365 |
+
}, indent=2))
|