"""Offline experiment for geometry-only CADFS selector candidates. The production resolver must not consume this module. It removes provenance intent only in a copied CDSL document, then compares that diagnostic replay to the source's final STEP. A strict passing probe is replayed a second time from its bound CDSL and can emit a *behavioural* selector record. The record is training/diagnostic evidence, not a claim that a heuristic recovered Onshape's hidden topology identity. """ from __future__ import annotations import argparse from copy import deepcopy import json import multiprocessing from pathlib import Path from typing import Any from .compare import compare_steps from .dataset import Sample, scan_dataset from .featurescript_parser import parse_featurescript from .lowering import lower_model from .rebuild import rebuild_candidate from .reports import read_json, write_json, write_manifest def strip_provenance_intents(value: Any) -> int: """Remove provenance-only selector fields from one copied CDSL value.""" removed = 0 if isinstance(value, dict): for key in ("selector_intent", "selector_intent_version"): if key in value: value.pop(key) removed += 1 for child in value.values(): removed += strip_provenance_intents(child) elif isinstance(value, list): for child in value: removed += strip_provenance_intents(child) return removed def _write_rebuild_artifacts(directory: Path, name: str, outcome: dict[str, Any]) -> None: """Persist the same useful replay artifacts as the regular pipeline.""" artifact = deepcopy(outcome) bound = artifact.pop("bound_cdsl", None) prefix = artifact.get("last_executable_prefix") if isinstance(prefix, dict): prefix_bound = prefix.pop("bound_cdsl", None) if bound is None: bound = prefix_bound write_json(directory / f"{name}.rebuild.json", artifact) if isinstance(bound, dict): write_json(directory / f"{name}.bound.cdsl.json", bound) def _rebuild_worker(candidate: str, step: str, result: str) -> None: write_json(Path(result), rebuild_candidate(read_json(Path(candidate)), Path(step))) def _compare_worker(gold: str, rebuilt: str, result: str) -> None: write_json(Path(result), compare_steps(Path(gold), Path(rebuilt))) def _isolated(target: Any, args: tuple[str, ...], result_path: Path, timeout_seconds: float) -> str: """Bound an OCC experiment so one candidate cannot stall the demo.""" result_path.unlink(missing_ok=True) process = multiprocessing.get_context("spawn").Process(target=target, args=args) process.start() process.join(timeout_seconds) if process.is_alive(): process.terminate() process.join(5) if process.is_alive(): process.kill() process.join() return "timeout" return "completed" if process.exitcode == 0 and result_path.exists() else "failed" def _run_rebuild(candidate_path: Path, step: Path, *, timeout_seconds: float) -> dict[str, Any]: worker = step.with_suffix(".worker.json") outcome = _isolated(_rebuild_worker, (str(candidate_path), str(step), str(worker)), worker, timeout_seconds) if outcome == "completed": result = read_json(worker) worker.unlink(missing_ok=True) return result step.unlink(missing_ok=True) return { "status": "rebuild_timeout" if outcome == "timeout" else "rebuild_failed", "error": { "type": "TimeoutError" if outcome == "timeout" else "WorkerProcessError", "message": f"selector candidate rebuild exceeded {timeout_seconds:g} seconds" if outcome == "timeout" else "selector candidate rebuild worker exited without a result", }, } def _run_comparison(gold_step: Path, rebuilt_step: Path, output: Path, *, timeout_seconds: float) -> tuple[dict[str, Any] | None, dict[str, Any] | None]: worker = output.with_suffix(".worker.json") outcome = _isolated(_compare_worker, (str(gold_step), str(rebuilt_step), str(worker)), worker, timeout_seconds) if outcome == "completed": comparison = read_json(worker) worker.unlink(missing_ok=True) return comparison, None return None, { "type": "TimeoutError" if outcome == "timeout" else "WorkerProcessError", "message": f"selector candidate comparison exceeded {timeout_seconds:g} seconds" if outcome == "timeout" else "selector candidate comparison worker exited without a result", } def _outcome_summary(outcome: dict[str, Any], comparison: dict[str, Any] | None) -> dict[str, Any]: summary: dict[str, Any] = { "rebuild_status": outcome.get("status"), "last_executable_prefix": { key: value for key, value in (outcome.get("last_executable_prefix") or {}).items() if key in {"failed_feature_id", "feature_count", "last_feature_id"} }, } error = outcome.get("error") if isinstance(error, dict): summary["error"] = {key: error.get(key) for key in ("type", "message")} if comparison is not None: summary["comparison"] = { "decision": comparison.get("decision"), "strict_passed": bool((comparison.get("strict") or {}).get("passed")), "rp_passed": bool((comparison.get("rp") or {}).get("passed")), } return summary def _strict_passed(comparison: dict[str, Any] | None) -> bool: return bool(isinstance(comparison, dict) and (comparison.get("strict") or {}).get("passed")) def _selector_sites(feature: dict[str, Any]) -> list[tuple[str, dict[str, Any]]]: """Return the public selector positions of one CDSL feature. The locations are deliberately structural rather than runtime IDs. A selector record must remain useful when a fresh replay creates different topology-record instances. """ sites: list[tuple[str, dict[str, Any]]] = [] for index, selector in enumerate(feature.get("selectors") or []): if isinstance(selector, dict): sites.append((f"selectors[{index}]", selector)) params = feature.get("params") or {} for name in ("end_condition", "reverse_end_condition"): condition = params.get(name) reference = condition.get("reference") if isinstance(condition, dict) else None if isinstance(reference, dict): sites.append((f"params.{name}.reference", reference)) return sites def _semantic_source_selector(selector: dict[str, Any]) -> dict[str, Any] | None: """Keep the FeatureScript meaning, never a source runtime topology ID.""" intent = selector.get("selector_intent") if not isinstance(intent, dict): return None result = { key: deepcopy(selector[key]) for key in ("kind", "owner_feature_id", "binding_feature_id", "output_role") if selector.get(key) is not None } result["selector_intent"] = deepcopy(intent) return result def _selector_record_entries( sample_id: str, provenance_candidate: dict[str, Any], compiled_cdsl: dict[str, Any], selector_binding: list[dict[str, Any]], ) -> list[dict[str, Any]]: """Extract FeatureScript meaning plus a freshly replayable binding plan. A geometry probe has only the selected record, not rejected alternatives. We preserve that limitation explicitly. When source and compiled selector counts differ (for example, one source query expands to several topology records), emit a feature group instead of inventing a one-to-one mapping. """ source_by_id = { str(feature.get("id")): feature for feature in provenance_candidate.get("features") or [] if isinstance(feature, dict) and isinstance(feature.get("id"), str) } compiled_by_id = { str(feature.get("id")): feature for feature in compiled_cdsl.get("features") or [] if isinstance(feature, dict) and isinstance(feature.get("id"), str) } entries: list[dict[str, Any]] = [] for binding in selector_binding: feature_id = binding.get("feature_id") if not isinstance(feature_id, str): continue source_feature = source_by_id.get(feature_id) compiled_feature = compiled_by_id.get(feature_id) if source_feature is None or compiled_feature is None: continue source_sites = [ (location, semantic) for location, selector in _selector_sites(source_feature) if (semantic := _semantic_source_selector(selector)) is not None ] if not source_sites: continue compiled_sites = _selector_sites(compiled_feature) resolved = [deepcopy(item) for item in binding.get("resolved") or [] if isinstance(item, dict)] feature_context = { "sample_id": sample_id, "feature_id": feature_id, "atomic_id": compiled_feature.get("atomic_id"), "prefix_feature_count": binding.get("prefix_feature_count"), "verification_scope": "behavioural_strict_replay", } if len(source_sites) == len(compiled_sites) == len(resolved): for index, ((location, source_selector), (_compiled_location, compiled_selector), resolved_record) in enumerate( zip(source_sites, compiled_sites, resolved) ): entries.append({ "schema": "cadfs_to_cdsl.selector_record.v1", "entry_id": f"{sample_id}:{feature_id}:{location}", **feature_context, "source_location": location, "source_selector": source_selector, "compiled_selector": deepcopy(compiled_selector), "resolved_record": resolved_record, "mapping": "one_to_one", "limitations": [ "The source dataset has no per-feature Onshape query witness.", "The compiled selector is verified only by a fresh local strict replay.", "Runtime snapshot IDs are replay evidence, not portable topology identities.", ], }) continue entries.append({ "schema": "cadfs_to_cdsl.selector_record.v1", "entry_id": f"{sample_id}:{feature_id}:feature_group", **feature_context, "source_selectors": [ {"location": location, "selector": selector} for location, selector in source_sites ], "compiled_selectors": [ {"location": location, "selector": deepcopy(selector)} for location, selector in compiled_sites ], "resolved_records": resolved, "mapping": "feature_group", "limitations": [ "The source query expanded or contracted during local binding, so this record does not claim a one-to-one mapping.", "The source dataset has no per-feature Onshape query witness.", "The compiled selector is verified only by a fresh local strict replay.", "Runtime snapshot IDs are replay evidence, not portable topology identities.", ], }) return entries def _emit_strict_selector_record( sample: Sample, directory: Path, provenance_candidate: dict[str, Any], geometry_probe: dict[str, Any], geometry_comparison: dict[str, Any] | None, gold_step: Path, *, rebuild_timeout_seconds: float, comparison_timeout_seconds: float, ) -> dict[str, Any]: """Create and independently verify a compiled CDSL only after strict proof.""" if not _strict_passed(geometry_comparison): return { "status": "not_emitted", "reason": "geometry_probe_not_strict", } bound_cdsl = geometry_probe.get("bound_cdsl") binding = geometry_probe.get("selector_binding") if not isinstance(bound_cdsl, dict) or not isinstance(binding, list): return { "status": "not_emitted", "reason": "geometry_probe_binding_missing", } compiled_candidate = deepcopy(bound_cdsl) compiled_path = directory / "selector_record.compiled.cdsl.json" write_json(compiled_path, compiled_candidate) compiled = _run_rebuild( compiled_path, directory / "selector_record.compiled.rebuild.step", timeout_seconds=rebuild_timeout_seconds, ) _write_rebuild_artifacts(directory, "selector_record.compiled", compiled) comparison = None comparison_error = None if compiled.get("status") == "rebuilt": comparison, comparison_error = _run_comparison( gold_step, directory / "selector_record.compiled.rebuild.step", directory / "selector_record.compiled.comparison.json", timeout_seconds=comparison_timeout_seconds, ) if comparison is not None: write_json(directory / "selector_record.compiled.comparison.json", comparison) result = { "status": "not_emitted", "compiled_cdsl": str(compiled_path), "compiled_replay": _outcome_summary(compiled, comparison), } if comparison_error is not None: result["compiled_replay"]["comparison_error"] = comparison_error if not _strict_passed(comparison): result["reason"] = "compiled_cdsl_not_strict" return result records = _selector_record_entries(sample.sample_id, provenance_candidate, compiled_candidate, binding) if not records: result["reason"] = "no_featurescript_selector_binding" return result record_path = directory / "selector-record.json" write_json(record_path, { "schema": "cadfs_to_cdsl.selector_record_set.v1", "sample_id": sample.sample_id, "source": { "featurescript": sample.files.get("featurescript"), "step": sample.files.get("step"), }, "verification": { "geometry_probe_strict": True, "compiled_cdsl_strict": True, "compiled_cdsl": str(compiled_path), }, "records": records, }) result.update({ "status": "strict_replayed", "record_count": len(records), "record_path": str(record_path), }) return result def _searchable_selector_sites(feature: dict[str, Any]) -> list[tuple[str, dict[str, Any]]]: """Return direct selector roots that can be replaced as a search branch. The experimental search does not flatten an intersection query into an unrelated member. It can search direct roots and extent references; other query trees remain a bounded unsupported search outcome. """ sites = _selector_sites(feature) return [ (location, selector) for location, selector in sites if not isinstance(selector.get("intersection_of"), list) ] def _replace_selector_site(cdsl: dict[str, Any], feature_id: str, location: str, selector: dict[str, Any]) -> None: """Replace one public direct selector site in a copied candidate CDSL.""" feature = next( (item for item in cdsl.get("features") or [] if isinstance(item, dict) and item.get("id") == feature_id), None, ) if feature is None: raise ValueError(f"search branch cannot find feature {feature_id}") if location.startswith("selectors[") and location.endswith("]"): index = int(location[len("selectors["):-1]) selectors = feature.get("selectors") or [] if index < 0 or index >= len(selectors) or not isinstance(selectors[index], dict): raise ValueError(f"search branch selector site is unavailable: {feature_id}:{location}") selectors[index] = deepcopy(selector) return prefix = "params." suffix = ".reference" if location.startswith(prefix) and location.endswith(suffix): name = location[len(prefix):-len(suffix)] condition = (feature.get("params") or {}).get(name) if not isinstance(condition, dict) or not isinstance(condition.get("reference"), dict): raise ValueError(f"search branch selector site is unavailable: {feature_id}:{location}") condition["reference"] = deepcopy(selector) return raise ValueError(f"search branch does not support nested selector site: {feature_id}:{location}") def _forced_candidate_selector(placeholder: dict[str, Any], candidate: dict[str, Any]) -> dict[str, Any]: """Turn one public runtime record into a branch-local explicit selector.""" from .selector_binding import _bound_selector selector, _bound = _bound_selector(placeholder, [candidate]) if not isinstance(selector, dict): raise ValueError("search branch produced an invalid explicit selector") return selector def _resolve_search_selector(placeholder: dict[str, Any], *, registry: Any, active_body_id: str | None) -> Any: """Mirror the legacy diagnostic binding policy without changing it.""" from .selector_binding import _runtime_selector runtime_selector = _runtime_selector(placeholder) resolution = registry.resolve(runtime_selector, active_body_id=active_body_id) intent = placeholder.get("selector_intent") if ( resolution.status != "resolved" and not isinstance(intent, dict) and not runtime_selector.get("owner_match_required") and not runtime_selector.get("output_role") ): fallback = dict(runtime_selector) fallback.pop("owner_feature_id", None) resolution = registry.resolve(fallback, active_body_id=active_body_id) return resolution def _search_candidate_records( resolution: Any, *, maximum: int, minimum_score: float = 0.8, ) -> tuple[list[dict[str, Any]], int]: """Keep only candidates the normal resolver considers geometrically valid.""" candidates = [ item for item in resolution.candidates if isinstance(item, dict) and item.get("record_id") and float(item.get("score") or 0.0) >= minimum_score ] candidates.sort(key=lambda item: (-float(item.get("score") or 0.0), str(item.get("record_id")))) return [deepcopy(item) for item in candidates[:maximum]], len(candidates) def _search_replay_worker(candidate: str, step: str, result: str, maximum_candidates: int) -> None: """Replay one branch until it builds or exposes its next selector decision.""" from engine.cdsl_engine.runtime import finalize_cdsl_execution, prepare_cdsl_execution from .selector_binding import _bound_selector, _selector_key bound = read_json(Path(candidate)) execution = prepare_cdsl_execution(bound) if not execution.analysis.runtime_eligible: first = next((item for item in execution.analysis.feature_results if not item.executable), None) code = first.blockers[0].code if first and first.blockers else "runtime_ineligible" write_json(Path(result), {"status": "runtime_ineligible", "reason": code}) return bound_features = { str(feature.get("id") or ""): feature for feature in bound.get("features") or [] if isinstance(feature, dict) } body_id_for_feature: dict[str, str | None] = {"__current__": None} evidence: list[dict[str, Any]] = [] for index, node in enumerate(execution.analysis.plan): feature = node.source_feature sites = _searchable_selector_sites(feature) if len(sites) != len(_selector_sites(feature)): write_json(Path(result), { "status": "search_unsupported", "feature_id": node.feature_id, "reason": "nested_selector_tree", "bound_cdsl": bound, "selector_binding": evidence, }) return resolved: list[dict[str, Any]] = [] for location, placeholder in sites: binding_feature_id = placeholder.get("binding_feature_id") if binding_feature_id is None: active_body_id = body_id_for_feature["__current__"] elif isinstance(binding_feature_id, str) and binding_feature_id in body_id_for_feature: active_body_id = body_id_for_feature[binding_feature_id] else: write_json(Path(result), { "status": "search_failed", "feature_id": node.feature_id, "reason": "selector_binding_feature_missing_or_forward", "bound_cdsl": bound, "selector_binding": evidence, }) return resolution = _resolve_search_selector( placeholder, registry=execution.session.topology, active_body_id=active_body_id, ) if resolution.status != "resolved": candidates, eligible_count = _search_candidate_records( resolution, maximum=maximum_candidates, ) if candidates: write_json(Path(result), { "status": "branchable_selector", "feature_id": node.feature_id, "source_location": location, "resolution_status": resolution.status, "diagnostic": resolution.diagnostic.as_dict() if resolution.diagnostic is not None else None, "candidates": candidates, "candidate_count": eligible_count, "bound_cdsl": bound, "selector_binding": evidence, }) return code = resolution.diagnostic.code if resolution.diagnostic is not None else f"selector_{resolution.status}" write_json(Path(result), { "status": "search_failed", "feature_id": node.feature_id, "reason": code, "bound_cdsl": bound, "selector_binding": evidence, }) return selected = list(resolution.records or ((resolution.record,) if resolution.record is not None else ())) if not selected: write_json(Path(result), { "status": "search_failed", "feature_id": node.feature_id, "reason": "selector_not_found", "bound_cdsl": bound, "selector_binding": evidence, }) return public_records = [record.public_dict() for record in selected] selector, bound_selectors = _bound_selector(placeholder, public_records) placeholder.clear() placeholder.update(selector) resolved.extend(bound_selectors) feature["selectors"] = list({ _selector_key(selector): selector for selector in feature.get("selectors") or [] }.values()) if feature.get("atomic_id") == "pattern_mirror": planes = [selector for selector in feature["selectors"] if selector.get("kind") == "plane"] if len(planes) != 1: write_json(Path(result), { "status": "search_failed", "feature_id": node.feature_id, "reason": "mirror_plane_binding_not_unique", "bound_cdsl": bound, "selector_binding": evidence, }) return feature.setdefault("params", {})["mirror_plane"] = planes[0] public_feature = bound_features[node.feature_id] public_feature["selectors"] = deepcopy(feature.get("selectors") or []) public_feature["params"] = deepcopy(feature.get("params") or {}) evidence.append({ "feature_id": node.feature_id, "prefix_feature_count": index, "selectors": public_feature["selectors"], "resolved": resolved, }) try: execution.execute_next(strict=True) except Exception as error: write_json(Path(result), { "status": "search_failed", "feature_id": node.feature_id, "reason": str(error), "bound_cdsl": bound, "selector_binding": evidence, }) return body_id_for_feature[node.feature_id] = execution.session.body_id body_id_for_feature["__current__"] = execution.session.body_id write_json(Path(result), { "status": "rebuilt", "bound_cdsl": bound, "selector_binding": evidence, "result": finalize_cdsl_execution(execution, Path(step)), }) def _run_search_replay(candidate_path: Path, step: Path, *, timeout_seconds: float, maximum_candidates: int) -> dict[str, Any]: worker = step.with_suffix(".worker.json") outcome = _isolated( _search_replay_worker, (str(candidate_path), str(step), str(worker), maximum_candidates), worker, timeout_seconds, ) if outcome == "completed": result = read_json(worker) worker.unlink(missing_ok=True) return result step.unlink(missing_ok=True) return { "status": "search_timeout" if outcome == "timeout" else "search_failed", "reason": "search_worker_timeout" if outcome == "timeout" else "search_worker_failed", } def _public_branch_summary(result: dict[str, Any], choices: list[dict[str, Any]], comparison: dict[str, Any] | None = None) -> dict[str, Any]: summary = { "status": result.get("status"), "feature_id": result.get("feature_id"), "source_location": result.get("source_location"), "reason": result.get("reason"), "resolution_status": result.get("resolution_status"), "choice_path": choices, } if comparison is not None: summary["comparison"] = { "decision": comparison.get("decision"), "strict_passed": _strict_passed(comparison), } return {key: value for key, value in summary.items() if value is not None} def _run_selector_search( sample: Sample, directory: Path, provenance_candidate: dict[str, Any], geometry_candidate: dict[str, Any], gold_step: Path, *, rebuild_timeout_seconds: float, comparison_timeout_seconds: float, maximum_branches: int, maximum_candidates: int, ) -> tuple[dict[str, Any], dict[str, Any] | None]: """Explore selector alternatives, accepting only a unique strict winner. Each branch is re-executed from an untouched CDSL document. We therefore never share mutable OCC topology between alternatives and cannot quietly inherit the result of a previous candidate. """ if maximum_branches <= 0 or maximum_candidates <= 0: return {"status": "disabled"}, None search_dir = directory / "selector-search" search_dir.mkdir(parents=True, exist_ok=True) pending: list[tuple[dict[str, Any], list[dict[str, Any]]]] = [(deepcopy(geometry_candidate), [])] seen: set[str] = set() terminals: list[tuple[dict[str, Any], dict[str, Any], list[dict[str, Any]], dict[str, Any] | None]] = [] branch_summaries: list[dict[str, Any]] = [] budget_exhausted = False candidates_truncated = False branch_index = 0 while pending: if branch_index >= maximum_branches: budget_exhausted = True break candidate, choices = pending.pop(0) fingerprint = json.dumps(candidate, sort_keys=True, separators=(",", ":")) if fingerprint in seen: continue seen.add(fingerprint) branch_index += 1 branch_name = f"branch-{branch_index:03d}" candidate_path = search_dir / f"{branch_name}.candidate.cdsl.json" step_path = search_dir / f"{branch_name}.rebuild.step" write_json(candidate_path, candidate) outcome = _run_search_replay( candidate_path, step_path, timeout_seconds=rebuild_timeout_seconds, maximum_candidates=maximum_candidates, ) _write_rebuild_artifacts(search_dir, branch_name, outcome) if outcome.get("status") == "branchable_selector": candidates = list(outcome.get("candidates") or []) total = int(outcome.get("candidate_count") or len(candidates)) if total > len(candidates): candidates_truncated = True branch_summaries.append(_public_branch_summary(outcome, choices)) for candidate_record in candidates: child = deepcopy(outcome["bound_cdsl"]) feature_id = str(outcome["feature_id"]) location = str(outcome["source_location"]) source_feature = next(item for item in child["features"] if item.get("id") == feature_id) source_selector = dict(_searchable_selector_sites(source_feature)[ next(index for index, (name, _selector) in enumerate(_searchable_selector_sites(source_feature)) if name == location) ][1]) forced = _forced_candidate_selector(source_selector, candidate_record) _replace_selector_site(child, feature_id, location, forced) pending.append((child, [*choices, { "feature_id": feature_id, "source_location": location, "record_id": candidate_record.get("record_id"), "score": candidate_record.get("score"), }])) continue comparison = None if outcome.get("status") == "rebuilt": comparison, comparison_error = _run_comparison( gold_step, step_path, search_dir / f"{branch_name}.comparison.json", timeout_seconds=comparison_timeout_seconds, ) if comparison is not None: write_json(search_dir / f"{branch_name}.comparison.json", comparison) elif comparison_error is not None: outcome["comparison_error"] = comparison_error terminals.append((outcome, candidate, choices, comparison)) branch_summaries.append(_public_branch_summary(outcome, choices, comparison)) if pending: budget_exhausted = True strict_winners = [item for item in terminals if _strict_passed(item[3])] search_complete = not budget_exhausted and not candidates_truncated report: dict[str, Any] = { "status": "unique_strict" if search_complete and len(strict_winners) == 1 else "no_unique_strict", "branches_executed": branch_index, "maximum_branches": maximum_branches, "maximum_candidates_per_selector": maximum_candidates, "search_complete": search_complete, "budget_exhausted": budget_exhausted, "candidates_truncated": candidates_truncated, "strict_winner_count": len(strict_winners), "branches": branch_summaries, } if not search_complete: report["reason"] = "search_budget_incomplete" return report, None if not strict_winners: report["reason"] = "no_strict_branch" return report, None if len(strict_winners) > 1: report["reason"] = "multiple_strict_branches" report["strict_choice_paths"] = [item[2] for item in strict_winners] return report, None winner, _candidate, choices, comparison = strict_winners[0] report["strict_choice_path"] = choices selector_record = _emit_strict_selector_record( sample, search_dir, provenance_candidate, winner, comparison, gold_step, rebuild_timeout_seconds=rebuild_timeout_seconds, comparison_timeout_seconds=comparison_timeout_seconds, ) report["selector_record"] = selector_record return report, selector_record if selector_record.get("status") == "strict_replayed" else None def run_geometry_probe( sample: Sample, output: Path, *, rebuild_timeout_seconds: float = 30.0, comparison_timeout_seconds: float = 60.0, search_maximum_branches: int = 32, search_maximum_candidates: int = 8, ) -> dict[str, Any]: """Compare normal provenance replay with an explicitly heuristic replay. The geometry replay reuses only geometry/stable-ID fields already emitted as diagnostic context. It never invents a selector, writes a new source parameter, or changes the production candidate. """ featurescript = Path(sample.files["featurescript"]) gold_step = Path(sample.files["step"]) if not featurescript.is_file() or not gold_step.is_file(): raise FileNotFoundError(f"{sample.sample_id}: the FeatureScript and source STEP are required") directory = output / "samples" / sample.sample_id directory.mkdir(parents=True, exist_ok=True) result = lower_model(parse_featurescript(featurescript.read_text(encoding="utf-8"), sample.sample_id), {}) if not isinstance(result.cdsl, dict): raise ValueError(f"{sample.sample_id}: lowering produced no CDSL candidate") provenance_candidate = result.cdsl geometry_candidate = deepcopy(provenance_candidate) removed_intents = strip_provenance_intents(geometry_candidate) write_json(directory / "provenance.candidate.cdsl.json", provenance_candidate) write_json(directory / "geometry_probe.candidate.cdsl.json", geometry_candidate) provenance = _run_rebuild( directory / "provenance.candidate.cdsl.json", directory / "provenance.rebuild.step", timeout_seconds=rebuild_timeout_seconds, ) geometry_probe = _run_rebuild( directory / "geometry_probe.candidate.cdsl.json", directory / "geometry_probe.rebuild.step", timeout_seconds=rebuild_timeout_seconds, ) _write_rebuild_artifacts(directory, "provenance", provenance) _write_rebuild_artifacts(directory, "geometry_probe", geometry_probe) comparison = None comparison_error = None if geometry_probe.get("status") == "rebuilt": comparison, comparison_error = _run_comparison( gold_step, directory / "geometry_probe.rebuild.step", directory / "geometry_probe.comparison.json", timeout_seconds=comparison_timeout_seconds, ) if comparison is not None: write_json(directory / "geometry_probe.comparison.json", comparison) selector_record = _emit_strict_selector_record( sample, directory, provenance_candidate, geometry_probe, comparison, gold_step, rebuild_timeout_seconds=rebuild_timeout_seconds, comparison_timeout_seconds=comparison_timeout_seconds, ) selector_search = {"status": "not_needed"} if not _strict_passed(comparison): selector_search, searched_record = _run_selector_search( sample, directory, provenance_candidate, geometry_candidate, gold_step, rebuild_timeout_seconds=rebuild_timeout_seconds, comparison_timeout_seconds=comparison_timeout_seconds, maximum_branches=search_maximum_branches, maximum_candidates=search_maximum_candidates, ) if searched_record is not None: selector_record = searched_record report = { "schema": "cadfs_to_cdsl.selector_candidate_geometry_probe.v1", "sample_id": sample.sample_id, "source": {"featurescript": str(featurescript), "step": str(gold_step)}, "strategy": { "name": "geometry_diagnostic_context", "removed_provenance_intent_fields": removed_intents, "classification": "heuristic_only", "limitations": [ "The source dataset contains only a final STEP, not per-feature source checkpoints.", "A final strict or RP pass does not prove that the FeatureScript selector is semantically correct.", "This probe must not be used by the production resolver or counted as provenance-selector coverage.", ], }, "provenance": _outcome_summary(provenance, None), "geometry_probe": _outcome_summary(geometry_probe, comparison), "selector_record": selector_record, "selector_search": selector_search, } if comparison_error is not None: report["geometry_probe"]["comparison_error"] = comparison_error write_json(directory / "report.json", report) return report def _arguments() -> argparse.Namespace: parser = argparse.ArgumentParser(description="Run an offline geometry-only selector candidate probe and strict record replay") parser.add_argument("--input", type=Path, default=Path("data/cadfs-sample/CADFS_test")) parser.add_argument("--output", type=Path, required=True) parser.add_argument("--sample-id", action="append", required=True) parser.add_argument("--rebuild-timeout-seconds", type=float, default=30.0) parser.add_argument("--comparison-timeout-seconds", type=float, default=60.0) parser.add_argument("--search-maximum-branches", type=int, default=32) parser.add_argument("--search-maximum-candidates", type=int, default=8) return parser.parse_args() def main() -> int: args = _arguments() samples = {sample.sample_id: sample for sample in scan_dataset(args.input, include_hashes=False)} missing = sorted(set(args.sample_id) - samples.keys()) if missing: raise ValueError("unknown sample ids: " + ", ".join(missing)) if args.rebuild_timeout_seconds <= 0 or args.comparison_timeout_seconds <= 0: raise ValueError("candidate probe timeouts must be positive") if args.search_maximum_branches < 0 or args.search_maximum_candidates < 0: raise ValueError("search budgets cannot be negative") reports = [ run_geometry_probe( samples[sample_id], args.output, rebuild_timeout_seconds=args.rebuild_timeout_seconds, comparison_timeout_seconds=args.comparison_timeout_seconds, search_maximum_branches=args.search_maximum_branches, search_maximum_candidates=args.search_maximum_candidates, ) for sample_id in args.sample_id ] records: list[dict[str, Any]] = [] for report in reports: record = report.get("selector_record") or {} record_path = record.get("record_path") if isinstance(record, dict) else None if isinstance(record_path, str) and Path(record_path).is_file(): payload = read_json(Path(record_path)) records.extend(item for item in payload.get("records") or [] if isinstance(item, dict)) write_manifest(args.output / "selector-records.jsonl", records) write_json(args.output / "summary.json", { "schema": "cadfs_to_cdsl.selector_candidate_geometry_probe_summary.v2", "sample_count": len(reports), "strict_selector_record_count": len(records), "reports": reports, }) return 0 if __name__ == "__main__": raise SystemExit(main())