sys1bench 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. sys1bench/__init__.py +3 -0
  2. sys1bench/adapters/__init__.py +19 -0
  3. sys1bench/adapters/base.py +159 -0
  4. sys1bench/adapters/embed_knn.py +61 -0
  5. sys1bench/adapters/encoder_finetuned.py +114 -0
  6. sys1bench/adapters/generic_http.py +161 -0
  7. sys1bench/adapters/hybrid_router.py +49 -0
  8. sys1bench/adapters/jev_openrouter.py +172 -0
  9. sys1bench/adapters/jev_typesafe.py +165 -0
  10. sys1bench/adapters/laya_local.py +192 -0
  11. sys1bench/adapters/llm_constrained.py +104 -0
  12. sys1bench/adapters/majority_prior.py +53 -0
  13. sys1bench/adapters/mock.py +77 -0
  14. sys1bench/adapters/nli_zeroshot.py +50 -0
  15. sys1bench/adapters/regex_keyword.py +70 -0
  16. sys1bench/analysis/__init__.py +4 -0
  17. sys1bench/analysis/arms.py +46 -0
  18. sys1bench/analysis/decision_value.py +43 -0
  19. sys1bench/analysis/decomposition.py +47 -0
  20. sys1bench/analysis/meta_eval.py +67 -0
  21. sys1bench/analysis/stats.py +76 -0
  22. sys1bench/cli.py +434 -0
  23. sys1bench/data/__init__.py +37 -0
  24. sys1bench/data/canary/canary.jsonl +200 -0
  25. sys1bench/data/framings/guardrail_intent.yaml +21 -0
  26. sys1bench/data/framings/log_triage.yaml +20 -0
  27. sys1bench/data/framings/phishing_email.yaml +25 -0
  28. sys1bench/data/framings/policy_compliance.yaml +18 -0
  29. sys1bench/data/framings/support_tickets.yaml +41 -0
  30. sys1bench/framing/__init__.py +10 -0
  31. sys1bench/framing/expand.py +214 -0
  32. sys1bench/framing/perturb.py +67 -0
  33. sys1bench/generators/__init__.py +10 -0
  34. sys1bench/generators/base.py +105 -0
  35. sys1bench/generators/guardrail_intent.py +95 -0
  36. sys1bench/generators/log_triage.py +100 -0
  37. sys1bench/generators/multilingual_tickets.py +84 -0
  38. sys1bench/generators/phishing_email.py +127 -0
  39. sys1bench/generators/policy_compliance.py +90 -0
  40. sys1bench/generators/rag_relevance.py +122 -0
  41. sys1bench/generators/support_tickets.py +212 -0
  42. sys1bench/metrics/__init__.py +9 -0
  43. sys1bench/metrics/calibration.py +164 -0
  44. sys1bench/metrics/consistency.py +71 -0
  45. sys1bench/metrics/decision_value.py +43 -0
  46. sys1bench/metrics/efficiency.py +35 -0
  47. sys1bench/metrics/ordinal.py +80 -0
  48. sys1bench/metrics/robustness.py +46 -0
  49. sys1bench/metrics/selective.py +67 -0
  50. sys1bench/report/__init__.py +1 -0
  51. sys1bench/report/dashboard.py +177 -0
  52. sys1bench/report/latex.py +40 -0
  53. sys1bench/report/plots.py +143 -0
  54. sys1bench/report/results_doc.py +237 -0
  55. sys1bench/report/scorecard.py +170 -0
  56. sys1bench/runners/__init__.py +2 -0
  57. sys1bench/runners/benchmark_runner.py +149 -0
  58. sys1bench/runners/cache.py +53 -0
  59. sys1bench/runners/canary.py +43 -0
  60. sys1bench/runners/hybrid_sweep.py +37 -0
  61. sys1bench/runners/noul_consistency.py +121 -0
  62. sys1bench/runners/ordinal_probes.py +90 -0
  63. sys1bench/runners/robustness.py +112 -0
  64. sys1bench/runners/suite.py +183 -0
  65. sys1bench/runners/sweeps.py +190 -0
  66. sys1bench/schemas.py +274 -0
  67. sys1bench-0.3.0.dist-info/METADATA +195 -0
  68. sys1bench-0.3.0.dist-info/RECORD +72 -0
  69. sys1bench-0.3.0.dist-info/WHEEL +5 -0
  70. sys1bench-0.3.0.dist-info/entry_points.txt +2 -0
  71. sys1bench-0.3.0.dist-info/licenses/LICENSE +202 -0
  72. sys1bench-0.3.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,100 @@
1
+ """Log triage generator: JSON log records with injected distractor keys.
2
+
3
+ severity (score 0..3) -> from the log level and error class, per a stated policy
4
+ owning_team (choice) -> from the service name (a mapping is given in the state as `team_directory`)
5
+ page_oncall (noul) -> policy: severity >= 2 AND the environment is production
6
+ The team directory lives in the state, so the model must read a lookup table rather than guess; the paging rule
7
+ combines two fields. Distractors are irrelevant keys with plausible values.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import json
13
+
14
+ from ..schemas import Controls, LabelProvenance, Level, Option, Question, TaskItem
15
+ from .base import BaseGenerator, canary_for, estimate_tokens, pad_to_tokens, register_generator
16
+
17
+ SERVICES = {"checkout-api": "payments", "ledger": "payments", "search-indexer": "discovery", "ranker": "discovery", "auth-gateway": "identity",
18
+ "session-store": "identity", "notify-worker": "messaging", "mailer": "messaging", "cdn-edge": "platform", "k8s-autoscaler": "platform"}
19
+ TEAMS = [("payments", "Checkout, ledger, refunds"), ("discovery", "Search and ranking"), ("identity", "Auth, sessions, SSO"),
20
+ ("messaging", "Email, push, in-app notifications"), ("platform", "Infra, CDN, orchestration")]
21
+ # (level, error class, base severity, message)
22
+ EVENTS = [
23
+ ("INFO", "deploy", 0, "deployment {ver} rolled out to {pct}% of pods"),
24
+ ("INFO", "cache", 0, "cache warmup complete in {ms} ms"),
25
+ ("WARN", "latency", 1, "p99 latency {ms} ms exceeds SLO of 800 ms for {n} minutes"),
26
+ ("WARN", "retry", 1, "upstream {dep} returned 503; retry {n}/5 succeeded"),
27
+ ("ERROR", "exception", 2, "NullPointerException in {fn} handling request {rid}; {n} requests failed"),
28
+ ("ERROR", "db", 2, "connection pool exhausted; {n} queries queued > 5 s"),
29
+ ("CRITICAL", "outage", 3, "health check failing on all replicas for {n} minutes; traffic returning 5xx"),
30
+ ("CRITICAL", "data", 3, "write path returned inconsistent results; {n} records may be lost"),
31
+ ("ERROR", "auth", 2, "token validation failing for {pct}% of requests since {ver}"),
32
+ ("WARN", "disk", 1, "disk usage at {pct}% on {n} nodes"),
33
+ ]
34
+ ENVS = ["production", "production", "staging", "canary"]
35
+ DISTRACTORS = {"trace_sampling": "0.1", "build_sha": "a3f9c21", "region": "eu-central-1", "feature_flags": ["new-ranker", "dark-mode"],
36
+ "previous_incident": "INC-2041 (resolved)", "runbook_url": "https://runbooks.internal/generic", "request_headers": {"x-ab": "B"}}
37
+ SEV = [Level(level=0, description="Informational; no action."), Level(level=1, description="Degraded; investigate within the day."),
38
+ Level(level=2, description="Errors affecting users; fix within hours."), Level(level=3, description="Outage or data loss; all hands now.")]
39
+ SEV_INSTR = "Severity 0-3 from the log record. Policy: INFO=0, WARN=1, ERROR=2, CRITICAL=3."
40
+
41
+
42
+ @register_generator("log_triage")
43
+ class LogTriageGenerator(BaseGenerator):
44
+ version = "1.0.0"
45
+
46
+ def generate(self) -> list[TaskItem]:
47
+ kb = self.knobs
48
+ rng, ctl = self.rng("items"), self.rng("controls")
49
+ items = []
50
+ for i in range(kb.n):
51
+ distract = ctl.random() < kb.distractor_density
52
+ noise_draw = ctl.random() < kb.label_noise
53
+ service = rng.choice(list(SERVICES))
54
+ level, cls, sev, msg = rng.choice(EVENTS)
55
+ env = rng.choice(ENVS)
56
+ msg = msg.format(ver=f"v{rng.randrange(2, 9)}.{rng.randrange(0, 30)}", pct=rng.randrange(5, 100), ms=rng.randrange(120, 4000),
57
+ n=rng.randrange(1, 60), dep=rng.choice(list(SERVICES)), fn=rng.choice(["chargeCard", "buildIndex", "verifyToken", "renderEmail"]),
58
+ rid=f"req-{rng.randrange(10**5, 10**6)}")
59
+ record = {"ts": f"2026-09-{rng.randrange(1, 28):02d}T{rng.randrange(0, 24):02d}:{rng.randrange(0, 60):02d}:00Z", "level": level, "service": service,
60
+ "env": env, "message": msg, "error_class": cls}
61
+ if distract:
62
+ for k in ctl.sample(list(DISTRACTORS), 3):
63
+ record[k] = DISTRACTORS[k]
64
+ state = {"log": record, "team_directory": SERVICES,
65
+ "paging_policy": "Page the on-call engineer only when severity is 2 or higher AND env is production."}
66
+ if kb.target_tokens:
67
+ state["context"] = pad_to_tokens(ctl, "", kb.target_tokens)
68
+ team = SERVICES[service]
69
+ page = sev >= 2 and env == "production"
70
+ noise = False
71
+ if kb.label_noise and noise_draw:
72
+ noise = True
73
+ team = ctl.choice([t for t, _ in TEAMS if t != team])
74
+ sev = ctl.choice([x for x in range(4) if x != sev])
75
+ page = not page
76
+ tid = f"logs_{kb.seed}_{i:05d}"
77
+ items.append(TaskItem(
78
+ task_id=tid, tier="G", domain="log_triage", language=kb.language, state=state,
79
+ state_tokens=estimate_tokens(json.dumps(state)),
80
+ questions={
81
+ "severity": Question(type="score", instructions=SEV_INSTR, criteria=SEV, ground_truth=sev, framing_group="logs.severity"),
82
+ "owning_team": Question(type="choice", instructions="Which team owns the service in `log.service`? Use `team_directory`.",
83
+ criteria=[Option(key=k, description=d) for k, d in TEAMS], ground_truth=team, framing_group="logs.owning_team"),
84
+ "page_oncall": Question(type="noul", instructions="Per `paging_policy`, should the on-call engineer be paged for this record?",
85
+ ground_truth=page, framing_group="logs.page_oncall"),
86
+ },
87
+ label_provenance=LabelProvenance(source="generator", generator=self.generator_id, seed=kb.seed, noise_injected=noise),
88
+ controls=Controls(distractor_density=kb.distractor_density), cost_matrix=self.cost_matrices(), canary=canary_for(tid),
89
+ metadata={"level": level, "env": env, "service": service},
90
+ ))
91
+ return items
92
+
93
+ def regex_rules(self) -> dict:
94
+ return {"logs.severity": {"0": [r'"level": "INFO"'], "1": [r'"level": "WARN"'], "2": [r'"level": "ERROR"'], "3": [r'"level": "CRITICAL"']},
95
+ "logs.owning_team": {t: [rf'"service": "{s}"' for s, tt in SERVICES.items() if tt == t] for t, _ in TEAMS},
96
+ "logs.page_oncall": [r'"level": "(ERROR|CRITICAL)"[^}]*"env": "production"|"env": "production"[^}]*"level": "(ERROR|CRITICAL)"']}
97
+
98
+ def cost_matrices(self):
99
+ return {"page_oncall": {"true": {"true": 0.0, "false": 50.0}, "false": {"true": 5.0, "false": 0.0}},
100
+ "severity": {str(t): {str(p): float(abs(t - p) * (3 if p < t else 1)) for p in range(4)} for t in range(4)}}
@@ -0,0 +1,84 @@
1
+ """Multilingual support tickets: the support_tickets task templated in German, Spanish, French and Hindi (Devanagari),
2
+ with English questions and criteria. Labels follow the same policy as the English generator. Used for Laya's language
3
+ router and for Jev's language behaviour; scripts other than Latin are the collapse case reported by Convai.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from ..schemas import Controls, LabelProvenance, Option, Question, TaskItem
9
+ from .base import BaseGenerator, canary_for, estimate_tokens, register_generator
10
+ from .support_tickets import PRIORITY_INSTRUCTIONS, PRIORITY_LEVELS, QUEUES
11
+
12
+ # queue, base urgency, {lang: template}
13
+ T = [
14
+ ("billing", 1, {"de": "Mir wurde am {day} zweimal {amt} € für Bestellung #{order} abgebucht. Es darf nur eine Abbuchung geben.",
15
+ "es": "Me cobraron dos veces {amt} € el {day} por el pedido #{order}. Solo debería haber un cargo.",
16
+ "fr": "J'ai été débité deux fois de {amt} € le {day} pour la commande #{order}. Il ne devrait y avoir qu'un seul débit.",
17
+ "hi": "ऑर्डर #{order} के लिए {day} को मुझसे दो बार ₹{amt} लिए गए। केवल एक ही शुल्क होना चाहिए।"}),
18
+ ("shipping", 1, {"de": "Bestellung #{order} gilt seit {day} als zugestellt, aber nichts ist angekommen.",
19
+ "es": "El pedido #{order} figura como entregado el {day}, pero no ha llegado nada.",
20
+ "fr": "La commande #{order} est indiquée livrée le {day}, mais rien n'est arrivé.",
21
+ "hi": "ऑर्डर #{order} {day} को डिलीवर दिखाया गया है, लेकिन कुछ भी नहीं आया।"}),
22
+ ("account", 2, {"de": "Ich bin ausgesperrt. Die Zwei-Faktor-Codes gehen an eine alte Nummer.",
23
+ "es": "No puedo entrar. Los códigos de dos factores van a un número antiguo.",
24
+ "fr": "Je suis bloqué. Les codes à deux facteurs partent vers un ancien numéro.",
25
+ "hi": "मैं लॉक आउट हो गया हूँ। टू-फैक्टर कोड पुराने नंबर पर जा रहे हैं।"}),
26
+ ("technical", 2, {"de": "Die App stürzt seit dem Update am {day} beim Start ab. Fehlercode {code}.",
27
+ "es": "La aplicación se cierra al abrirla desde la actualización del {day}. Código de error {code}.",
28
+ "fr": "L'application plante au démarrage depuis la mise à jour du {day}. Code d'erreur {code}.",
29
+ "hi": "{day} के अपडेट के बाद से ऐप खोलते ही क्रैश हो जाता है। एरर कोड {code}।"}),
30
+ ("cancellation", 1, {"de": "Bitte kündigen Sie mein Abo vor der Verlängerung am {day}.",
31
+ "es": "Por favor cancelen mi suscripción antes de la renovación del {day}.",
32
+ "fr": "Veuillez annuler mon abonnement avant le renouvellement du {day}.",
33
+ "hi": "कृपया {day} के नवीनीकरण से पहले मेरी सदस्यता रद्द करें।"}),
34
+ ("sales", 0, {"de": "Gibt es einen Rabatt bei jährlicher Zahlung für das Team-Paket?",
35
+ "es": "¿Hay descuento por pago anual en el plan de equipo?",
36
+ "fr": "Y a-t-il une remise pour un paiement annuel sur l'offre équipe ?",
37
+ "hi": "टीम प्लान पर वार्षिक भुगतान के लिए कोई छूट है?"}),
38
+ ]
39
+ ANGRY = {"de": "Das ist völlig inakzeptabel, ich bin wütend.", "es": "Esto es completamente inaceptable, estoy furioso.",
40
+ "fr": "C'est totalement inacceptable, je suis furieux.", "hi": "यह पूरी तरह अस्वीकार्य है, मैं बहुत गुस्से में हूँ।"}
41
+ CALM = {"de": "Vielen Dank im Voraus.", "es": "Gracias de antemano.", "fr": "Merci d'avance.", "hi": "अग्रिम धन्यवाद।"}
42
+ CTX = {"de": "Kontext: Kundenstufe {tier}.", "es": "Contexto: nivel de cliente {tier}.", "fr": "Contexte : niveau client {tier}.", "hi": "संदर्भ: ग्राहक स्तर {tier}।"}
43
+ TIERS = ["free", "standard", "gold", "enterprise"]
44
+
45
+
46
+ @register_generator("multilingual_tickets")
47
+ class MultilingualTicketsGenerator(BaseGenerator):
48
+ version = "1.0.0"
49
+
50
+ def generate(self) -> list[TaskItem]:
51
+ kb = self.knobs
52
+ rng = self.rng("items")
53
+ langs = [kb.language] if kb.language in ("de", "es", "fr", "hi") else ["de", "es", "fr", "hi"]
54
+ queues = [q for q in QUEUES if q[0] in {t[0] for t in T}]
55
+ items = []
56
+ for i in range(kb.n):
57
+ lang = langs[i % len(langs)]
58
+ queue, base, tmpl = rng.choice(T)
59
+ angry = rng.random() < 0.3
60
+ tier = rng.choice(TIERS)
61
+ body = tmpl[lang].format(day=rng.choice(["Montag", "3.", "gestern"]) if lang == "de" else rng.choice(["lunes", "3", "ayer"]) if lang == "es"
62
+ else rng.choice(["lundi", "3", "hier"]) if lang == "fr" else rng.choice(["सोमवार", "3 तारीख", "कल"]),
63
+ amt=rng.choice([19, 45, 89, 249]), order=rng.randrange(10000, 99999), code=f"E{rng.randrange(100, 999)}")
64
+ state = f"{body} {(ANGRY if angry else CALM)[lang]}\n{CTX[lang].format(tier=tier)}"
65
+ priority = min(4, base + (1 if angry else 0) + (1 if tier in ("gold", "enterprise") else 0))
66
+ tid = f"mltickets_{kb.seed}_{lang}_{i:05d}"
67
+ items.append(TaskItem(
68
+ task_id=tid, tier="G", domain="support_triage", language=lang, state=state, state_tokens=estimate_tokens(state),
69
+ questions={
70
+ "queue": Question(type="choice", instructions="Which queue should handle this ticket?", criteria=[Option(key=k, description=d) for k, d in queues],
71
+ ground_truth=queue, framing_group="tickets.queue"),
72
+ "is_angry": Question(type="noul", instructions="Is the customer expressing anger or hostility?", ground_truth=angry, framing_group="tickets.is_angry"),
73
+ "priority": Question(type="score", instructions=PRIORITY_INSTRUCTIONS, criteria=PRIORITY_LEVELS, ground_truth=priority, framing_group="tickets.priority"),
74
+ },
75
+ label_provenance=LabelProvenance(source="generator", generator=self.generator_id, seed=kb.seed), controls=Controls(),
76
+ canary=canary_for(tid), metadata={"lang": lang, "tier": tier, "angry": angry, "base_urgency": base},
77
+ ))
78
+ return items
79
+
80
+ def regex_rules(self) -> dict:
81
+ return {"tickets.is_angry": [r"inakzeptabel|inaceptable|furieux|अस्वीकार्य"]}
82
+
83
+ def cost_matrices(self):
84
+ return {}
@@ -0,0 +1,127 @@
1
+ """Phishing email generator (is_phishing: noul, attack_class: choice, urgency: score) with a built-in
2
+ 5-way decomposition for Suite B (sender mismatch, credential request, urgency language, link/domain
3
+ mismatch, unexpected attachment) combined by a fixed rule.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import json
9
+
10
+ from ..schemas import Controls, LabelProvenance, Level, Option, Question, TaskItem
11
+ from .base import BaseGenerator, canary_for, estimate_tokens, pad_to_tokens, register_generator
12
+
13
+ BRANDS = ["Northwind Bank", "Contoso Cloud", "Fabrikam Shipping", "Tailspin Payroll", "Woodgrove HR"]
14
+ LEGIT_DOMAINS = {"Northwind Bank": "northwindbank.com", "Contoso Cloud": "contoso.com", "Fabrikam Shipping": "fabrikam.com",
15
+ "Tailspin Payroll": "tailspin.com", "Woodgrove HR": "woodgrove.com"}
16
+ LOOKALIKE = {"northwindbank.com": "northwind-bank-secure.com", "contoso.com": "contoso-cloud-login.net",
17
+ "fabrikam.com": "fabrikam-delivery.co", "tailspin.com": "tailspin-payroll.info", "woodgrove.com": "woodgrove-hr.org"}
18
+
19
+ CLASSES = [
20
+ ("credential_harvest", "Asks the recipient to log in or verify credentials via a link"),
21
+ ("invoice_fraud", "Fake invoice or payment redirection"),
22
+ ("malware_attachment", "Unexpected attachment the recipient is urged to open"),
23
+ ("ceo_fraud", "Impersonates an executive requesting an urgent transfer or gift cards"),
24
+ ("benign", "Legitimate message with no attack"),
25
+ ]
26
+ URGENCY = [Level(level=0, description="No time pressure"), Level(level=1, description="Mild: 'when you get a chance'"),
27
+ Level(level=2, description="Moderate: action requested within days"), Level(level=3, description="High: within 24 hours or account consequences")]
28
+
29
+ LEGIT_TEMPLATES = [
30
+ ("Your {brand} statement is ready", "Hi {name}, your monthly statement is available in the app under Documents. No action is required.", 0, False),
31
+ ("Delivery update for order {order}", "Hi {name}, your parcel is out for delivery today. Track it in your account.", 0, False),
32
+ ("Team lunch Thursday", "Hi {name}, we're doing lunch Thursday at noon. Reply if you have dietary needs.", 1, False),
33
+ ("Q3 timesheet reminder", "Hi {name}, timesheets are due Friday. Submit through the usual portal when you get a chance.", 1, False),
34
+ ("Invoice {order} attached", "Hi {name}, attached is the invoice we discussed on the call. Let me know if anything looks off.", 1, True),
35
+ ]
36
+ PHISH_TEMPLATES = [
37
+ ("credential_harvest", "Urgent: verify your {brand} account", "Dear customer, unusual sign-in detected. Verify your identity within 24 hours at https://{fake}/verify or your account will be suspended.", 3, False),
38
+ ("credential_harvest", "Password expires today", "Your {brand} password expires today. Reset now: https://{fake}/reset", 3, False),
39
+ ("invoice_fraud", "Updated banking details for invoice {order}", "Hi {name}, please note our bank details have changed. Remit invoice {order} to the new account in the attached PDF by end of week.", 2, True),
40
+ ("malware_attachment", "Scanned document", "Please see the attached scan (Document_{order}.zip). Open to review.", 1, True),
41
+ ("ceo_fraud", "Quick favour", "{name}, I'm in a meeting and need you to buy 5 gift cards for a client today. Send the codes here. Keep it confidential. Sent from my iPhone", 3, False),
42
+ ]
43
+ NAMES = ["Alex", "Priya", "Jordan", "Mei", "Sam", "Fatima", "Luca", "Noor"]
44
+
45
+
46
+ @register_generator("phishing_email")
47
+ class PhishingEmailGenerator(BaseGenerator):
48
+ version = "1.0.1"
49
+
50
+ def generate(self) -> list[TaskItem]:
51
+ kb = self.knobs
52
+ rng = self.rng("items")
53
+ ctl = self.rng("controls")
54
+ items: list[TaskItem] = []
55
+ for i in range(kb.n):
56
+ distract = ctl.random() < kb.distractor_density
57
+ noise_draw = ctl.random() < kb.label_noise
58
+ brand = rng.choice(BRANDS)
59
+ legit_dom = LEGIT_DOMAINS[brand]
60
+ name = rng.choice(NAMES)
61
+ order = rng.randrange(10000, 99999)
62
+ is_phish = rng.random() < 0.5
63
+ if is_phish:
64
+ cls, subj, body, urg, attach = rng.choice(PHISH_TEMPLATES)
65
+ sender_dom = LOOKALIKE[legit_dom] if rng.random() < 0.8 else legit_dom # 20% spoof the real domain
66
+ fake = LOOKALIKE[legit_dom]
67
+ else:
68
+ subj, body, urg, attach = rng.choice(LEGIT_TEMPLATES)
69
+ cls, sender_dom, fake = "benign", legit_dom, legit_dom
70
+ body = body.format(brand=brand, name=name, order=order, fake=fake)
71
+ subj = subj.format(brand=brand, order=order)
72
+ state = {"from": f"{brand} <no-reply@{sender_dom}>", "to": f"{name.lower()}@example-corp.com", "subject": subj,
73
+ "body": body, "attachments": ([f"file_{order}.{'zip' if cls == 'malware_attachment' else 'pdf'}"] if attach else []),
74
+ "known_brand_domain": legit_dom}
75
+ if distract:
76
+ state["thread_history"] = "Earlier: 'Thanks for the quick turnaround last month!'"
77
+ if kb.target_tokens:
78
+ state["footer"] = pad_to_tokens(ctl, "", kb.target_tokens - estimate_tokens(body))
79
+ noise = False
80
+ truth_phish, truth_cls, truth_urg = is_phish, cls, urg
81
+ if kb.label_noise and noise_draw:
82
+ noise = True
83
+ truth_phish = not truth_phish
84
+ truth_cls = ctl.choice([c[0] for c in CLASSES if c[0] != cls])
85
+ tid = f"phish_{kb.seed}_{i:05d}"
86
+ # decomposition sub-questions (Suite B): each is a noul with rule-derived truth
87
+ sub = {
88
+ "sender_mismatch": ("Does the sender domain differ from the known brand domain?", sender_dom != legit_dom),
89
+ "asks_credentials": ("Does the message ask the recipient to log in, verify, or reset credentials via a link?", cls == "credential_harvest"),
90
+ "urgent_language": ("Does the message pressure the recipient to act within 24 hours or threaten consequences?", urg >= 3),
91
+ "payment_redirect": ("Does the message ask to change payment details or send money or gift cards?", cls in ("invoice_fraud", "ceo_fraud")),
92
+ # v1.0.1: invoice-fraud mails also push an unrequested attachment ("new account in the attached PDF"); the legit
93
+ # "Invoice attached" template was discussed on a call, so it stays false.
94
+ "unexpected_attachment": ("Does the message urge opening an attachment the recipient did not request?", cls in ("malware_attachment", "invoice_fraud")),
95
+ }
96
+ questions = {
97
+ "is_phishing": Question(type="noul", instructions="Is this email a phishing or social-engineering attack?",
98
+ ground_truth=truth_phish, framing_group="phish.is_phishing"),
99
+ "attack_class": Question(type="choice", instructions="Classify the email.", criteria=[Option(key=k, description=d) for k, d in CLASSES],
100
+ ground_truth=truth_cls, framing_group="phish.attack_class"),
101
+ "urgency": Question(type="score", instructions="Rate the time pressure the message applies, 0 (none) to 3 (within 24 hours or threats).",
102
+ criteria=URGENCY, ground_truth=truth_urg, framing_group="phish.urgency"),
103
+ }
104
+ for k, (instr, truth) in sub.items():
105
+ questions[f"sub_{k}"] = Question(type="noul", instructions=instr, ground_truth=truth,
106
+ framing_group=f"phish.sub.{k}", decomposition_of="is_phishing")
107
+ items.append(TaskItem(
108
+ task_id=tid, tier="G", domain="email_security", language=kb.language, state=state,
109
+ state_tokens=estimate_tokens(json.dumps(state)), questions=questions,
110
+ label_provenance=LabelProvenance(source="generator", generator=self.generator_id, seed=kb.seed, noise_injected=noise),
111
+ controls=Controls(distractor_density=kb.distractor_density), cost_matrix=self.cost_matrices(), canary=canary_for(tid),
112
+ metadata={"class": cls, "sender_domain": sender_dom, "decomposition_rule": "phishing if sender_mismatch or any of the other four"},
113
+ ))
114
+ return items
115
+
116
+ def regex_rules(self) -> dict:
117
+ return {
118
+ "phish.is_phishing": [r"verify your", r"suspended", r"expires today", r"gift cards", r"bank details have changed", r"\.zip", r"-secure\.com|-login\.net|-delivery\.co|-payroll\.info|-hr\.org"],
119
+ "phish.attack_class": {"credential_harvest": [r"verify", r"reset now", r"password"], "invoice_fraud": [r"bank details", r"remit"],
120
+ "malware_attachment": [r"\.zip", r"scanned document"], "ceo_fraud": [r"gift cards", r"confidential"],
121
+ "benign": [r"no action is required", r"team lunch", r"timesheet", r"out for delivery"]},
122
+ "phish.urgency": {"3": [r"24 hours|today|suspended"], "2": [r"end of week"], "1": [r"when you get a chance|let me know"], "0": [r"no action"]},
123
+ }
124
+
125
+ def cost_matrices(self) -> dict[str, dict[str, dict[str, float]]]:
126
+ # missing a phish costs 20; false alarm costs 1
127
+ return {"is_phishing": {"true": {"true": 0.0, "false": 20.0}, "false": {"true": 1.0, "false": 0.0}}}
@@ -0,0 +1,90 @@
1
+ """Policy compliance generator: a short policy (3 to 5 numbered clauses) plus a request.
2
+
3
+ compliant (noul) -> no clause is violated
4
+ violated_clause (choice over clause ids plus a required "none") -> the single violated clause, or none
5
+ severity (score 0..2) -> 0 compliant, 1 minor clause, 2 major clause (clause severities stated in the policy)
6
+ Requests are built from the same clause set, so the label is exact and the "none" option is correct for a stated
7
+ fraction of items. Nothing in the request text names the clause.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import json
13
+
14
+ from ..schemas import Controls, LabelProvenance, Level, Option, Question, TaskItem
15
+ from .base import BaseGenerator, canary_for, estimate_tokens, pad_to_tokens, register_generator
16
+
17
+ # clause: (id, text, severity 1|2, violating request templates, compliant request templates)
18
+ CLAUSES = [
19
+ ("C1", "Refunds are only issued within 30 days of purchase.", 2,
20
+ ["I bought this {days} days ago and want a full refund.", "Please refund order #{order}; it was placed {days} days back."],
21
+ ["I bought this {sdays} days ago and want a full refund.", "Order #{order} from {sdays} days ago, please refund."]),
22
+ ("C2", "Account details may only be shared with the verified account holder.", 2,
23
+ ["I'm calling for my husband; can you read me his last four transactions?", "My roommate's account: what email is on file? He asked me to check."],
24
+ ["Can you read me my own last four transactions? I've verified via the security question.", "What email is on my account? I'm the holder and verified."]),
25
+ ("C3", "Discount codes cannot be combined with other promotions.", 1,
26
+ ["Apply code SAVE20 on top of the current summer sale, please.", "Stack my loyalty coupon with the Black Friday price."],
27
+ ["Apply code SAVE20 to my order; no other promotions are on it.", "Use my loyalty coupon on this full-price item."]),
28
+ ("C4", "Shipping address changes are not possible after dispatch.", 1,
29
+ ["My order shipped this morning; change the address to my office.", "Tracking says dispatched; please redirect it to my parents' house."],
30
+ ["My order hasn't shipped yet; change the address to my office.", "Order still processing, please update the delivery address."]),
31
+ ("C5", "Support agents must not provide legal or tax advice.", 2,
32
+ ["Should I declare this refund as income on my tax return?", "Is it legal for my employer to deduct this from my pay?"],
33
+ ["Can you send me the invoice so my accountant can handle the taxes?", "Where do I download my annual statement?"]),
34
+ ]
35
+ SEV = [Level(level=0, description="Compliant: no clause is violated."), Level(level=1, description="Minor violation (a clause marked minor)."),
36
+ Level(level=2, description="Major violation (a clause marked major).")]
37
+
38
+
39
+ @register_generator("policy_compliance")
40
+ class PolicyComplianceGenerator(BaseGenerator):
41
+ version = "1.0.0"
42
+
43
+ def generate(self) -> list[TaskItem]:
44
+ kb = self.knobs
45
+ rng, ctl = self.rng("items"), self.rng("controls")
46
+ none_frac = kb.none_correct_frac if kb.none_correct_frac else 0.3
47
+ items = []
48
+ for i in range(kb.n):
49
+ noise_draw = ctl.random() < kb.label_noise
50
+ k = rng.randrange(3, 6)
51
+ clauses = rng.sample(CLAUSES, k)
52
+ policy = "\n".join(f"{cid}. {txt} [{'major' if sev == 2 else 'minor'}]" for cid, txt, sev, _, _ in clauses)
53
+ compliant = rng.random() < none_frac
54
+ target = rng.choice(clauses)
55
+ tmpl = rng.choice(target[4] if compliant else target[3])
56
+ request = tmpl.format(days=rng.randrange(31, 120), sdays=rng.randrange(1, 29), order=rng.randrange(10000, 99999))
57
+ state = {"policy": policy, "customer_request": request}
58
+ if kb.target_tokens:
59
+ state["history"] = pad_to_tokens(ctl, "", kb.target_tokens)
60
+ violated = "none" if compliant else target[0]
61
+ sev = 0 if compliant else target[2]
62
+ noise = False
63
+ if kb.label_noise and noise_draw:
64
+ noise = True
65
+ compliant = not compliant
66
+ violated = "none" if compliant else target[0]
67
+ sev = 0 if compliant else target[2]
68
+ options = [Option(key=cid, description=txt) for cid, txt, _, _, _ in clauses] + [Option(key="none", description="No clause is violated.")]
69
+ tid = f"policy_{kb.seed}_{i:05d}"
70
+ items.append(TaskItem(
71
+ task_id=tid, tier="G", domain="policy_compliance", language=kb.language, state=state, state_tokens=estimate_tokens(json.dumps(state)),
72
+ questions={
73
+ "compliant": Question(type="noul", instructions="Can the request be fulfilled without violating any clause of `policy`?",
74
+ ground_truth=compliant, framing_group="policy.compliant"),
75
+ "violated_clause": Question(type="choice", instructions="Which clause of `policy` would fulfilling `customer_request` violate? Choose none if it complies.",
76
+ criteria=options, ground_truth=violated, framing_group="policy.violated_clause"),
77
+ "severity": Question(type="score", instructions="Violation severity per the [minor]/[major] tags in `policy`; 0 if compliant.",
78
+ criteria=SEV, ground_truth=sev, framing_group="policy.severity"),
79
+ },
80
+ label_provenance=LabelProvenance(source="generator", generator=self.generator_id, seed=kb.seed, noise_injected=noise),
81
+ controls=Controls(none_correct=compliant), cost_matrix=self.cost_matrices(), canary=canary_for(tid),
82
+ metadata={"target_clause": target[0], "k": k},
83
+ ))
84
+ return items
85
+
86
+ def regex_rules(self) -> dict:
87
+ return {"policy.compliant": [r"(?!)"]}
88
+
89
+ def cost_matrices(self):
90
+ return {"compliant": {"true": {"true": 0.0, "false": 2.0}, "false": {"true": 10.0, "false": 0.0}}}
@@ -0,0 +1,122 @@
1
+ """RAG relevance generator: query plus K candidate passages.
2
+
3
+ Questions:
4
+ best_passage (choice over K passage ids, K up to 255) -> the one passage that answers the query
5
+ is_relevant (noul) -> does passage `focus` answer the query (focus is a random candidate; 50% positives)
6
+ relevance (score 0..3) -> grade of `focus`: 0 unrelated, 1 same topic, 2 partial, 3 answers it
7
+
8
+ Passages are built from a fact table: each fact has a topic, an entity, an attribute and a value. Distractors are
9
+ (a) same topic different entity (hard), (b) same entity different attribute (hard), (c) unrelated (easy). Labels are
10
+ exact by construction, so this is the cardinality sweep generator for Suite C (K in 2..255).
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import json
16
+ import random
17
+
18
+ from ..schemas import Controls, LabelProvenance, Level, Option, Question, TaskItem
19
+ from .base import BaseGenerator, canary_for, estimate_tokens, pad_to_tokens, register_generator
20
+
21
+ TOPICS = {
22
+ "planets": (["Mercury", "Venus", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"],
23
+ {"moons": lambda r: str(r.randrange(0, 90)), "day_length_hours": lambda r: str(r.randrange(10, 5900)),
24
+ "discovery_year": lambda r: str(r.randrange(1600, 1950)), "mean_temp_c": lambda r: str(r.randrange(-220, 470))}),
25
+ "companies": (["Northwind", "Contoso", "Fabrikam", "Tailspin", "Woodgrove", "Litware", "Adatum", "Proseware"],
26
+ {"headquarters": lambda r: r.choice(["Oslo", "Lisbon", "Austin", "Kyoto", "Nairobi", "Toronto"]),
27
+ "founded": lambda r: str(r.randrange(1950, 2022)), "employees": lambda r: str(r.randrange(20, 90000)),
28
+ "ceo": lambda r: r.choice(["A. Okafor", "M. Lindqvist", "R. Tanaka", "S. Mehta", "J. Alvarez"])}),
29
+ "recipes": (["ratatouille", "bibimbap", "moussaka", "feijoada", "pho", "tagine", "paella", "goulash"],
30
+ {"origin_country": lambda r: r.choice(["France", "Korea", "Greece", "Brazil", "Vietnam", "Morocco", "Spain", "Hungary"]),
31
+ "cook_time_minutes": lambda r: str(r.randrange(15, 240)), "main_protein": lambda r: r.choice(["beef", "pork", "chicken", "lamb", "none", "fish"]),
32
+ "servings": lambda r: str(r.randrange(2, 12))}),
33
+ "software": (["Kestrel", "Nimbus", "Quartz", "Vellum", "Marlin", "Orchid", "Tundra", "Zephyr"],
34
+ {"latest_version": lambda r: f"{r.randrange(1, 9)}.{r.randrange(0, 20)}", "license": lambda r: r.choice(["MIT", "Apache-2.0", "GPL-3.0", "BSD-3", "proprietary"]),
35
+ "language": lambda r: r.choice(["Rust", "Go", "Python", "TypeScript", "C++"]), "first_release": lambda r: str(r.randrange(2005, 2026))}),
36
+ }
37
+ TEMPLATES = ["{entity}'s {attr_h} is {value}.", "According to the {topic} handbook, the {attr_h} of {entity} is {value}.",
38
+ "Note: {entity} — {attr_h}: {value}.", "For {entity}, records list the {attr_h} as {value}."]
39
+ QUERY_TEMPLATES = ["What is the {attr_h} of {entity}?", "{entity}: {attr_h}?", "Tell me {entity}'s {attr_h}.", "Look up the {attr_h} for {entity}."]
40
+ REL_LEVELS = [Level(level=0, description="Unrelated to the query's topic or entity."),
41
+ Level(level=1, description="Same topic but about a different entity or attribute; does not answer the query."),
42
+ Level(level=2, description="About the right entity but a different attribute, or the right attribute for a different entity."),
43
+ Level(level=3, description="Directly states the value the query asks for.")]
44
+
45
+
46
+ def _fact(rng: random.Random, topic: str, entity: str, attr: str) -> tuple[str, str]:
47
+ ents, attrs = TOPICS[topic]
48
+ value = attrs[attr](rng)
49
+ text = rng.choice(TEMPLATES).format(entity=entity, attr_h=attr.replace("_", " "), value=value, topic=topic)
50
+ return text, value
51
+
52
+
53
+ @register_generator("rag_relevance")
54
+ class RagRelevanceGenerator(BaseGenerator):
55
+ version = "1.0.0"
56
+
57
+ def generate(self) -> list[TaskItem]:
58
+ kb = self.knobs
59
+ rng = self.rng("items")
60
+ ctl = self.rng("controls")
61
+ K = max(2, min(kb.cardinality or 5, 255))
62
+ items: list[TaskItem] = []
63
+ for i in range(kb.n):
64
+ topic = rng.choice(list(TOPICS))
65
+ ents, attrs = TOPICS[topic]
66
+ entity, attr = rng.choice(ents), rng.choice(list(attrs))
67
+ query = rng.choice(QUERY_TEMPLATES).format(entity=entity, attr_h=attr.replace("_", " "))
68
+ gold_text, _ = _fact(rng, topic, entity, attr)
69
+ passages: list[tuple[str, int]] = [(gold_text, 3)] # (text, relevance grade)
70
+ # hard distractors
71
+ while len(passages) < K:
72
+ kind = rng.random()
73
+ if kind < 0.35: # same entity, other attribute
74
+ other_attr = rng.choice([a for a in attrs if a != attr])
75
+ passages.append((_fact(rng, topic, entity, other_attr)[0], 2))
76
+ elif kind < 0.7: # same attribute, other entity
77
+ other_ent = rng.choice([e for e in ents if e != entity])
78
+ passages.append((_fact(rng, topic, other_ent, attr)[0], 2))
79
+ elif kind < 0.85: # same topic, other entity and attribute
80
+ other_ent = rng.choice([e for e in ents if e != entity])
81
+ other_attr = rng.choice([a for a in attrs if a != attr])
82
+ passages.append((_fact(rng, topic, other_ent, other_attr)[0], 1))
83
+ else: # unrelated topic
84
+ ot = rng.choice([t for t in TOPICS if t != topic])
85
+ oe, oa = rng.choice(TOPICS[ot][0]), rng.choice(list(TOPICS[ot][1]))
86
+ passages.append((_fact(rng, ot, oe, oa)[0], 0))
87
+ rng.shuffle(passages)
88
+ ids = [f"p{j:03d}" for j in range(K)]
89
+ gold_id = ids[[g for _, g in passages].index(3)]
90
+ focus_idx = rng.randrange(K) if rng.random() < 0.5 else ids.index(gold_id)
91
+ focus_id, focus_grade = ids[focus_idx], passages[focus_idx][1]
92
+ state = {"query": query, "passages": {pid: txt for pid, (txt, _) in zip(ids, passages)}, "focus": focus_id}
93
+ noise_draw = ctl.random() < kb.label_noise
94
+ if kb.target_tokens:
95
+ state["notes"] = pad_to_tokens(ctl, "", kb.target_tokens)
96
+ noise = False
97
+ if kb.label_noise and noise_draw:
98
+ noise = True
99
+ gold_id = ctl.choice([p for p in ids if p != gold_id])
100
+ tid = f"rag_{kb.seed}_K{K}_{i:05d}"
101
+ items.append(TaskItem(
102
+ task_id=tid, tier="G", domain="rag_relevance", language=kb.language, state=state,
103
+ state_tokens=estimate_tokens(json.dumps(state)),
104
+ questions={
105
+ "best_passage": Question(type="choice", instructions="Which passage in `passages` directly answers `query`?",
106
+ criteria=[Option(key=pid, description="") for pid in ids], ground_truth=gold_id, framing_group="rag.best_passage"),
107
+ "is_relevant": Question(type="noul", instructions="Does the passage with id `focus` directly answer `query`?",
108
+ ground_truth=(focus_grade == 3), framing_group="rag.is_relevant"),
109
+ "relevance": Question(type="score", instructions="Grade how well the passage with id `focus` answers `query`.",
110
+ criteria=REL_LEVELS, ground_truth=focus_grade, framing_group="rag.relevance"),
111
+ },
112
+ label_provenance=LabelProvenance(source="generator", generator=self.generator_id, seed=kb.seed, noise_injected=noise),
113
+ controls=Controls(distractor_density=kb.distractor_density), cost_matrix=self.cost_matrices(), canary=canary_for(tid),
114
+ metadata={"topic": topic, "entity": entity, "attribute": attr, "K": K, "focus_grade": focus_grade},
115
+ ))
116
+ return items
117
+
118
+ def regex_rules(self) -> dict:
119
+ return {"rag.is_relevant": [r"(?!)"]} # no sensible regex baseline: passages are ids; kept so the rule file exists
120
+
121
+ def cost_matrices(self) -> dict[str, dict[str, dict[str, float]]]:
122
+ return {"is_relevant": {"true": {"true": 0.0, "false": 5.0}, "false": {"true": 1.0, "false": 0.0}}}