Cursor: Apply local changes for cloud agent
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Promote only acceptance-gated verifiers into privacy-safe shadow mode."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import shutil
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
DEFAULT_CANDIDATE_DIRECTORY = Path(
|
||||
"ModelTraining/ClipboardSemantics/VerifierCandidates"
|
||||
)
|
||||
DEFAULT_RESOURCE_DIRECTORY = Path(
|
||||
"OSGKeyboardShared/Resources/ClipboardSemantics"
|
||||
)
|
||||
DEFAULT_REPORT = Path(
|
||||
"ModelTraining/ClipboardSemantics/verifier-deployment-report.json"
|
||||
)
|
||||
|
||||
|
||||
def sha256(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as stream:
|
||||
for chunk in iter(lambda: stream.read(128 * 1024), b""):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def parse_arguments() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"--candidate-directory",
|
||||
type=Path,
|
||||
default=DEFAULT_CANDIDATE_DIRECTORY,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--resource-directory",
|
||||
type=Path,
|
||||
default=DEFAULT_RESOURCE_DIRECTORY,
|
||||
)
|
||||
parser.add_argument("--report", type=Path, default=DEFAULT_REPORT)
|
||||
parser.add_argument(
|
||||
"--apply",
|
||||
action="store_true",
|
||||
help="Copy eligible models and update the deployed manifest.",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
arguments = parse_arguments()
|
||||
candidate_manifest_path = (
|
||||
arguments.candidate_directory / "clipboard-semantic-models.json"
|
||||
)
|
||||
deployed_manifest_path = (
|
||||
arguments.resource_directory / "clipboard-semantic-models.json"
|
||||
)
|
||||
candidate_manifest = json.loads(
|
||||
candidate_manifest_path.read_text(encoding="utf-8")
|
||||
)
|
||||
deployed_manifest = json.loads(
|
||||
deployed_manifest_path.read_text(encoding="utf-8")
|
||||
)
|
||||
|
||||
candidate_verifiers = candidate_manifest.get("verifiers") or []
|
||||
eligible = [
|
||||
verifier
|
||||
for verifier in candidate_verifiers
|
||||
if verifier.get("acceptedForAutomaticRouting") is True
|
||||
and verifier.get("deploymentMode") == "automatic"
|
||||
]
|
||||
entries = []
|
||||
for verifier in candidate_verifiers:
|
||||
model_path = arguments.candidate_directory / verifier["modelFile"]
|
||||
if not model_path.is_file():
|
||||
raise FileNotFoundError(model_path)
|
||||
entries.append(
|
||||
{
|
||||
"id": verifier["id"],
|
||||
"eligible": verifier in eligible,
|
||||
"candidateDeploymentMode": verifier.get("deploymentMode"),
|
||||
"candidateSHA256": sha256(model_path),
|
||||
"decision": (
|
||||
"promote-to-shadow"
|
||||
if verifier in eligible
|
||||
else "retain-current-models"
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
applied = False
|
||||
if arguments.apply and eligible:
|
||||
promoted_verifiers = []
|
||||
for verifier in eligible:
|
||||
source = arguments.candidate_directory / verifier["modelFile"]
|
||||
destination = arguments.resource_directory / verifier["modelFile"]
|
||||
shutil.copy2(source, destination)
|
||||
shadow = dict(verifier)
|
||||
shadow["acceptedForAutomaticRouting"] = False
|
||||
shadow["deploymentMode"] = "shadow"
|
||||
shadow["candidatePassedAcceptance"] = True
|
||||
promoted_verifiers.append(shadow)
|
||||
|
||||
# Always start from the deployed manifest so binary classifiers cannot
|
||||
# be replaced by an experimental candidate as a side effect.
|
||||
deployed_manifest["schemaVersion"] = 3
|
||||
deployed_manifest["verifiers"] = promoted_verifiers
|
||||
deployed_manifest["verifierGeneratedAt"] = candidate_manifest.get(
|
||||
"verifierGeneratedAt"
|
||||
)
|
||||
deployed_manifest["verifierLabelPolicy"] = candidate_manifest.get(
|
||||
"verifierLabelPolicy"
|
||||
)
|
||||
deployed_manifest_path.write_text(
|
||||
json.dumps(
|
||||
deployed_manifest,
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
sort_keys=True,
|
||||
)
|
||||
+ "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
applied = True
|
||||
|
||||
report = {
|
||||
"schemaVersion": 1,
|
||||
"generatedAt": datetime.now(timezone.utc).isoformat(),
|
||||
"applyRequested": arguments.apply,
|
||||
"applied": applied,
|
||||
"eligibleVerifierCount": len(eligible),
|
||||
"deployedClassifierCountPreserved": len(
|
||||
deployed_manifest.get("classifiers") or []
|
||||
),
|
||||
"policy": (
|
||||
"Only acceptance-gated automatic candidates may be copied, and "
|
||||
"their first deployed mode is forced to shadow. If none pass, the "
|
||||
"deployed manifest and current binary models remain untouched."
|
||||
),
|
||||
"verifiers": entries,
|
||||
}
|
||||
arguments.report.parent.mkdir(parents=True, exist_ok=True)
|
||||
arguments.report.write_text(
|
||||
json.dumps(report, ensure_ascii=False, indent=2, sort_keys=True) + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
print(
|
||||
"VERIFIER_DEPLOYMENT_POLICY "
|
||||
f"eligible={len(eligible)} applied={str(applied).lower()}"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user