app
Fusion - the Fusion 360 Bridge
Public Made by Adomby adom
Drive Autodesk Fusion 360 from the cloud via Adom Bridge: component libraries, IPC package generation, board layout, exports (STEP/Gerbers/BOM/CPL), fast APS cloud search, and parametric modeling.
master
Drew Owens
The mechanical layer on 1.12.8 with the restart fixes (1.13.0-dev.1, add-in 1.0.23)
2ef0220
14d ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
#!/usr/bin/env python3
"""Freeze the Python mechanical admission layer as goldens for its Rust port.
John's pattern: the Python side generates, the Rust side matches byte for byte. The source is the
dev.9 admission layer, kept verbatim as tests/mech_reference (journal, operation history) and
tests/mech_exe.py (validate, prepare_request, capabilities), with the add-in's schema.json and write
set. The same reference drives the offline add-in tests, so both sides share one contract.
Python's validator walks set intersections, so an input with several faults reports them in
hash order. The script therefore runs its corpus in eight child processes with different
PYTHONHASHSEED values and keeps only the cases all eight runs answer identically.
python3 tools/gen-mech-goldens.py [--evidence DIR]
"""
import argparse, copy, hashlib, json, os, re, subprocess, sys, tempfile
from pathlib import Path
from types import ModuleType, SimpleNamespace as NS
ROOT = Path(__file__).resolve().parents[1]
GOLDEN = ROOT / "rust" / "core" / "tests" / "golden"
SESSION = "S-GOLDEN-0000"
STALE = "S-STALE-0000"
SOURCE_VERSION = "1.12.0-dev.10"
def load_mechanical():
"""The add-in package (schema, write set) plus the frozen dev.9 admission reference."""
adsk = ModuleType("adsk"); adsk.core = ModuleType("adsk.core"); adsk.fusion = ModuleType("adsk.fusion")
adsk.core.Application = object
adsk.fusion.Design = NS(cast=lambda value: value)
adsk.fusion.CalculationAccuracy = NS(LowCalculationAccuracy=0, MediumCalculationAccuracy=1, HighCalculationAccuracy=2, VeryHighCalculationAccuracy=3)
adsk.fusion.FeatureHealthStates = NS(HealthyFeatureHealthState=0)
adsk.fusion.JointDirections = NS(XAxisJointDirection=0, YAxisJointDirection=1, ZAxisJointDirection=2, CustomJointDirection=3)
for module in (adsk, adsk.core, adsk.fusion):
sys.modules[module.__name__] = module
pkg = ModuleType("golden_commands"); pkg.__path__ = [str(ROOT / "addin/AdomBridge/commands")]
sys.modules[pkg.__name__] = pkg
sys.path.insert(0, str(ROOT / "tests"))
import importlib
addin = importlib.import_module("golden_commands.mechanical")
import mech_exe
from mech_reference import journal as jm, operation_history as hm
m = NS(SCHEMA=addin.SCHEMA, WRITES=addin.WRITES, transactions=addin.transactions,
prepare_request=lambda args: mech_exe.prepare_request(args, addin.SCHEMA, addin.WRITES, SESSION),
capabilities=lambda app, args: mech_exe.capabilities(addin.SCHEMA, SESSION, addin.transactions.capability()))
return m, jm, hm
# ---- canonical JSON -------------------------------------------------------------------------
CANONICAL_VALUES = [
None, True, False, 0, 1, -1, 7, 2 ** 53 + 1, -(2 ** 62), 0.0, -0.0, 1.0, -1.5, 100.0, 0.1, 0.30000000000000004,
1e-05, 1.23e-05, 0.0001, 0.00012, 1e16, 1.5e16, 9999999999999998.0, 1e15, 123456789.12345679, 1e22, 1e-07,
5e-324, 1.7976931348623157e308, 2.5e-310, 1792500002.123456, 1792500000.125, 42.0, 3.141592653589793,
"", "plain", "quote\"back\\slash", "ctl\n\r\t\b\f\x00\x01\x1f\x7f", "slash/", "é", "Ω≈ç√", "😀 emoji", "
",
"", [], {}, [1, [2, [3, {}]]], {"b": 1, "a": 2, "A": 3, "_": 4, "é": 5, "aa": 6, "a ": 7, "10": 8, "9": 9},
{"nested": {"z": [1.0, 2.5, {"y": None, "x": True}], "k": "v"}}, [0.1, -0.0, 1e100, -1e-100],
]
def canonical_cases(journal):
out = []
for value in CANONICAL_VALUES:
raw = journal.canonical(value)
out.append({"value": value, "canonical": raw.decode("ascii"), "sha256": hashlib.sha256(raw).hexdigest()})
return out
# ---- validation corpus ----------------------------------------------------------------------
def enum_strings(schema, acc):
if isinstance(schema, dict):
for value in schema.get("enum", []):
if isinstance(value, str):
acc.add(value)
for child in schema.values():
enum_strings(child, acc)
elif isinstance(schema, list):
for child in schema:
enum_strings(child, acc)
return acc
def scrub(value, keep, key=None):
"""Keep enum vocabulary and structure; replace every other string with a stable token."""
if isinstance(value, dict):
return {k: scrub(v, keep, k) for k, v in value.items()}
if isinstance(value, list):
return [scrub(v, keep, key) for v in value]
if isinstance(value, str):
if key in ("op", "type", "action") or value in keep:
return value
if key == "sessionId":
return SESSION
if key == "operationId":
return "op-" + hashlib.sha256(value.encode()).hexdigest()[:12]
return "s" + hashlib.sha256(value.encode()).hexdigest()[:10]
return value
def harvest(evidence, schema, keep, per_op=12):
found = {}
def walk(node):
if isinstance(node, dict):
if isinstance(node.get("op"), str) and node["op"] in schema:
args = scrub(node, keep)
found.setdefault(args["op"], {})[json.dumps(args, sort_keys=True)] = args
for child in node.values():
walk(child)
elif isinstance(node, list):
for child in node:
walk(child)
if evidence and Path(evidence).is_dir():
for path in sorted(Path(evidence).rglob("*.json")):
try:
walk(json.loads(path.read_text(encoding="utf-8")))
except (ValueError, UnicodeError, OSError):
continue
return {op: list(cases.values())[:per_op] for op, cases in found.items()}
def synth(schema, depth=0):
"""A best-effort valid value for one schema node (the golden records Python's verdict either way)."""
if depth > 12:
return None
if "oneOf" in schema:
variant = schema["oneOf"][0]
merged = {k: v for k, v in schema.items() if k != "oneOf"}
merged["properties"] = {**schema.get("properties", {}), **variant.get("properties", {})}
merged["required"] = list(dict.fromkeys(schema.get("required", []) + variant.get("required", [])))
merged["type"] = schema.get("type", variant.get("type"))
return synth(merged, depth + 1)
if "enum" in schema:
return schema["enum"][0]
kind = schema.get("type")
if kind == "object":
out = {}
for key in schema.get("required", []):
if key in ("op", "sessionId", "operationId", "target"):
continue
out[key] = synth(schema.get("properties", {}).get(key, {}), depth + 1)
return out
if kind == "array":
return [synth(schema.get("items", {}), depth + 1) for _ in range(schema.get("minItems", 0))]
if kind in ("number", "integer"):
low = schema.get("minimum", schema.get("exclusiveMinimum", 0))
value = low + (1 if "exclusiveMinimum" in schema else 0)
if "maximum" in schema:
value = min(value, schema["maximum"])
return int(value) if kind == "integer" else float(value)
if kind == "boolean":
return False
if kind == "string":
return "x"
return {}
TARGET = {"documentId": SESSION + ":doc", "revision": "r1"}
def base_args(op, op_schema):
args = {"op": op}
args.update(synth(op_schema))
required = op_schema.get("required", [])
if "sessionId" in required: args["sessionId"] = SESSION
if "operationId" in required: args["operationId"] = "op-synth-" + op
if "target" in required: args["target"] = dict(TARGET)
return args
def mutations(args, op_schema):
out = []
props = op_schema.get("properties", {})
for key in list(args):
if key == "op":
continue
dropped = dict(args); dropped.pop(key); out.append(dropped)
for key, prop in props.items():
kind = prop.get("type")
bad = {"object": "nope", "string": 7, "boolean": "yes", "number": "1", "integer": 1.5, "array": {}}.get(kind)
if bad is not None:
out.append({**args, key: bad})
if kind in ("number", "integer"):
if "minimum" in prop: out.append({**args, key: prop["minimum"] - 1})
if "maximum" in prop: out.append({**args, key: prop["maximum"] + 1})
if "exclusiveMinimum" in prop: out.append({**args, key: prop["exclusiveMinimum"]})
out.append({**args, key: True})
if "enum" in prop:
out.append({**args, key: "__not_in_enum__"})
if kind == "array" and "maxItems" in prop:
out.append({**args, key: [synth(prop.get("items", {}))] * (prop["maxItems"] + 1)})
out.append({**args, "unexpectedField": 1})
if "sessionId" in args:
out.append({**args, "sessionId": STALE})
if "operationId" in args:
out.append({**args, "operationId": ""})
out.append({**args, "operationId": "o" * 129})
out.append({**args, "operationId": 5})
out.append({**args, "caller": {"agent": "x"}, "_callerBudgetMs": 1000})
return out
EXTRA = [
"not an object", [], {}, {"op": "no_such_op"}, {"op": 5}, {"op": "inspect"}, {"op": "health"},
{"op": "health", "recompute": True}, {"op": "health", "recompute": True, "sessionId": SESSION, "operationId": "h1"},
{"op": "inspect", "target": dict(TARGET)},
]
def nested_mutations(args, limit=24):
"""One fault at a time below the top level: retype a leaf, drop a key, add an unknown key."""
out, paths = [], []
def walk(node, path):
if isinstance(node, dict):
if path:
paths.append((path, "dict"))
for k, v in node.items():
if not path and k in ("op", "sessionId", "operationId"):
continue
walk(v, path + [k])
elif isinstance(node, list):
if path:
paths.append((path, "list"))
for i, v in enumerate(node[:3]):
walk(v, path + [i])
elif path:
paths.append((path, "leaf"))
walk(args, [])
def setp(root, path, value, drop=False):
root = copy.deepcopy(root); node = root
for k in path[:-1]:
node = node[k]
if drop:
del node[path[-1]]
else:
node[path[-1]] = value
return root
for path, kind in paths:
if len(out) >= limit:
break
if len(path) < 2:
continue
current = args
for k in path:
current = current[k]
if kind == "leaf":
bad = "str" if isinstance(current, (int, float)) and not isinstance(current, bool) else 3 if isinstance(current, str) else "yes"
out.append(setp(args, path, bad))
elif kind == "dict":
out.append(setp(args, path, "notobject"))
out.append(setp(args, path + ["__unknown__"], 1))
for k in list(current)[:2]:
out.append(setp(args, path + [k], None, drop=True))
else:
out.append(setp(args, path, {"not": "a list"}))
return out[:limit]
def corpus(m, evidence):
keep = enum_strings(m.SCHEMA, set())
real = harvest(evidence, m.SCHEMA, keep)
cases = list(EXTRA)
for op, op_schema in m.SCHEMA.items():
args = base_args(op, op_schema)
cases.append(args)
cases.extend(mutations(args, op_schema))
cases.extend(nested_mutations(args))
for sample in real.get(op, []):
cases.append(sample)
cases.extend(mutations(sample, op_schema)[:6])
cases.extend(nested_mutations(sample))
return cases
def run_validation(m, cases):
out = []
for args in cases:
try:
frozen, write = m.prepare_request(copy.deepcopy(args))
out.append({"args": args, "ok": {"canonical": json.dumps(frozen, sort_keys=True, separators=(",", ":")), "write": write}})
except Exception as error: # MechanicalError / JournalError
out.append({"args": args, "error": {"code": getattr(error, "code", type(error).__name__), "message": str(error),
"details": getattr(error, "details", {})}})
return out
# ---- history scenarios ----------------------------------------------------------------------
class Clock:
def __init__(self): self.t = 1792500000.0; self.step = 0
def time(self):
self.step += 1
# mostly exact binary fractions, every fifth one a repr-hard value
return self.t + self.step * 0.125 + (0.000001 * self.step if self.step % 5 == 0 else 0)
def history_scenarios(m, jm, hm):
scenarios = []
def fresh(max_records=jm.MAX_RECORDS, fail_on=None):
clock = Clock(); jm.time = NS(time=clock.time)
directory = Path(tempfile.mkdtemp(prefix="mech-golden-"))
class Injecting(jm.Journal):
calls = 0
def transition(self, *a, **k):
Injecting.calls += 1
if fail_on is not None and Injecting.calls == fail_on:
raise jm.JournalError("operation_storage_failed", "OSError: injected persistence failure")
return super().transition(*a, **k)
journal = Injecting(directory, max_records=max_records)
return hm.OperationHistory(SESSION, SOURCE_VERSION, journal), journal
def rows(journal):
import sqlite3
if not journal.path.exists():
return []
con = sqlite3.connect(str(journal.path))
try:
return [{"sessionId": s, "operationId": o, "record": bytes(r).decode("ascii"), "digest": d}
for s, o, r, d in con.execute("SELECT session_id,operation_id,record_json,record_digest FROM operations ORDER BY session_id,operation_id")]
finally:
con.close()
def write_req(op_id, extra=None):
req = {"op": "parameter_set", "sessionId": SESSION, "operationId": op_id, "target": dict(TARGET),
"name": "w", "expression": "42 mm"}
req.update(extra or {})
return json.loads(jm.canonical(req, jm.MAX_REQUEST_BYTES))
def scenario(name, body, **kw):
history, journal = fresh(**kw)
steps = []
def call(kind, *args):
fn = getattr(history, kind)
result = fn(*copy.deepcopy(args))
steps.append({"call": kind, "args": list(args), "out": result})
return result
body(call)
scenarios.append({"name": name, "options": kw, "steps": steps, "rows": rows(journal)})
doc = {"documentId": SESSION + ":doc", "creationId": "c1", "dataFileId": None}
ok = {"success": True, "sessionId": SESSION, "operation": "parameter_set", "data": {"value": 42}, "operationId": "w1"}
bad = {"success": False, "sessionId": SESSION, "errorCode": "expression_invalid", "error": "bad", "operationId": "w2"}
def s_success(call):
r = write_req("w1"); d, rep = call("admit", r, "d-1")
call("start", d, r, doc); call("finish", d, ok)
call("status", {"sessionId": SESSION, "operationId": "w1"})
call("admit", r, "d-2") # replay of a terminal record
scenario("success_then_replay", s_success)
def s_failure(call):
r = write_req("w2"); d, _ = call("admit", r, "d-1"); call("start", d, r, doc); call("finish", d, bad)
call("status", {"sessionId": SESSION, "operationId": "w2"})
scenario("handler_failure", s_failure)
def s_conflict(call):
r = write_req("w3"); d, _ = call("admit", r, "d-1")
call("admit", write_req("w3", {"expression": "43 mm"}), "d-2")
call("admit", r, "d-3") # same request while dispatched: pending reply
call("status", {"sessionId": SESSION, "operationId": "w3"})
scenario("conflict_and_pending", s_conflict)
def s_not_executed(call):
r = write_req("w4"); d, _ = call("admit", r, "d-1")
call("not_executed", d, {"success": False, "errorCode": "request_expired_before_execution", "error": "expired"})
call("start", d, r, doc) # a late handler loses its claim
call("status", {"sessionId": SESSION, "operationId": "w4"})
scenario("not_executed_wins_claim", s_not_executed)
def s_session(call):
call("admit", write_req("w5", {"sessionId": STALE}), "d-1")
call("status", {"sessionId": STALE, "operationId": "w5"})
call("status", {"sessionId": SESSION, "operationId": "nope"})
call("status", {"sessionId": "", "operationId": "x"})
call("status", {"operationId": "x"})
call("not_executed", {"sessionId": STALE, "operationId": "w5", "requestDigest": "0" * 64, "dispatchId": "d"}, {"success": False, "error": "x"})
call("not_executed", None, {"success": False, "error": "passthrough"})
scenario("session_and_unknown", s_session)
def s_capacity(call):
call("admit", write_req("c1"), "d-1"); call("admit", write_req("c2"), "d-2")
scenario("capacity", s_capacity, max_records=1)
def s_storage_after_execution(call):
r = write_req("w6"); d, _ = call("admit", r, "d-1"); call("start", d, r, doc)
call("finish", d, {**ok, "operationId": "w6"}) # 3rd transition fails: dispatched, executing, [terminal]
call("status", {"sessionId": SESSION, "operationId": "w6"})
call("admit", r, "d-2")
scenario("storage_failure_after_execution", s_storage_after_execution, fail_on=3)
def s_storage_at_claim(call):
r = write_req("w7"); call("admit", r, "d-1") # the dispatch claim (1st transition) fails
call("status", {"sessionId": SESSION, "operationId": "w7"})
scenario("storage_failure_at_dispatch_claim", s_storage_at_claim, fail_on=1)
def s_identity(call):
r = write_req("w8"); d, _ = call("admit", r, "d-1")
call("start", {**d, "requestDigest": "f" * 64}, r, doc)
call("start", d, write_req("w8", {"expression": "1 mm"}), doc)
scenario("start_identity_mismatch", s_identity)
return scenarios
def capabilities_golden(m):
caps = m.capabilities(None, {})
caps["sessionId"] = "<session>"
return caps
def child(evidence):
m, jm, hm = load_mechanical()
print(json.dumps({"validation": run_validation(m, corpus(m, evidence))}))
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--evidence", default=str(ROOT.parent / "research" / "fusion-mechanical-verification-2026-10-02"))
ap.add_argument("--child", action="store_true")
a = ap.parse_args()
if a.child:
return child(a.evidence)
runs = []
for seed in ("1", "2", "3", "4", "5", "6", "7", "8"):
env = dict(os.environ, PYTHONHASHSEED=seed)
out = subprocess.run([sys.executable, __file__, "--child", "--evidence", a.evidence], env=env, capture_output=True, text=True, check=True)
runs.append(json.loads(out.stdout)["validation"])
stable = [cases[0] for cases in zip(*runs) if all(c == cases[0] for c in cases)]
# Keep every distinct outcome, at most four of the smallest inputs per (op, outcome).
groups = {}
for case in stable:
op = case["args"].get("op") if isinstance(case["args"], dict) else None
outcome = ("ok", case["ok"]["write"]) if "ok" in case else (case["error"]["code"], case["error"]["message"])
groups.setdefault((str(op), json.dumps(outcome)), []).append(case)
kept = []
for key in sorted(groups):
kept.extend(sorted(groups[key], key=lambda c: len(json.dumps(c["args"])))[:4])
total, stable = len(stable), kept
m, jm, hm = load_mechanical()
meta = {"source": "addin/AdomBridge/commands/mechanical (dev.9 merged onto 1.11.44)", "generator": "tools/gen-mech-goldens.py", "session": SESSION}
GOLDEN.mkdir(parents=True, exist_ok=True)
def dump(name, body):
compact = name == "mech_validation.json"
text = json.dumps({**meta, **body}, indent=None if compact else 1, separators=(",", ":") if compact else None, ensure_ascii=True)
(GOLDEN / name).write_text(text + "\n", encoding="utf-8")
dump("mech_canonical.json", {"cases": canonical_cases(jm)})
dump("mech_validation.json", {"cases": stable, "droppedNondeterministic": len(runs[0]) - total, "sampledFrom": total})
dump("mech_capabilities.json", {"capabilities": capabilities_golden(m), "writes": sorted(m.WRITES)})
dump("mech_history.json", {"sourceVersion": SOURCE_VERSION, "scenarios": history_scenarios(m, jm, hm)})
print("goldens: %d canonical, %d validation kept of %d stable (%d dropped as hash-order dependent), 9 history scenarios"
% (len(CANONICAL_VALUES), len(stable), total, len(runs[0]) - total))
if __name__ == "__main__":
main()