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,401 @@
1
+ """Result Pseudocode decomposition IR (ADR-0009 prototype).
2
+
3
+ Structured result artifact emitted by the EXECUTE operation alongside the
4
+ native deliverable: the result is decomposed per file, reconciled against
5
+ the mechanically derived requirement IDs of the confirmed Prompt Pseudocode,
6
+ and grounded in execution-evidence citations that the controller validates
7
+ mechanically. Model-asserted content is never trusted:
8
+
9
+ - every cited path MUST resolve inside the workspace;
10
+ - every cited section marker MUST appear verbatim in the cited artifact;
11
+ - every quoted observation MUST appear verbatim in the cited artifact;
12
+ - every requirement ID derived from the confirmed prompt body MUST be
13
+ reconciled exactly once.
14
+
15
+ Zero external dependencies (stdlib only), per the harness constraint.
16
+ """
17
+ from __future__ import annotations
18
+
19
+ import hashlib
20
+ import json
21
+ import re
22
+ from pathlib import Path
23
+
24
+ # Commitment verbs: prompt-body lines opening with one of these are
25
+ # mechanically promoted to requirement IDs (R1..Rn, in order).
26
+ _COMMITMENT_VERB = re.compile(
27
+ r"^(?:IMPLEMENT|DEFINE|GENERATE|DELIVER|ENSURE|PRESERVE|REMOVE|UPDATE|"
28
+ r"MAINTAIN|FIX|SCAN|ALIGN|ELIMINATE|COMPARE|APPLY|MODIFY|LOCATE|CREATE|"
29
+ r"VERIFY|REWRITE|REBUILD|PRESENT|ADD)\b"
30
+ )
31
+
32
+ _VALID_STATUS = {"satisfied", "partial", "open"}
33
+
34
+ RESULT_IR_INSTRUCTIONS_TEMPLATE = """The response MUST end with a fenced ```json block containing the Result IR object, exactly this shape:
35
+ {{"files": [{{"filename": "<name>.py", "satisfies": ["R<n>", ...], "evidence": {{"path": "<workspace-relative path of the artifact this file's content is grounded in>", "section": "<verbatim section marker inside that artifact, or omitted>"}}}}], "reconciliation": [{{"requirement": "R<n>", "status": "satisfied|partial|open", "evidence": {{"path": "...", "section": "...", "observed": "<verbatim quote from the cited artifact supporting this status>"}}}}, ...], "open_defects": [{{"id": "D<n>", "description": "<defect>", "evidence": {{"path": "...", "observed": "<verbatim quote>"}}}}]}}
36
+ Rules: cite ONLY the artifacts listed under AVAILABLE EVIDENCE PATHS below; every "observed" string MUST be copied verbatim from the cited artifact; every requirement ID MUST appear in "reconciliation" exactly once; do not invent paths, sections, quotes, or requirement IDs."""
37
+
38
+ _EVIDENCE_KEYS = ("path", "section", "observed")
39
+
40
+
41
+ def derive_requirements(prompt_body: str) -> list[str]:
42
+ """Mechanically derive requirement IDs (R1..Rn) from the confirmed prompt."""
43
+ reqs: list[str] = []
44
+ for line in (prompt_body or "").splitlines():
45
+ s = line.strip()
46
+ if s and _COMMITMENT_VERB.match(s):
47
+ reqs.append(s)
48
+ return reqs
49
+
50
+
51
+ def render_instructions(
52
+ requirements: list[str],
53
+ repo_root: str | Path | None = None,
54
+ evidence_paths: list[str] | None = None,
55
+ ) -> str:
56
+ numbered = "\n".join(f"R{i}: {r}" for i, r in enumerate(requirements, 1))
57
+ paths = "\n".join(f"- {p}" for p in (evidence_paths or ["execution://body"]))
58
+ return (
59
+ "CONFIRMED REQUIREMENTS (mechanically derived; reconcile EVERY ID):\n"
60
+ + (numbered or "R1: (no commitment lines detected)")
61
+ + "\n\n"
62
+ + load_standard_instructions(repo_root).replace(
63
+ "{evidence_paths}", paths or "- execution://body"
64
+ )
65
+ )
66
+
67
+
68
+ def load_standard_instructions(repo_root: str | Path | None = None) -> str:
69
+ """ADR-0009: the controller imports the Result IR standard at the EXECUTE
70
+ stage and renders its instruction block verbatim. Single source of truth
71
+ is contracts/standards/RESULT_STANDARD.md."""
72
+ root = Path(repo_root) if repo_root else Path(__file__).resolve().parents[3]
73
+ standard = root / "contracts" / "standards" / "RESULT_STANDARD.md"
74
+ text = standard.read_text(encoding="utf-8")
75
+ m = re.search(
76
+ r"<!-- RESULT-IR:INSTRUCTIONS.*?-->\s*\n(.*?)\n?<!-- /RESULT-IR:INSTRUCTIONS -->",
77
+ text,
78
+ re.S,
79
+ )
80
+ if not m:
81
+ raise ValueError("result_standard_instructions_missing")
82
+ return m.group(1).strip()
83
+
84
+
85
+ def extract_result_ir(body: str) -> dict | None:
86
+ """Extract the Result IR from an execution body: the LAST fenced ```json
87
+ block, or — as models sometimes emit it unfenced — a trailing raw JSON
88
+ object starting with {\"files\"."""
89
+ body = body or ""
90
+ blocks = re.findall(r"```json\s*\n(.*?)```", body, re.S)
91
+ for candidate in reversed(blocks):
92
+ try:
93
+ obj = json.loads(candidate)
94
+ except json.JSONDecodeError:
95
+ continue
96
+ if isinstance(obj, dict):
97
+ return obj
98
+ idx = body.rfind('{"files"')
99
+ if idx >= 0:
100
+ try:
101
+ obj, _ = json.JSONDecoder().raw_decode(body[idx:])
102
+ except json.JSONDecodeError:
103
+ return None
104
+ if isinstance(obj, dict):
105
+ return obj
106
+ return None
107
+
108
+
109
+ def _resolve_evidence(
110
+ ev: object,
111
+ workspace_path: Path,
112
+ errors: list[str],
113
+ where: str,
114
+ execution_body: str | None = None,
115
+ ) -> None:
116
+ if not isinstance(ev, dict) or "path" not in ev:
117
+ errors.append(f"{where}: evidence object with 'path' required")
118
+ return
119
+ raw = str(ev["path"]).replace("\\", "/")
120
+ if raw == "execution://body":
121
+ # Reserved self-reference (RS-04): resolves to the current execution
122
+ # body — the just-produced deliverable, not yet on disk.
123
+ if execution_body is None:
124
+ errors.append(f"{where}: execution://body cited but no execution body available")
125
+ return
126
+ content = execution_body
127
+ else:
128
+ candidate = (workspace_path / raw).resolve()
129
+ try:
130
+ candidate.relative_to(workspace_path.resolve())
131
+ except ValueError:
132
+ errors.append(f"{where}: evidence path escapes workspace: {raw}")
133
+ return
134
+ if not candidate.is_file():
135
+ errors.append(f"{where}: evidence path does not resolve: {raw}")
136
+ return
137
+ content = candidate.read_text(encoding="utf-8", errors="replace")
138
+ section = ev.get("section")
139
+ if section and str(section) not in content:
140
+ errors.append(f"{where}: cited section marker not found in artifact: {section!r}")
141
+ observed = ev.get("observed")
142
+ if observed and str(observed).strip() not in content:
143
+ errors.append(
144
+ f"{where}: cited observation is not a verbatim substring of the artifact: {str(observed)[:80]!r}"
145
+ )
146
+
147
+
148
+ def validate_result_ir(
149
+ ir: object,
150
+ workspace_path: str | Path,
151
+ requirements: list[str],
152
+ execution_body: str | None = None,
153
+ ) -> tuple[list[str], dict]:
154
+ """Mechanically validate a Result IR against the workspace filesystem.
155
+
156
+ Returns (errors, normalized_ir). Empty errors means every structural,
157
+ coverage, and evidence-citation constraint held.
158
+ """
159
+ errors: list[str] = []
160
+ ws = Path(workspace_path).resolve()
161
+ if not isinstance(ir, dict):
162
+ return ["Result IR must be a JSON object"], {}
163
+
164
+ files = ir.get("files")
165
+ recon = ir.get("reconciliation")
166
+ defects = ir.get("open_defects", [])
167
+ if not isinstance(files, list) or not files:
168
+ errors.append("'files' must be a non-empty array")
169
+ if not isinstance(recon, list) or not recon:
170
+ errors.append("'reconciliation' must be a non-empty array")
171
+ if not isinstance(defects, list):
172
+ errors.append("'open_defects' must be an array when present")
173
+
174
+ # Requirement coverage: every derived ID reconciled exactly once.
175
+ seen: dict[str, int] = {}
176
+ if isinstance(recon, list):
177
+ for i, entry in enumerate(recon, 1):
178
+ if not isinstance(entry, dict):
179
+ errors.append(f"reconciliation[{i}]: must be an object")
180
+ continue
181
+ rid = str(entry.get("requirement", "")).strip()
182
+ seen[rid] = seen.get(rid, 0) + 1
183
+ status = str(entry.get("status", "")).strip().lower()
184
+ if status not in _VALID_STATUS:
185
+ errors.append(f"reconciliation[{i}] ({rid}): invalid status {status!r}")
186
+ if rid not in {f"R{j}" for j in range(1, len(requirements) + 1)}:
187
+ errors.append(f"reconciliation[{i}]: unknown requirement ID {rid!r}")
188
+ _resolve_evidence(entry.get("evidence"), ws, errors, f"reconciliation[{i}] ({rid})", execution_body)
189
+ total = {f"R{j}" for j in range(1, len(requirements) + 1)}
190
+ for rid in sorted(total - set(seen)):
191
+ errors.append(f"requirement {rid} is not reconciled")
192
+ for rid, n in sorted(seen.items()):
193
+ if n > 1 and rid in total:
194
+ errors.append(f"requirement {rid} reconciled {n} times (expected exactly once)")
195
+
196
+ if isinstance(files, list):
197
+ for i, f in enumerate(files, 1):
198
+ if not isinstance(f, dict) or not str(f.get("filename", "")).strip():
199
+ errors.append(f"files[{i}]: 'filename' required")
200
+ continue
201
+ _resolve_evidence(
202
+ f.get("evidence"), ws, errors, f"files[{i}] ({f.get('filename')})", execution_body
203
+ )
204
+
205
+ if isinstance(defects, list):
206
+ for i, d in enumerate(defects, 1):
207
+ if not isinstance(d, dict) or not str(d.get("description", "")).strip():
208
+ errors.append(f"open_defects[{i}]: 'description' required")
209
+ continue
210
+ _resolve_evidence(d.get("evidence"), ws, errors, f"open_defects[{i}]", execution_body)
211
+
212
+ return errors, (ir if not errors else {})
213
+
214
+
215
+ def render_prior_ir_section(ir: object) -> str:
216
+ """Render a validated prior Result IR for injection into the next epoch."""
217
+ try:
218
+ body = json.dumps(ir, indent=2, ensure_ascii=False)
219
+ except (TypeError, ValueError):
220
+ return ""
221
+ return (
222
+ "\n\n## PRIOR RESULT IR (mechanically validated by the host; "
223
+ "reconciliation statuses and open defects are authoritative)\n"
224
+ + body
225
+ )
226
+
227
+
228
+ def render_execution_brief(ir: object | None, requirements: list[str]) -> str:
229
+ """Host-side execution brief (ADR-0009 experiment): entity-dense steering
230
+ assembled mechanically from the prior validated IR — open defects as the
231
+ authoritative work list, delivery discipline to suppress cross-file churn.
232
+ Mirrors the entity-extraction purpose of DRAFT_PROMPT at the execute
233
+ boundary without an additional worker call."""
234
+ lines = ["\n## EXECUTION BRIEF (host-assembled; authoritative)"]
235
+ defects = (ir or {}).get("open_defects") or []
236
+ open_reqs = [
237
+ r for r in ((ir or {}).get("reconciliation") or [])
238
+ if isinstance(r, dict) and r.get("status") in {"partial", "open"}
239
+ ]
240
+ if defects:
241
+ lines.append("OPEN DEFECTS (fix these and only these):")
242
+ for d in defects:
243
+ lines.append(f"- {d.get('id')}: {d.get('description')}")
244
+ elif open_reqs:
245
+ lines.append("UNRESOLVED REQUIREMENTS:")
246
+ for r in open_reqs:
247
+ lines.append(f"- {r.get('requirement')}: {r.get('status')}")
248
+ else:
249
+ lines.append("NO OPEN DEFECTS recorded in the prior Result IR; if the task is a fix task, "
250
+ "the defect description is in the user message — fix exactly that.")
251
+ lines.append(
252
+ "DELIVERY DISCIPLINE: change only what the listed defects require; every file "
253
+ "section MUST contain only code (no prose, no requirement echoes, no fence markers "
254
+ "inside sections); reproduce all other files byte-identical; never weaken or "
255
+ "hardcode tests to force a pass."
256
+ )
257
+ if requirements:
258
+ lines.append("REQUIREMENT LEDGER: " + "; ".join(
259
+ f"R{i}: {r.splitlines()[0][:80]}" for i, r in enumerate(requirements, 1)
260
+ ))
261
+ return "\n".join(lines)
262
+
263
+
264
+ def load_ir_from_deliverable(deliverable_text: str | None) -> dict | None:
265
+ """Recover the embedded Result IR from a prior deliverable (restore/chaining)."""
266
+ if not deliverable_text:
267
+ return None
268
+ ir = extract_result_ir(deliverable_text)
269
+ return ir if isinstance(ir, dict) and ir.get("reconciliation") else None
270
+
271
+
272
+ def artifact_sha256(path: str | Path) -> str:
273
+ return hashlib.sha256(Path(path).read_bytes()).hexdigest()
274
+
275
+
276
+ def _norm(text: str) -> str:
277
+ """Whitespace-normalized form for verbatim entity matching (content, not
278
+ layout, is the 4B check)."""
279
+ return " ".join((text or "").split())
280
+
281
+
282
+ # ---------------------------------------------------------------------------
283
+ # Typed entity pipeline (ADR-0009 / TRD-0003 RS-02 extension): entities are
284
+ # parsed and classified at DRAFT_EXECUTE time; the host applies
285
+ # kind-appropriate mechanical checks instead of heuristic string filters.
286
+
287
+ _ENTITY_KINDS = {
288
+ "delivery_marker": {"brief": "line", "deliverable": "line"},
289
+ "api_signature": {"brief": "normalized", "deliverable": "normalized"},
290
+ "constant": {"brief": "normalized", "deliverable": "normalized"},
291
+ "wire_format": {"brief": "normalized", "deliverable": "normalized"},
292
+ "threshold": {"brief": "normalized", "deliverable": "normalized"},
293
+ "meta": {"brief": "normalized", "deliverable": None},
294
+ }
295
+
296
+
297
+ def parse_typed_entities(raw_entities: list) -> list[dict]:
298
+ """Normalize raw entity entries (dicts per schema, or legacy plain strings)
299
+ into typed records. Untyped strings become kind 'meta' (brief-only) so
300
+ legacy emission degrades gracefully instead of triggering false retries."""
301
+ typed = []
302
+ for e in raw_entities or []:
303
+ if isinstance(e, dict) and str(e.get("value", "")).strip():
304
+ kind = str(e.get("kind", "meta")).strip()
305
+ if kind not in _ENTITY_KINDS:
306
+ kind = "meta"
307
+ typed.append({
308
+ "kind": kind,
309
+ "value": str(e["value"]),
310
+ "name": e.get("name"),
311
+ "struct_format": e.get("struct_format"),
312
+ "declared_size": e.get("declared_size"),
313
+ })
314
+ elif isinstance(e, str) and e.strip():
315
+ typed.append({"kind": "meta", "value": e.strip(),
316
+ "name": None, "struct_format": None, "declared_size": None})
317
+ return typed
318
+
319
+
320
+ def arithmetic_checks(typed_entities: list[dict]) -> list[str]:
321
+ """Mechanical arithmetic validation over typed entities (host-side, stdlib).
322
+
323
+ - wire_format: struct.calcsize(struct_format) MUST equal declared_size
324
+ (catches native-alignment vs declared-size drift, e.g. HEADER_SIZE=16
325
+ with a 20-byte '4s Q I' native layout).
326
+ - constant with name MAGIC-like byte literal: the literal length MUST fit
327
+ the wire_format's leading string-field width when both are declared
328
+ (catches b'WAL01' truncated by a '4s' field).
329
+ """
330
+ import struct as _struct
331
+ errors: list[str] = []
332
+ wire = [e for e in typed_entities if e["kind"] == "wire_format"]
333
+ for e in wire:
334
+ fmt, declared = e.get("struct_format"), e.get("declared_size")
335
+ if not fmt or declared is None:
336
+ continue
337
+ try:
338
+ actual = _struct.calcsize(fmt)
339
+ except struct.error as exc:
340
+ errors.append(f"wire_format {fmt!r}: invalid struct format ({exc})")
341
+ continue
342
+ if actual != declared:
343
+ errors.append(
344
+ f"wire_format arithmetic mismatch: calcsize({fmt!r}) = {actual} "
345
+ f"but declared_size = {declared}"
346
+ )
347
+ consts = [e for e in typed_entities if e["kind"] == "constant" and e.get("name")]
348
+ wires = [e for e in typed_entities if e["kind"] == "wire_format" and e.get("struct_format")]
349
+ for c in consts:
350
+ val = c["value"]
351
+ if not (val.startswith(("b'", 'b"')) and val.endswith(("'", '"'))):
352
+ continue
353
+ try:
354
+ import ast as _ast
355
+ literal = _ast.literal_eval(val)
356
+ except (ValueError, SyntaxError):
357
+ continue
358
+ if not isinstance(literal, (bytes, bytearray)):
359
+ continue
360
+ for w in wires:
361
+ m = re.search(r"(\d+)s", str(w["struct_format"]))
362
+ if not m:
363
+ continue
364
+ width = int(m.group(1))
365
+ if len(literal) > width:
366
+ errors.append(
367
+ f"constant {c['name']} = {val} is {len(literal)} bytes but wire_format "
368
+ f"{w['struct_format']!r} allots a {width}-byte field (truncation on write)"
369
+ )
370
+ return errors
371
+
372
+
373
+ def entity_enforcement_misses(
374
+ typed_entities: list[dict],
375
+ brief_body: str,
376
+ deliverable_body: str | None,
377
+ ) -> tuple[list[str], list[str]]:
378
+ """Apply per-kind enforcement. Returns (brief_misses, deliverable_misses)
379
+ with kind-tagged strings. meta entities are never checked against the
380
+ deliverable; delivery markers are checked line-verbatim."""
381
+ brief_misses: list[str] = []
382
+ deliv_misses: list[str] = []
383
+ norm_brief, norm_deliv = _norm(brief_body), _norm(deliverable_body or "")
384
+ deliv_lines = set((deliverable_body or "").splitlines())
385
+ for e in typed_entities:
386
+ kind, val = e["kind"], e["value"]
387
+ rule = _ENTITY_KINDS[kind]
388
+ if rule["brief"] == "line":
389
+ if val not in brief_body:
390
+ brief_misses.append(f"{kind}:{val}")
391
+ elif _norm(val) not in norm_brief:
392
+ brief_misses.append(f"{kind}:{val}")
393
+ target = rule["deliverable"]
394
+ if target is None:
395
+ continue
396
+ if target == "line":
397
+ if val not in deliv_lines and val not in (deliverable_body or ""):
398
+ deliv_misses.append(f"{kind}:{val}")
399
+ elif _norm(val) not in norm_deliv:
400
+ deliv_misses.append(f"{kind}:{val}")
401
+ return brief_misses, deliv_misses
@@ -0,0 +1,40 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from pathlib import Path
5
+ from typing import Any
6
+ import json
7
+
8
+
9
+ @dataclass(frozen=True)
10
+ class ObservationSpec:
11
+ check_id: str
12
+ requirements: tuple[str, ...]
13
+ resolver: str
14
+ operations: tuple[str, ...]
15
+
16
+
17
+ class SemanticObserverRegistry:
18
+ """Read-only registry for declared semantic checks.
19
+
20
+ This module does not invoke a model, retry candidate work, mutate artifacts,
21
+ or gate the baseline Condition C candidate path.
22
+ """
23
+
24
+ def __init__(self, repo_root: str | Path):
25
+ root = Path(repo_root)
26
+ contract = json.loads((root / "contracts" / "VERIFICATION_CONTRACT.json").read_text(encoding="utf-8"))
27
+ self.mode = contract["baseline_profile"]["S"]
28
+ self._checks = tuple(
29
+ ObservationSpec(
30
+ check_id=item["id"],
31
+ requirements=tuple(item["requirements"]),
32
+ resolver=item["resolver"],
33
+ operations=tuple(item["operations"]),
34
+ )
35
+ for item in contract["checks"]
36
+ if item["class"] == "S"
37
+ )
38
+
39
+ def for_operation(self, operation: str) -> tuple[ObservationSpec, ...]:
40
+ return tuple(spec for spec in self._checks if operation in spec.operations)