994d06aaea
- 新增 selector_candidate_demo,移除 provenance intent 后枚举候选 selector - 对候选分支执行有界重建与严格 STEP 比较 - 仅在候选遍历完整且唯一 strict 通过时生成 selector 映射记录 - 增加 selector 候选搜索、预算限制和记录生成的测试 - 保持生产 selector resolver 不受 Demo 逻辑影响 - 更新 CADFS 能力台账,记录 IMPRINT 派生 profile 的 lineage selector 缺口
898 lines
38 KiB
Python
898 lines
38 KiB
Python
"""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())
|