Phase 0: wipe PrimeBAT template, SimHarness (sim+probe), lint + schema harvest tools

Gate G0 green: scan clean, lint 0 failures, clients 200, probe P0 facts captured,
simulator journaling. Probe locked: uuid row grouping, compound AlarmState strings,
java-exception bare-except gotcha, millis dates OK, ackUserName field.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-16 12:07:23 -05:00
parent 310b1b3b9e
commit af662f6fd2
78 changed files with 911 additions and 1957 deletions

View File

@@ -0,0 +1,179 @@
# probe - gateway-side API ground truth (SimHarness, dev-only, Jython 2.7).
#
# Runs inside the gateway via the ProbeTick timer (enable it, scan, wait <=20s,
# read the output, disable it). Writes JSON to the bind-mounted repo path:
# container: /usr/local/bin/ignition/data/projects/.probe/out.json
# host repo: ignition/gateway/projects/.probe/out.json
#
# Edit PROBE_NAME / add sections as needed; every section is try-wrapped so a
# single API surprise never kills the whole probe.
import json
OUT_DIR = "/usr/local/bin/ignition/data/projects/.probe"
PROBE_NAME = "P0"
def _safe(fn):
# Bare except is deliberate: in Jython, JAVA exceptions (e.g. bad kwargs
# into system.* calls) do NOT subclass Python's Exception and would fly
# straight past 'except Exception'.
try:
return {"ok": True, "value": fn()}
except:
import sys
et, ev = sys.exc_info()[0], sys.exc_info()[1]
return {"ok": False, "error": "%s: %s" % (getattr(et, "__name__", et), ev)}
def _write(result):
from java.io import File
File(OUT_DIR).mkdirs()
f = open(OUT_DIR + "/out.json", "w")
try:
f.write(json.dumps(result, indent=1, default=lambda o: repr(o)))
finally:
f.close()
def _prop(evt, names):
out = {}
for n in names:
try:
v = evt.get(n)
out[n] = {"cls": v is not None and v.__class__.__name__ or None,
"repr": repr(v)[:200]}
except Exception, e:
out[n] = {"error": "%s" % e}
return out
def _entry_facts(evt):
d = {}
d["cls"] = evt.__class__.__name__
try:
d["id"] = str(evt.getId())
except Exception, e:
d["id_error"] = "%s" % e
try:
s = evt.getState()
d["state_str"] = str(s)
d["state_cls"] = s is not None and s.__class__.__name__ or None
except Exception, e:
d["state_error"] = "%s" % e
try:
d["source"] = str(evt.getSource())
except Exception, e:
d["source_error"] = "%s" % e
try:
d["displayPath"] = repr(evt.getDisplayPath())
except Exception, e:
d["displayPath_error"] = "%s" % e
try:
p = evt.getPriority()
d["priority_cls"] = p is not None and p.__class__.__name__ or None
d["priority_str"] = str(p)
try:
d["priority_int"] = int(p.ordinal())
except Exception:
try:
d["priority_int"] = int(p)
except Exception:
d["priority_int"] = None
except Exception, e:
d["priority_error"] = "%s" % e
d["props"] = _prop(evt, ["eventTime", "eventType", "eventtype", "ackUser",
"ackUserName", "isSystemEvent", "systemEvent",
"eventValue", "name", "label"])
for getter in ("getActiveData", "getClearedData", "getAckData"):
try:
v = getattr(evt, getter)()
d[getter] = v is not None and repr(v)[:200] or None
except Exception, e:
d[getter] = "ERR %s" % e
try:
from java.util import Date
t = evt.get("eventTime")
if t is not None and isinstance(t, Date):
d["eventTime_ms"] = t.getTime()
except Exception:
pass
return d
def _p0():
result = {}
end = system.date.now()
start = system.date.addHours(end, -8)
def q_basic():
r = system.alarm.queryJournal(startDate=start, endDate=end)
entries = [e for e in r]
by_id = {}
for e in entries:
try:
k = str(e.getId())
except Exception:
k = "?"
by_id.setdefault(k, []).append(e)
multi = {}
n = 0
for k, evts in by_id.items():
if len(evts) > 1 and n < 5:
states = []
for e in evts:
try:
states.append(str(e.getState()))
except Exception:
states.append("?")
multi[k] = states
n += 1
return {
"result_cls": r.__class__.__name__,
"len_works": _safe(lambda: len(r)),
"dataset_attr": hasattr(r, "getDataset"),
"entry_count": len(entries),
"distinct_ids": len(by_id),
"multi_state_id_samples": multi,
"first_entries": [_entry_facts(e) for e in entries[:6]],
"system_rows": [_entry_facts(e) for e in entries
if str(_safe(lambda: str(e.getSource()))["value"]).startswith("evt:")][:3],
}
result["basic"] = _safe(q_basic)
result["no_kwargs_at_all"] = _safe(
lambda: len([e for e in system.alarm.queryJournal(
startDate=start, endDate=end)]))
result["with_journal_name"] = _safe(
lambda: len([e for e in system.alarm.queryJournal(
journalName="Journal", startDate=start, endDate=end)]))
result["bad_journal_name"] = _safe(
lambda: len([e for e in system.alarm.queryJournal(
journalName="NoSuchJournal", startDate=start, endDate=end)]))
result["long_millis_dates"] = _safe(
lambda: len([e for e in system.alarm.queryJournal(
startDate=system.date.toMillis(start),
endDate=system.date.toMillis(end))]))
result["include_system_false"] = _safe(
lambda: len([e for e in system.alarm.queryJournal(
startDate=start, endDate=end, includeSystemEvents=False)]))
result["include_system_false_alt"] = _safe(
lambda: len([e for e in system.alarm.queryJournal(
startDate=start, endDate=end, isSystem=False)]))
def q_status():
r = system.alarm.queryStatus(state=["ActiveUnacked", "ActiveAcked"])
entries = [e for e in r]
return {"count": len(entries),
"first_entries": [_entry_facts(e) for e in entries[:5]]}
result["status"] = _safe(q_status)
result["shelved"] = _safe(lambda: repr(system.alarm.getShelvedPaths()))
return result
def run():
result = {"probe": PROBE_NAME,
"ranAt": str(system.date.now())}
result["payload"] = _safe(_p0)
_write(result)
system.util.getLogger("probe").info("probe %s written to %s" % (PROBE_NAME, OUT_DIR))