Fix golden audit issues and tighten analysis/export pipeline.

Resolve property placeholders before call-chain linking, skip phantom routing states and lifecycle triggers from transition matching, dedupe exported states, and add golden JSON audit regression plus HTML lifecycle endpoint display.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-12 05:49:25 +02:00
parent 4f13c9449c
commit b158179559
53 changed files with 882 additions and 2057 deletions

View File

@@ -0,0 +1,168 @@
#!/usr/bin/env python3
"""Deep audit of golden JSON analysis exports."""
import json
import re
from collections import defaultdict
from pathlib import Path
GOLDEN = Path(__file__).resolve().parent.parent / "src/test/resources/golden"
PH = re.compile(r"\$\{")
def fn_form(identifier):
if not identifier:
return identifier
last = identifier.rfind(".")
if last <= 0:
return identifier
prev = identifier.rfind(".", 0, last)
return identifier[prev + 1 :] if prev > 0 else identifier
def sid(st):
if isinstance(st, dict):
return st.get("fullIdentifier") or st.get("rawName")
return st
def eid(ev):
if isinstance(ev, dict):
return ev.get("fullIdentifier") or ev.get("rawName")
return ev
def audit_file(path, data):
name = data.get("name", path.stem)
issues = []
st_fqn = data.get("stateTypeFqn")
ev_fqn = data.get("eventTypeFqn")
transitions = data.get("transitions") or []
starts = list(data.get("startStates") or [])
ends = list(data.get("endStates") or [])
states = data.get("states") or []
meta = data.get("metadata") or {}
is_pkg_enum = st_fqn and "." in st_fqn and st_fqn not in ("String", "java.lang.String")
is_string = st_fqn in (None, "String", "java.lang.String") or (
not st_fqn and not ev_fqn
)
if PH.search(json.dumps(data)):
issues.append("BUG: unresolved ${placeholder} in export")
# Duplicate fullIdentifiers in states[]
by_full = defaultdict(list)
for s in states:
by_full[sid(s)].append(s.get("rawName"))
for fid, raws in by_full.items():
if fid and len(raws) > 1 and len(set(raws)) > 1:
issues.append(f"NOISE: duplicate state entry {fid!r} with rawNames {raws}")
# Collect transition state/event ids
t_events, t_sources, t_targets = set(), set(), set()
for t in transitions:
if eid(t.get("event")):
t_events.add(eid(t.get("event")))
for s in t.get("sourceStates") or []:
t_sources.add(sid(s))
for s in t.get("targetStates") or []:
t_targets.add(sid(s))
graph = t_sources | t_targets
# startStates canonical consistency
for ss in starts:
if is_string and ss and not ss.startswith("String."):
if any(x.startswith("String.") for x in graph):
issues.append(f"BUG: startStates {ss!r} bare but transitions use String.* form")
if ss not in graph and fn_form(ss) not in {fn_form(x) for x in graph}:
issues.append(f"WARN: startStates {ss!r} not in transition graph")
# endStates
for es in ends:
if es not in graph and fn_form(es) not in {fn_form(x) for x in graph}:
issues.append(f"WARN: endStates {es!r} not in transition graph")
# Enum canonical fullIdentifiers
if is_pkg_enum:
for i, t in enumerate(transitions):
for side in ("sourceStates", "targetStates"):
for s in t.get(side) or []:
fid = sid(s)
if fid and not fid.startswith(st_fqn + ".") and not fid.startswith('"'):
if "." in fid and not fid.startswith("<"):
issues.append(f"BUG: transitions[{i}].{side} {fid!r} not under {st_fqn}")
ev = eid(t.get("event"))
if ev and ev_fqn and "." in ev_fqn and not ev.startswith("<") and not ev.startswith(ev_fqn + "."):
if ev not in ("event",) and "." in ev and not ev.startswith(ev_fqn):
issues.append(f"BUG: transitions[{i}].event {ev!r} not under {ev_fqn}")
for i, tr in enumerate(meta.get("triggers") or []):
for field in ("event", "sourceState"):
val = tr.get(field)
if not val or val in ("event", "payload", "customMessage", "eventProvider"):
continue
type_fqn = ev_fqn if field == "event" else st_fqn
if type_fqn and "." in val and not val.startswith("<") and not val.startswith(type_fqn + "."):
if field == "sourceState" and val not in graph and fn_form(val) not in {fn_form(x) for x in graph}:
issues.append(f"BUG: triggers[{i}].sourceState {val!r} not a machine state")
elif field == "event" and type_fqn and val.isupper() is False and "." not in val and not val.startswith("<"):
if val not in ("event", "payload", "customMessage", "eventProvider", "true", "false"):
issues.append(f"INFO: triggers[{i}].event dynamic {val!r}")
# matchedTransitions
for i, chain in enumerate(meta.get("callChains") or []):
for j, mt in enumerate(chain.get("matchedTransitions") or []):
me, ms, mtgt = mt.get("event"), mt.get("sourceState"), mt.get("targetState")
ok = False
for t in transitions:
if fn_form(eid(t.get("event"))) != fn_form(me):
continue
srcs = [sid(s) for s in t.get("sourceStates") or []]
tgts = [sid(s) for s in t.get("targetStates") or []]
if (not ms or any(fn_form(x) == fn_form(ms) for x in srcs)) and (
not mtgt or any(fn_form(x) == fn_form(mtgt) for x in tgts)
):
ok = True
break
if not ok:
issues.append(
f"BUG: callChains[{i}].matchedTransitions[{j}] ({ms!r},{me!r},{mtgt!r}) no transition"
)
# String machine: mixed NEW vs String.NEW
if is_string or st_fqn == "String":
all_ids = set(graph) | set(starts) | set(ends) | {sid(s) for s in states}
for x in list(all_ids):
if not x:
continue
bare = x.strip('"')
if bare and "." not in bare and f"String.{bare}" in all_ids:
issues.append(f"BUG: mixed bare {x!r} and String.{bare}")
# stateTypeFqn missing on enum machine
if is_pkg_enum and not st_fqn:
issues.append("BUG: enum machine missing stateTypeFqn")
return name, issues
def main():
total = 0
for path in sorted(GOLDEN.rglob("*.json")):
data = json.loads(path.read_text())
machine, issues = audit_file(path, data)
rel = path.relative_to(GOLDEN)
bugs = [i for i in issues if i.startswith("BUG")]
warns = [i for i in issues if i.startswith("WARN")]
noise = [i for i in issues if i.startswith("NOISE")]
info = [i for i in issues if i.startswith("INFO")]
if bugs or warns:
print(f"\n{'='*70}\n{rel}\n machine: {machine}")
for i in bugs + warns + noise + info:
print(f" [{i.split(':')[0]}] {i.split(': ', 1)[-1]}")
total += len(bugs) + len(warns)
print(f"\nTotal BUG+WARN: {total}")
if __name__ == "__main__":
main()