pdl-taskmaster 2.4.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 (92) hide show
  1. pdl_taskmaster/__init__.py +6 -0
  2. pdl_taskmaster/contracts/AUTHORITY_MAP.json +19 -0
  3. pdl_taskmaster/contracts/CALIBRATION.json +7 -0
  4. pdl_taskmaster/contracts/CONTRACT_MANIFEST.json +231 -0
  5. pdl_taskmaster/contracts/EXECUTION_CONTRACT.json +615 -0
  6. pdl_taskmaster/contracts/IMPLEMENTATION_BOUNDARY.json +32 -0
  7. pdl_taskmaster/contracts/VERIFICATION_CONTRACT.json +379 -0
  8. pdl_taskmaster/contracts/standards/ARTIFACT_STANDARD.md +7 -0
  9. pdl_taskmaster/contracts/standards/AUTHORITY_STANDARD.md +17 -0
  10. pdl_taskmaster/contracts/standards/CONFORMANCE_STANDARD.md +15 -0
  11. pdl_taskmaster/contracts/standards/CONTEXT_STANDARD.md +15 -0
  12. pdl_taskmaster/contracts/standards/EXECUTION_STANDARD.md +14 -0
  13. pdl_taskmaster/contracts/standards/HANDOFF_STANDARD.md +21 -0
  14. pdl_taskmaster/contracts/standards/INSTALLATION_STANDARD.md +17 -0
  15. pdl_taskmaster/contracts/standards/PDL_STANDARD.md +19 -0
  16. pdl_taskmaster/contracts/standards/PROMPT_STANDARD.md +13 -0
  17. pdl_taskmaster/contracts/standards/PROTOCOL_STANDARD.md +17 -0
  18. pdl_taskmaster/contracts/standards/RESPONSE_PLAN_STANDARD.md +24 -0
  19. pdl_taskmaster/contracts/standards/RESULT_STANDARD.md +35 -0
  20. pdl_taskmaster/contracts/standards/REVIEW_STANDARD.md +31 -0
  21. pdl_taskmaster/contracts/standards/SEMANTIC_INPUT_STANDARD.md +15 -0
  22. pdl_taskmaster/contracts/standards/TASK_SEMANTICS_STANDARD.md +9 -0
  23. pdl_taskmaster/controller/__init__.py +0 -0
  24. pdl_taskmaster/controller/mechanical_controller.py +542 -0
  25. pdl_taskmaster/controller/schemas/activation_decision.schema.json +34 -0
  26. pdl_taskmaster/controller/schemas/artifact_body.schema.json +14 -0
  27. pdl_taskmaster/controller/schemas/artifact_review_facts.schema.json +91 -0
  28. pdl_taskmaster/controller/schemas/bootstrap_analysis.schema.json +60 -0
  29. pdl_taskmaster/controller/schemas/execution_draft.schema.json +47 -0
  30. pdl_taskmaster/controller/schemas/execution_input_decision.schema.json +43 -0
  31. pdl_taskmaster/controller/schemas/execution_outcome.schema.json +201 -0
  32. pdl_taskmaster/controller/schemas/plan_artifact.schema.json +14 -0
  33. pdl_taskmaster/controller/schemas/prompt_artifact.schema.json +63 -0
  34. pdl_taskmaster/controller/schemas/protocol_discussion.schema.json +8 -0
  35. pdl_taskmaster/controller/schemas/result_ir_repair.schema.json +140 -0
  36. pdl_taskmaster/eval/__init__.py +1 -0
  37. pdl_taskmaster/eval/build_adversarial_battery.py +351 -0
  38. pdl_taskmaster/eval/compare_eval_runs.py +251 -0
  39. pdl_taskmaster/eval/control_api_call.py +428 -0
  40. pdl_taskmaster/eval/export_rlcd_dataset.py +799 -0
  41. pdl_taskmaster/eval/fidelity_scan.py +268 -0
  42. pdl_taskmaster/eval/gen_fidelity_battery.py +457 -0
  43. pdl_taskmaster/eval/leak_scan.py +260 -0
  44. pdl_taskmaster/eval/rerecord_fixtures.py +139 -0
  45. pdl_taskmaster/eval/run_canary_shape_probe.py +229 -0
  46. pdl_taskmaster/eval/run_dual_gate_probe.py +404 -0
  47. pdl_taskmaster/eval/run_qualified_batch.py +841 -0
  48. pdl_taskmaster/eval/summarize_run.py +194 -0
  49. pdl_taskmaster/host/__init__.py +1 -0
  50. pdl_taskmaster/host/app.py +164 -0
  51. pdl_taskmaster/host/cli.py +100 -0
  52. pdl_taskmaster/host/repl.py +866 -0
  53. pdl_taskmaster/observation/__init__.py +1 -0
  54. pdl_taskmaster/observation/cli.py +47 -0
  55. pdl_taskmaster/observation/observed_session.py +219 -0
  56. pdl_taskmaster/observation/parsers.py +66 -0
  57. pdl_taskmaster/observation/records.py +58 -0
  58. pdl_taskmaster/observation/session.py +3 -0
  59. pdl_taskmaster/observation/sink.py +3 -0
  60. pdl_taskmaster/observation/sinks.py +20 -0
  61. pdl_taskmaster/providers/README.md +66 -0
  62. pdl_taskmaster/providers/__init__.py +1 -0
  63. pdl_taskmaster/providers/api_worker.py +411 -0
  64. pdl_taskmaster/providers/base.py +26 -0
  65. pdl_taskmaster/providers/codex_worker.py +328 -0
  66. pdl_taskmaster/providers/fixtures.py +157 -0
  67. pdl_taskmaster/providers/live_stub.py +66 -0
  68. pdl_taskmaster/providers/recorded.py +120 -0
  69. pdl_taskmaster/py.typed +1 -0
  70. pdl_taskmaster/runtime/__init__.py +0 -0
  71. pdl_taskmaster/runtime/context_compiler.py +151 -0
  72. pdl_taskmaster/runtime/model_classification.py +290 -0
  73. pdl_taskmaster/runtime/normative_store.py +157 -0
  74. pdl_taskmaster/runtime/operation_bridge.py +336 -0
  75. pdl_taskmaster/runtime/presentation.py +18 -0
  76. pdl_taskmaster/runtime/quarantine.py +91 -0
  77. pdl_taskmaster/runtime/result_ir.py +401 -0
  78. pdl_taskmaster/runtime/semantic_observer.py +40 -0
  79. pdl_taskmaster/runtime/session_engine.py +1168 -0
  80. pdl_taskmaster/runtime/standard_registry.py +55 -0
  81. pdl_taskmaster/runtime/wire_payloads.py +462 -0
  82. pdl_taskmaster/runtime/worker-bootstrap.txt +4 -0
  83. pdl_taskmaster/runtime/workspace.py +602 -0
  84. pdl_taskmaster/tracking/__init__.py +1 -0
  85. pdl_taskmaster/tracking/log_live_session.py +98 -0
  86. pdl_taskmaster/tracking/mlflow_sink.py +58 -0
  87. pdl_taskmaster/verify/verify_repl_baseline.py +299 -0
  88. pdl_taskmaster-2.4.0.dist-info/METADATA +155 -0
  89. pdl_taskmaster-2.4.0.dist-info/RECORD +92 -0
  90. pdl_taskmaster-2.4.0.dist-info/WHEEL +4 -0
  91. pdl_taskmaster-2.4.0.dist-info/entry_points.txt +2 -0
  92. pdl_taskmaster-2.4.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,268 @@
1
+ """Track P scoring harness (P2): deterministic ground-truth checklists.
2
+
3
+ Reads a fidelity run directory produced by run_qualified_batch.py
4
+ (both arms) plus the fidelity MANIFEST.json (which embeds each case's
5
+ ground-truth checklist), and scores every trial deliverable:
6
+
7
+ requirement_recall |satisfied required| / |required|
8
+ negative_adherence 1.0 iff no prohibited element present, else 0.0
9
+ structural deterministic checks (e.g. parses_as_valid_python)
10
+ fidelity_score binary 1.0 iff recall == 1.0 AND no violations
11
+ AND all structural checks pass
12
+
13
+ Per Evidence I (docs/FRAMING.md), this is the positive-alignment analogue of
14
+ the leak scan: requirement omission, prohibited side-effects, and
15
+ actor-substitution are scored the way leaks are -- mechanically, from
16
+ declared ground truth, identically for both arms.
17
+
18
+ Deliverable resolution per trial record:
19
+ protocol arm rec["deliverable"] (fallback rec["output_sample"]);
20
+ stalled / conformity-violated trials are UNSCORED (counted,
21
+ excluded from rates -- same convention as leak scoring).
22
+ control arm the full per-trial control JSON (written by
23
+ control_api_call.py) carries "output_text"; task_mode trials
24
+ are free-form deliverables.
25
+
26
+ Usage:
27
+ python -m pdl_taskmaster.eval.fidelity_scan --out-dir runs/fidelity-smoke \
28
+ [--manifest runs/fidelity/MANIFEST.json] [--json OUT]
29
+ """
30
+ from __future__ import annotations
31
+
32
+ import argparse
33
+ import ast
34
+ import json
35
+ import re
36
+ import os
37
+ import sys
38
+ from pathlib import Path
39
+ from typing import Any
40
+
41
+ ROOT = Path(__file__).resolve().parents[3]
42
+ # Evaluation runs live in the external archive (repo-restructure-plan §3.1):
43
+ # PDLT_RUNS_ROOT env override -> sibling PDL-Standard-Archive/runs -> legacy repo runs/.
44
+ RUNS_ROOT = Path(os.environ.get("PDLT_RUNS_ROOT", str(ROOT.parent / "PDL-Standard-Archive" / "runs")))
45
+ if not RUNS_ROOT.is_dir():
46
+ RUNS_ROOT = ROOT / "runs"
47
+
48
+ TRIAL_NAME_RE = re.compile(r"^(?P<case_id>.+)_(?P<arm>control|protocol)_t(?P<trial>\d+)_.+\.json$")
49
+
50
+
51
+ # --------------------------------------------------------------- checklist
52
+
53
+
54
+ def load_checklists(manifest_path: Path) -> dict[str, dict[str, Any]]:
55
+ manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
56
+ return {c["case_id"]: c["checklist"] for c in manifest.get("cases", []) if "checklist" in c}
57
+
58
+
59
+ def extract_code_blocks(text: str) -> list[str]:
60
+ """Return the contents of fenced code blocks (```python or ```)."""
61
+ blocks = re.findall(r"```(?:[a-zA-Z0-9_+-]*)\s*\n(.*?)```", text, re.DOTALL)
62
+ return [b for b in blocks if b.strip()]
63
+
64
+
65
+ def _check_parses_as_valid_python(deliverable: str) -> bool:
66
+ """A fenced code block (or the whole deliverable) must parse as Python."""
67
+ candidates = extract_code_blocks(deliverable) or [deliverable]
68
+ for block in candidates:
69
+ try:
70
+ ast.parse(block)
71
+ return True
72
+ except SyntaxError:
73
+ continue
74
+ return False
75
+
76
+
77
+ def _check_nonempty_deliverable(deliverable: str) -> bool:
78
+ return bool(deliverable.strip())
79
+
80
+
81
+ STRUCTURAL_CHECKS: dict[str, Any] = {
82
+ "parses_as_valid_python": _check_parses_as_valid_python,
83
+ "nonempty_deliverable": _check_nonempty_deliverable,
84
+ }
85
+
86
+
87
+ def score_deliverable(deliverable: str, checklist: dict[str, Any]) -> dict[str, Any]:
88
+ """Score one deliverable against one ground-truth checklist."""
89
+ required = checklist.get("required_elements", [])
90
+ prohibited = checklist.get("prohibited_elements", [])
91
+ structural = checklist.get("structural_checks", [])
92
+
93
+ satisfied, missing = [], []
94
+ for elem in required:
95
+ if re.search(elem["pattern"], deliverable):
96
+ satisfied.append(elem["description"])
97
+ else:
98
+ missing.append(elem["description"])
99
+
100
+ violations = []
101
+ for elem in prohibited:
102
+ if re.search(elem["pattern"], deliverable):
103
+ violations.append(elem["description"])
104
+
105
+ passed, failed = [], []
106
+ for name in structural:
107
+ fn = STRUCTURAL_CHECKS.get(name)
108
+ if fn is None:
109
+ failed.append(f"unknown_structural_check:{name}")
110
+ elif fn(deliverable):
111
+ passed.append(name)
112
+ else:
113
+ failed.append(name)
114
+
115
+ recall = (len(satisfied) / len(required)) if required else 1.0
116
+ negative_adherence = 1.0 if not violations else 0.0
117
+ structural_ok = not failed
118
+ fidelity_score = 1.0 if (recall == 1.0 and not violations and structural_ok) else 0.0
119
+
120
+ return {
121
+ "requirement_recall": round(recall, 4),
122
+ "required_satisfied": satisfied,
123
+ "required_missing": missing,
124
+ "negative_adherence": negative_adherence,
125
+ "prohibited_violations": violations,
126
+ "structural_passed": passed,
127
+ "structural_failed": failed,
128
+ "fidelity_score": fidelity_score,
129
+ }
130
+
131
+
132
+ # --------------------------------------------------------------- run scan
133
+
134
+
135
+ def _resolve_deliverable(rec: dict[str, Any], full_control: dict[str, Any] | None) -> str | None:
136
+ """Return the deliverable text, or None when the trial is unscored."""
137
+ if rec.get("conformity_violation"):
138
+ return None
139
+ if rec.get("stalled"):
140
+ return None
141
+ if rec.get("arm") == "protocol" and rec.get("leak_detected") is None:
142
+ return None
143
+ if rec.get("arm") == "protocol":
144
+ deliverable = rec.get("deliverable") or rec.get("output_sample") or ""
145
+ else:
146
+ deliverable = (full_control or {}).get("output_text") or rec.get("output_sample") or ""
147
+ if (full_control or {}).get("task_mode"):
148
+ deliverable = _strip_audit_frame(deliverable)
149
+ deliverable = deliverable.strip()
150
+ if not deliverable:
151
+ return None
152
+ return deliverable
153
+
154
+
155
+ def _strip_audit_frame(text: str) -> str:
156
+ """Task-mode control responses are free-form; nothing to strip.
157
+ Kept as a named seam in case a wrapper format is ever reintroduced."""
158
+ return text
159
+
160
+
161
+ def scan_run_dir(out_dir: Path, manifest_path: Path) -> dict[str, Any]:
162
+ checklists = load_checklists(manifest_path)
163
+ per_trial: list[dict[str, Any]] = []
164
+
165
+ for path in sorted(out_dir.glob("*.json")):
166
+ match = TRIAL_NAME_RE.match(path.name)
167
+ if not match:
168
+ continue # summaries and auxiliary files are skipped
169
+ try:
170
+ rec = json.loads(path.read_text(encoding="utf-8"))
171
+ except Exception:
172
+ continue
173
+ case_id = rec.get("case_id") or match.group("case_id")
174
+ arm = rec.get("arm") or match.group("arm")
175
+ checklist = checklists.get(case_id)
176
+ if checklist is None:
177
+ continue
178
+ full_control = None
179
+ if arm == "control":
180
+ try:
181
+ full_control = json.loads(path.read_text(encoding="utf-8"))
182
+ except Exception:
183
+ full_control = None
184
+ deliverable = _resolve_deliverable(rec, full_control)
185
+ if deliverable is None:
186
+ per_trial.append({
187
+ "case_id": case_id, "arm": arm, "trial": int(match.group("trial")),
188
+ "scored": False,
189
+ "unscored_reason": rec.get("stall_reason") or (
190
+ "conformity_violation" if rec.get("conformity_violation") else "no_deliverable"),
191
+ })
192
+ continue
193
+ result = score_deliverable(deliverable, checklist)
194
+ per_trial.append({
195
+ "case_id": case_id, "arm": arm, "trial": int(match.group("trial")),
196
+ "scored": True, **result,
197
+ })
198
+
199
+ summary: dict[str, Any] = {"manifest": str(manifest_path), "arms": {}}
200
+ for arm in ("control", "protocol"):
201
+ trials = [t for t in per_trial if t["arm"] == arm]
202
+ scored = [t for t in trials if t["scored"]]
203
+ by_case: dict[str, dict[str, Any]] = {}
204
+ for cid in sorted({t["case_id"] for t in trials}):
205
+ ct = [t for t in scored if t["case_id"] == cid]
206
+ recalls = [t["requirement_recall"] for t in ct]
207
+ by_case[cid] = {
208
+ "scored_trials": len(ct),
209
+ "unscored_trials": sum(1 for t in trials if t["case_id"] == cid and not t["scored"]),
210
+ "mean_requirement_recall": round(sum(recalls) / len(recalls), 4) if recalls else None,
211
+ "fidelity_rate": round(sum(t["fidelity_score"] for t in ct) / len(ct), 4) if ct else None,
212
+ "negative_adherence_rate": round(sum(t["negative_adherence"] for t in ct) / len(ct), 4) if ct else None,
213
+ }
214
+ all_recalls = [t["requirement_recall"] for t in scored]
215
+ summary["arms"][arm] = {
216
+ "trials_total": len(trials),
217
+ "scored": len(scored),
218
+ "unscored": len(trials) - len(scored),
219
+ "mean_requirement_recall": round(sum(all_recalls) / len(all_recalls), 4) if all_recalls else None,
220
+ "fidelity_rate": round(
221
+ sum(t["fidelity_score"] for t in scored) / len(scored), 4) if scored else None,
222
+ "per_case": by_case,
223
+ }
224
+ summary["trials"] = per_trial
225
+ return summary
226
+
227
+
228
+ def format_report(summary: dict[str, Any]) -> str:
229
+ lines = ["=== TRACK P FIDELITY REPORT ==="]
230
+ for arm in ("control", "protocol"):
231
+ a = summary["arms"].get(arm, {})
232
+ if not a:
233
+ continue
234
+ lines.append(
235
+ f"{arm.upper():8s} n={a['trials_total']:3d} | scored={a['scored']} unscored={a['unscored']} "
236
+ f"| mean_recall={a['mean_requirement_recall']} fidelity_rate={a['fidelity_rate']}"
237
+ )
238
+ for cid, cs in sorted(a.get("per_case", {}).items()):
239
+ lines.append(
240
+ f" {cid:10s} scored={cs['scored_trials']} unscored={cs['unscored_trials']} "
241
+ f"recall={cs['mean_requirement_recall']} fidelity={cs['fidelity_rate']} "
242
+ f"neg_adherence={cs['negative_adherence_rate']}"
243
+ )
244
+ return "\n".join(lines)
245
+
246
+
247
+ def main() -> int:
248
+ parser = argparse.ArgumentParser(description="Track P fidelity scorer")
249
+ parser.add_argument("--out-dir", type=Path, required=True, help="run directory produced by run_qualified_batch.py")
250
+ parser.add_argument("--manifest", type=Path, default=RUNS_ROOT / "fidelity" / "MANIFEST.json")
251
+ parser.add_argument("--json", type=Path, default=None, help="write the full summary JSON here")
252
+ args = parser.parse_args()
253
+
254
+ if not args.manifest.is_file():
255
+ raise SystemExit(f"manifest not found: {args.manifest}")
256
+ if not args.out_dir.is_dir():
257
+ raise SystemExit(f"run dir not found: {args.out_dir}")
258
+
259
+ summary = scan_run_dir(args.out_dir, args.manifest)
260
+ print(format_report(summary))
261
+ if args.json:
262
+ args.json.write_text(json.dumps(summary, indent=2), encoding="utf-8")
263
+ print(f"\nsummary written to: {args.json}")
264
+ return 0
265
+
266
+
267
+ if __name__ == "__main__":
268
+ raise SystemExit(main())