@h2loop/autosar 1.5.4 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/agents/autosar-integrator.md +1 -0
- package/package.json +1 -1
- package/plugins/index.js +75 -64
- package/python/ecuc_engine/add_like.py +475 -0
- package/python/ecuc_engine/add_signal.py +13 -3
- package/python/ecuc_engine/cli.py +22 -2
- package/python/ecuc_engine/rules/__init__.py +1 -1
- package/python/ecuc_engine/rules/chain.py +288 -0
- package/python/ecuc_engine/validate.py +68 -1
- package/skills/autosar/SKILL.md +24 -2
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"""Completeness of the communication chain: what a message / signal needs in every layer and file.
|
|
2
|
+
|
|
3
|
+
Consistency rules (overlap, ranges, extract layout) do not see an UNFINISHED change: a new message with no PduR
|
|
4
|
+
route or no CanIf PDU, a signal added to the extract but not to Com, a ComSignal attached to no I-PDU, an I-PDU
|
|
5
|
+
triggered on a channel but carried by no frame. These rules do, so the verify -> fix loop has something to fix.
|
|
6
|
+
Nothing is keyed by names: navigation is by ECUC definition (module-relative) and by reference.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from . import rule
|
|
11
|
+
from .system import mappings_for, system_ipdus
|
|
12
|
+
|
|
13
|
+
CITE_ROUTE = ("SWS_PduR (an I-PDU handled by Com is routed by a PduR routing path to/from a lower-layer PDU); "
|
|
14
|
+
"SWS_CanIf CanIfTxPduRef/CanIfRxPduRef; Com ComPduIdRef")
|
|
15
|
+
CITE_SIG = "Com ComIPduSignalRef (each ComSignal belongs to an I-PDU)"
|
|
16
|
+
CITE_EXTRACT = "System Template ISignalToIPduMapping vs ECUC_Com ComSignal (the ECU configuration implements the extract)"
|
|
17
|
+
CITE_TRIG = "System Template PduTriggering/FrameTriggering/ISignalTriggering (what is sent on a channel is triggered)"
|
|
18
|
+
UPPER = {"Com", "PduR", "EcuC", "EcucValueCollection"}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _module(ctx, e) -> str:
|
|
22
|
+
return ctx.module_of(e).name
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _pdu_users(ctx) -> dict:
|
|
26
|
+
"""EcuC Pdu path -> {module names of containers that reference it}."""
|
|
27
|
+
users: dict = {}
|
|
28
|
+
for path, group in ctx.p.by_path.items():
|
|
29
|
+
e = group[0]
|
|
30
|
+
if e.kind != "container":
|
|
31
|
+
continue
|
|
32
|
+
mod = _module(ctx, e)
|
|
33
|
+
for g in group:
|
|
34
|
+
for pv in g.params:
|
|
35
|
+
if pv.is_ref and pv.value:
|
|
36
|
+
users.setdefault(pv.value.strip(), set()).add(mod)
|
|
37
|
+
return users
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _routes(ctx) -> dict:
|
|
41
|
+
"""EcuC Pdu path -> set of EcuC Pdu paths at the other end of a PduR routing path."""
|
|
42
|
+
out: dict = {}
|
|
43
|
+
for rp in ctx.containers("PduR/PduRRoutingPaths/PduRRoutingPath"):
|
|
44
|
+
src = ctx.target(ctx.value(rp, "PduRSrcPduRRef"))
|
|
45
|
+
spdu = ctx.target(ctx.value(src, "PduRSrcPduRef")) if src is not None else None
|
|
46
|
+
for dpv in ctx.values(rp, "PduRDestPduRRef"):
|
|
47
|
+
d = ctx.target(dpv)
|
|
48
|
+
dpdu = ctx.target(ctx.value(d, "PduRDestPduRef")) if d is not None else None
|
|
49
|
+
if spdu is not None and dpdu is not None:
|
|
50
|
+
out.setdefault(spdu.path, set()).add(dpdu.path)
|
|
51
|
+
out.setdefault(dpdu.path, set()).add(spdu.path)
|
|
52
|
+
return out
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@rule("H2L-CHAIN-001", "error", CITE_ROUTE, "every Com I-PDU is routed by PduR to/from a lower-layer (bus) PDU")
|
|
56
|
+
def com_ipdu_routed(ctx):
|
|
57
|
+
if not ctx.containers("PduR/PduRRoutingPaths/PduRRoutingPath"):
|
|
58
|
+
return # no PduR configured in this project: nothing to check against
|
|
59
|
+
users = _pdu_users(ctx)
|
|
60
|
+
routes = _routes(ctx)
|
|
61
|
+
for ip in ctx.containers("Com/ComConfig/ComIPdu"):
|
|
62
|
+
pv = ctx.value(ip, "ComPduIdRef")
|
|
63
|
+
pdu = ctx.target(pv)
|
|
64
|
+
if pdu is None:
|
|
65
|
+
yield ip, ip.line, f"{ip.name}: ComPduIdRef is missing or points nowhere, so the I-PDU has no EcuC Pdu", None
|
|
66
|
+
continue
|
|
67
|
+
ends = routes.get(pdu.path, set())
|
|
68
|
+
if not ends:
|
|
69
|
+
yield ip, ip.line, (f"{ip.name}: no PduR routing path has its EcuC Pdu {pdu.name} as source or destination, "
|
|
70
|
+
"so Com can neither send nor receive it (add the routing path)"), pv.def_ref
|
|
71
|
+
continue
|
|
72
|
+
lower = [e for e in ends if users.get(e, set()) - UPPER]
|
|
73
|
+
if not lower:
|
|
74
|
+
names = ", ".join(sorted(p.rsplit("/", 1)[-1] for p in ends))
|
|
75
|
+
yield ip, ip.line, (f"{ip.name}: routed to/from {names}, but no lower-layer module (CanIf, LinIf, FrIf, "
|
|
76
|
+
"SoAd, ...) refers to that Pdu, so nothing reaches the bus (add the bus-interface PDU)"), pv.def_ref
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@rule("H2L-CHAIN-002", "error", CITE_SIG, "every ComSignal belongs to a Com I-PDU")
|
|
80
|
+
def com_signal_attached(ctx):
|
|
81
|
+
owned = {pv.value.strip() for ip in ctx.containers("Com/ComConfig/ComIPdu")
|
|
82
|
+
for pv in ctx.values(ip, "ComIPduSignalRef") if pv.value}
|
|
83
|
+
for s in ctx.containers("Com/ComConfig/ComSignal"):
|
|
84
|
+
if s.path not in owned:
|
|
85
|
+
yield s, s.line, f"{s.name}: no ComIPdu lists it in ComIPduSignalRef, so it is never sent or received", None
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@rule("H2L-CHAIN-003", "error", CITE_EXTRACT, "every signal the extract maps into a Com I-PDU has a ComSignal")
|
|
89
|
+
def extract_signals_have_comsignals(ctx):
|
|
90
|
+
sysi = ctx.p.system
|
|
91
|
+
if not sysi.by_path:
|
|
92
|
+
return
|
|
93
|
+
for ip in ctx.containers("Com/ComConfig/ComIPdu"):
|
|
94
|
+
pdu = ctx.target(ctx.value(ip, "ComPduIdRef"))
|
|
95
|
+
s_ip = system_ipdus(ctx, pdu)
|
|
96
|
+
if not s_ip:
|
|
97
|
+
continue
|
|
98
|
+
mapped = {(m.parent, m.one("I-SIGNAL-REF")) for x in s_ip for m in
|
|
99
|
+
(sysi.get(c) for c in (sysi.get(x).children if sysi.get(x) else [])) if m is not None and m.one("I-SIGNAL-REF")}
|
|
100
|
+
if not mapped:
|
|
101
|
+
continue
|
|
102
|
+
covered = set()
|
|
103
|
+
linked_any = False
|
|
104
|
+
members = [ctx.target(pv) for pv in ctx.values(ip, "ComIPduSignalRef")]
|
|
105
|
+
for pv in ctx.values(ip, "ComIPduSignalGroupRef"): # signal groups: their ComGroupSignals implement members
|
|
106
|
+
grp = ctx.target(pv)
|
|
107
|
+
if grp is not None:
|
|
108
|
+
members.append(grp)
|
|
109
|
+
members.extend(c for c in grp.children if ctx.rel(c.def_ref).endswith("ComGroupSignal"))
|
|
110
|
+
for s in members:
|
|
111
|
+
if s is None:
|
|
112
|
+
continue
|
|
113
|
+
ms = mappings_for(ctx, s, s_ip)
|
|
114
|
+
linked_any = linked_any or bool(ms)
|
|
115
|
+
covered |= {m.one("I-SIGNAL-REF") for m in ms}
|
|
116
|
+
if not linked_any and (ctx.values(ip, "ComIPduSignalRef") or ctx.values(ip, "ComIPduSignalGroupRef")):
|
|
117
|
+
continue # this project's ComSignals do not link to the extract at all: nothing to compare
|
|
118
|
+
for parent, isig in sorted(mapped):
|
|
119
|
+
if isig not in covered:
|
|
120
|
+
yield ip, ip.line, (f"{ip.name}: the extract maps I-SIGNAL {isig.rsplit('/', 1)[-1]} into {parent} "
|
|
121
|
+
"but no ComSignal of this I-PDU implements it (add the ComSignal, e.g. "
|
|
122
|
+
"autosar_add_signal / autosar_add_like)"), None
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@rule("H2L-CHAIN-004", "warning", CITE_TRIG, "a triggered I-PDU is carried by a triggered frame; its signals are triggered")
|
|
126
|
+
def extract_triggering_complete(ctx):
|
|
127
|
+
sysi = ctx.p.system
|
|
128
|
+
if not sysi.by_path:
|
|
129
|
+
return
|
|
130
|
+
frame_of = {}
|
|
131
|
+
for m in sysi.tags("PDU-TO-FRAME-MAPPING"):
|
|
132
|
+
for p in m.values.get("PDU-REF", []):
|
|
133
|
+
frame_of.setdefault(p, set()).add(m.parent)
|
|
134
|
+
triggered_frames = {ft.one("FRAME-REF") for tag in ("CAN-FRAME-TRIGGERING", "FLEXRAY-FRAME-TRIGGERING",
|
|
135
|
+
"LIN-FRAME-TRIGGERING", "ETHERNET-FRAME-TRIGGERING")
|
|
136
|
+
for ft in sysi.tags(tag)}
|
|
137
|
+
lines = {}
|
|
138
|
+
for pt in sysi.tags("PDU-TRIGGERING"):
|
|
139
|
+
ipdu = pt.one("I-PDU-REF")
|
|
140
|
+
ii = sysi.get(ipdu)
|
|
141
|
+
if ii is None or ii.tag != "I-SIGNAL-I-PDU":
|
|
142
|
+
continue
|
|
143
|
+
frames = frame_of.get(ipdu, set())
|
|
144
|
+
if frames and not (frames & triggered_frames):
|
|
145
|
+
lines.setdefault(pt.path, []).append(
|
|
146
|
+
f"I-PDU {ipdu.rsplit('/', 1)[-1]} is triggered on {pt.parent} but its frame(s) "
|
|
147
|
+
f"{', '.join(sorted(f.rsplit('/', 1)[-1] for f in frames))} have no frame triggering")
|
|
148
|
+
trig = {sysi.get(st).one("I-SIGNAL-REF") for st in pt.values.get("I-SIGNAL-TRIGGERING-REF", []) if sysi.get(st)}
|
|
149
|
+
if not pt.values.get("I-SIGNAL-TRIGGERING-REF"):
|
|
150
|
+
continue # this project does not list signal triggerings on PDU triggerings
|
|
151
|
+
for c in ii.children:
|
|
152
|
+
m = sysi.get(c)
|
|
153
|
+
isig = m.one("I-SIGNAL-REF") if m is not None else None
|
|
154
|
+
if isig and isig not in trig:
|
|
155
|
+
lines.setdefault(pt.path, []).append(
|
|
156
|
+
f"I-SIGNAL {isig.rsplit('/', 1)[-1]} is mapped into {ipdu.rsplit('/', 1)[-1]} but {pt.path.rsplit('/', 1)[-1]} "
|
|
157
|
+
"has no I-SIGNAL-TRIGGERING for it")
|
|
158
|
+
if not lines:
|
|
159
|
+
return
|
|
160
|
+
# report against an ECUC element is the framework's shape; anchor each finding on a Com I-PDU that uses it
|
|
161
|
+
anchors = {}
|
|
162
|
+
for ip in ctx.containers("Com/ComConfig/ComIPdu"):
|
|
163
|
+
pdu = ctx.target(ctx.value(ip, "ComPduIdRef"))
|
|
164
|
+
for x in system_ipdus(ctx, pdu):
|
|
165
|
+
anchors.setdefault(x, ip)
|
|
166
|
+
for ptp, msgs in sorted(lines.items()):
|
|
167
|
+
pt = sysi.get(ptp)
|
|
168
|
+
ip = anchors.get(pt.one("I-PDU-REF")) if pt else None
|
|
169
|
+
if ip is None:
|
|
170
|
+
continue
|
|
171
|
+
for msg in msgs:
|
|
172
|
+
yield ip, ip.line, msg, None
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _mapped_system_signals(sysi) -> set:
|
|
176
|
+
"""System signals that a SYSTEM-MAPPING maps to an SWC data element (SENDER-RECEIVER-TO-SIGNAL-MAPPING)."""
|
|
177
|
+
out = set()
|
|
178
|
+
for sm in sysi.tags("SYSTEM-MAPPING"):
|
|
179
|
+
out.update(sm.values.get("SYSTEM-SIGNAL-REF", []))
|
|
180
|
+
return out
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
@rule("H2L-CHAIN-005", "warning", "System Template SenderReceiverToSignalMapping (application data reaches the bus "
|
|
184
|
+
"through a data element mapped to the system signal)", "a sent signal is mapped to an SWC data element")
|
|
185
|
+
def sent_signal_mapped_to_swc(ctx):
|
|
186
|
+
sysi = ctx.p.system
|
|
187
|
+
if not sysi.by_path:
|
|
188
|
+
return
|
|
189
|
+
mapped = _mapped_system_signals(sysi)
|
|
190
|
+
if not mapped:
|
|
191
|
+
return # this project maps no data elements to signals: nothing to compare against
|
|
192
|
+
for ip in ctx.containers("Com/ComConfig/ComIPdu"):
|
|
193
|
+
if (ctx.text(ip, "ComIPduDirection") or "").upper() != "SEND":
|
|
194
|
+
continue
|
|
195
|
+
pdu = ctx.target(ctx.value(ip, "ComPduIdRef"))
|
|
196
|
+
s_ip = system_ipdus(ctx, pdu)
|
|
197
|
+
sigs = [ctx.target(pv) for pv in ctx.values(ip, "ComIPduSignalRef")]
|
|
198
|
+
linked = [(s, mappings_for(ctx, s, s_ip)) for s in sigs if s is not None]
|
|
199
|
+
# only where the I-PDU's other signals ARE mapped: a whole BSW-filled I-PDU (counters, NM) is not flagged
|
|
200
|
+
has = [s for s, ms in linked if any(sysi.get(m.one("I-SIGNAL-REF")) and
|
|
201
|
+
sysi.get(m.one("I-SIGNAL-REF")).one("SYSTEM-SIGNAL-REF") in mapped for m in ms)]
|
|
202
|
+
if not has and len(linked) > 1:
|
|
203
|
+
continue
|
|
204
|
+
for s, ms in linked:
|
|
205
|
+
ss = {sysi.get(m.one("I-SIGNAL-REF")).one("SYSTEM-SIGNAL-REF") for m in ms if sysi.get(m.one("I-SIGNAL-REF"))}
|
|
206
|
+
if ms and not (ss & mapped):
|
|
207
|
+
yield s, s.line, (f"{s.name} (sent in {ip.name}): its system signal {', '.join(sorted(x.rsplit('/', 1)[-1] for x in ss if x))} "
|
|
208
|
+
"is not mapped to any SWC data element (no SENDER-RECEIVER-TO-SIGNAL-MAPPING), so no "
|
|
209
|
+
"application value reaches it; map the providing port's data element to it"), None
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
@rule("H2L-CHAIN-006", "warning", "SWS_Rte data send/receive points (a port no runnable accesses is never served)",
|
|
213
|
+
"every sender-receiver port of an SWC is accessed by a runnable")
|
|
214
|
+
def port_accessed(ctx):
|
|
215
|
+
sysi = ctx.p.system
|
|
216
|
+
if not sysi.by_path:
|
|
217
|
+
return
|
|
218
|
+
accessed = set()
|
|
219
|
+
for tag in ("VARIABLE-ACCESS", "RUNNABLE-ENTITY", "SWC-INTERNAL-BEHAVIOR"):
|
|
220
|
+
for i in sysi.tags(tag):
|
|
221
|
+
accessed.update(i.values.get("PORT-PROTOTYPE-REF", []))
|
|
222
|
+
if not accessed:
|
|
223
|
+
return
|
|
224
|
+
swcs = [i for t in ("APPLICATION-SW-COMPONENT-TYPE", "SENSOR-ACTUATOR-SW-COMPONENT-TYPE") for i in sysi.tags(t)]
|
|
225
|
+
anchors = ctx.containers("Rte/RteSwComponentInstance") or ctx.containers("Com/ComConfig/ComIPdu")
|
|
226
|
+
for swc in swcs:
|
|
227
|
+
ports = [sysi.get(c) for c in swc.children]
|
|
228
|
+
sr = [p for p in ports if p is not None and p.tag in ("P-PORT-PROTOTYPE", "R-PORT-PROTOTYPE")
|
|
229
|
+
and (sysi.get(p.one("PROVIDED-INTERFACE-TREF") or p.one("REQUIRED-INTERFACE-TREF")) or p).tag == "SENDER-RECEIVER-INTERFACE"]
|
|
230
|
+
if not any(p.path in accessed for p in sr):
|
|
231
|
+
continue # this SWC has no accessed S/R port at all (not modelled to that depth)
|
|
232
|
+
for p in sr:
|
|
233
|
+
if p.path not in accessed and anchors:
|
|
234
|
+
a = anchors[0]
|
|
235
|
+
yield a, a.line, (f"port {p.path} of {swc.path.rsplit('/', 1)[-1]} is not accessed by any runnable "
|
|
236
|
+
"(no data send/receive point refers to it), so the RTE never reads or writes it"), None
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def _ident_int(v):
|
|
240
|
+
try:
|
|
241
|
+
return int(str(v).strip(), 0)
|
|
242
|
+
except (TypeError, ValueError):
|
|
243
|
+
return None
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
@rule("H2L-CHAIN-007", "error", "System Template FrameTriggering.identifier (one frame per CAN id on a channel)",
|
|
247
|
+
"two frames do not use the same CAN id on one channel")
|
|
248
|
+
def frame_ids_unique(ctx):
|
|
249
|
+
sysi = ctx.p.system
|
|
250
|
+
if not sysi.by_path:
|
|
251
|
+
return
|
|
252
|
+
seen: dict = {}
|
|
253
|
+
for ft in sysi.tags("CAN-FRAME-TRIGGERING"):
|
|
254
|
+
i = _ident_int(ft.one("IDENTIFIER"))
|
|
255
|
+
if i is not None:
|
|
256
|
+
seen.setdefault((ft.parent, i), []).append(ft)
|
|
257
|
+
anchors = ctx.containers("CanIf/CanIfInitCfg/CanIfTxPduCfg") or ctx.containers("Com/ComConfig/ComIPdu")
|
|
258
|
+
if not anchors:
|
|
259
|
+
return
|
|
260
|
+
for (ch, i), fts in sorted(seen.items(), key=lambda x: (x[0][0] or "", x[0][1])):
|
|
261
|
+
if len(fts) > 1:
|
|
262
|
+
a = anchors[0]
|
|
263
|
+
yield a, a.line, (f"CAN id 0x{i:X} is used by {len(fts)} frame triggerings on {ch}: "
|
|
264
|
+
f"{', '.join(f.path.rsplit('/', 1)[-1] for f in fts)} (a new frame kept its template's id?)"), None
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
@rule("H2L-SYS-003", "error", "System Template CanFrameTriggering.identifier vs CanIf CanIfTxPduCanId/CanIfRxPduCanId",
|
|
268
|
+
"a CanIf PDU's CAN id equals the frame triggering id of the frame that carries it")
|
|
269
|
+
def canif_id_matches_extract(ctx):
|
|
270
|
+
sysi = ctx.p.system
|
|
271
|
+
if not sysi.by_path:
|
|
272
|
+
return
|
|
273
|
+
ft_by_ipdu: dict = {}
|
|
274
|
+
for ft in sysi.tags("CAN-FRAME-TRIGGERING"):
|
|
275
|
+
for ip in sysi.ipdus_of_triggering(ft.path):
|
|
276
|
+
ft_by_ipdu.setdefault(ip, []).append(ft)
|
|
277
|
+
for kind, idp, refp in (("Tx", "CanIfTxPduCanId", "CanIfTxPduRef"), ("Rx", "CanIfRxPduCanId", "CanIfRxPduRef")):
|
|
278
|
+
for c in ctx.containers(f"CanIf/CanIfInitCfg/CanIf{kind}PduCfg"):
|
|
279
|
+
pv = ctx.value(c, idp)
|
|
280
|
+
cid = _ident_int(pv.value) if pv is not None else None
|
|
281
|
+
pdu = ctx.target(ctx.value(c, refp))
|
|
282
|
+
if cid is None or pdu is None:
|
|
283
|
+
continue
|
|
284
|
+
fts = [ft for ip in system_ipdus(ctx, pdu) for ft in ft_by_ipdu.get(ip, [])]
|
|
285
|
+
ids = {_ident_int(ft.one("IDENTIFIER")) for ft in fts} - {None}
|
|
286
|
+
if ids and cid not in ids:
|
|
287
|
+
yield c, pv.node.line, (f"{c.name}: {idp}=0x{cid:X} but the extract sends its I-PDU in a frame triggered "
|
|
288
|
+
f"with id {', '.join(f'0x{x:X}' for x in sorted(ids))}"), pv.def_ref
|
|
@@ -23,6 +23,8 @@ Hand-written semantic rules (ecuc_engine/rules, ids H2L-*) run afterwards, with
|
|
|
23
23
|
"""
|
|
24
24
|
from __future__ import annotations
|
|
25
25
|
|
|
26
|
+
import os
|
|
27
|
+
|
|
26
28
|
from typing import Optional
|
|
27
29
|
|
|
28
30
|
from . import defs as D
|
|
@@ -211,7 +213,8 @@ def validate(project: Project, files: Optional[list] = None, module: Optional[st
|
|
|
211
213
|
check_elem_group(m.path)
|
|
212
214
|
|
|
213
215
|
from .rules import run_rules
|
|
214
|
-
|
|
216
|
+
found = list(run_rules(project))
|
|
217
|
+
dg.items.extend(_delta_only(project, found))
|
|
215
218
|
from .probe import load_cfg
|
|
216
219
|
cfg = load_cfg(project.root)
|
|
217
220
|
if code: # configuration completeness seen from the C code: handles used but not configured
|
|
@@ -282,3 +285,67 @@ def validate(project: Project, files: Optional[list] = None, module: Optional[st
|
|
|
282
285
|
def _nomatch(project: Project, msg: str) -> dict:
|
|
283
286
|
return {"file": "", "line": 0, "path": project.root, "rule": "H2L-FILTER-NOMATCH", "severity": "error",
|
|
284
287
|
"message": msg, "defRef": None}
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
# Convention rules (a sent signal mapped to an SWC data element, a port accessed by a runnable) are true for what a
|
|
291
|
+
# change adds but not for every project's legacy (BSW-filled counters, unused ports). They report only findings that
|
|
292
|
+
# the last commit did not already have, i.e. what this working copy's changes left incomplete. Without git (or
|
|
293
|
+
# without a commit) they report nothing.
|
|
294
|
+
DELTA_RULES = ("H2L-CHAIN-005", "H2L-CHAIN-006")
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def _delta_only(project: Project, found: list) -> list:
|
|
298
|
+
keep = [d for d in found if d["rule"] not in DELTA_RULES]
|
|
299
|
+
delta = [d for d in found if d["rule"] in DELTA_RULES]
|
|
300
|
+
if not delta:
|
|
301
|
+
return keep
|
|
302
|
+
base = _baseline_findings(project)
|
|
303
|
+
if base is None:
|
|
304
|
+
return keep
|
|
305
|
+
import re as _re
|
|
306
|
+
key = lambda d: (d["rule"], d["path"], _re.sub(r"(\.arxml|\.ARXML):\d+", r"\1", d["message"])) # noqa: E731
|
|
307
|
+
return keep + [d for d in delta if key(d) not in {key(b) for b in base}]
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def _baseline_findings(project: Project) -> Optional[list]:
|
|
311
|
+
"""DELTA_RULES findings on the files as of git HEAD (None when there is no usable HEAD)."""
|
|
312
|
+
import shutil
|
|
313
|
+
import subprocess
|
|
314
|
+
import tempfile
|
|
315
|
+
root = project.root
|
|
316
|
+
try:
|
|
317
|
+
top = subprocess.run(["git", "-C", root, "rev-parse", "--show-toplevel"], capture_output=True, text=True,
|
|
318
|
+
timeout=20).stdout.strip()
|
|
319
|
+
if not top or subprocess.run(["git", "-C", root, "rev-parse", "--verify", "-q", "HEAD"], capture_output=True,
|
|
320
|
+
timeout=20).returncode != 0:
|
|
321
|
+
return None
|
|
322
|
+
except (OSError, subprocess.SubprocessError):
|
|
323
|
+
return None
|
|
324
|
+
files = list(project.sources.config) + list(project.sources.input)
|
|
325
|
+
tmp = tempfile.mkdtemp(prefix="ecuc-base-")
|
|
326
|
+
try:
|
|
327
|
+
for f in files:
|
|
328
|
+
rel_top = os.path.relpath(os.path.realpath(f), os.path.realpath(top))
|
|
329
|
+
r = subprocess.run(["git", "-C", top, "show", f"HEAD:{rel_top}"], capture_output=True, timeout=60)
|
|
330
|
+
if r.returncode != 0:
|
|
331
|
+
continue # new in this working copy: absent from the baseline
|
|
332
|
+
dst = os.path.join(tmp, os.path.relpath(f, root))
|
|
333
|
+
os.makedirs(os.path.dirname(dst), exist_ok=True)
|
|
334
|
+
with open(dst, "wb") as fh:
|
|
335
|
+
fh.write(r.stdout)
|
|
336
|
+
cfg = [os.path.join(tmp, os.path.relpath(f, root)) for f in project.sources.config
|
|
337
|
+
if os.path.exists(os.path.join(tmp, os.path.relpath(f, root)))]
|
|
338
|
+
inp = os.path.join(tmp, ".hydron")
|
|
339
|
+
os.makedirs(inp, exist_ok=True)
|
|
340
|
+
import json as _json
|
|
341
|
+
with open(os.path.join(inp, "autosar.json"), "w") as fh:
|
|
342
|
+
_json.dump({"input": [os.path.relpath(os.path.join(tmp, os.path.relpath(f, root)), tmp)
|
|
343
|
+
for f in project.sources.input
|
|
344
|
+
if os.path.exists(os.path.join(tmp, os.path.relpath(f, root)))]}, fh)
|
|
345
|
+
from .rules import run_rules
|
|
346
|
+
bp = Project(tmp, config_files=cfg or None, def_files=project.sources.defs or None)
|
|
347
|
+
return [d for d in run_rules(bp) if d["rule"] in DELTA_RULES]
|
|
348
|
+
except Exception: # noqa: BLE001 -- a baseline that cannot be built just turns the delta rules off
|
|
349
|
+
return None
|
|
350
|
+
finally:
|
|
351
|
+
shutil.rmtree(tmp, ignore_errors=True)
|
package/skills/autosar/SKILL.md
CHANGED
|
@@ -71,6 +71,7 @@ grounding, no tools, no exploring the project.
|
|
|
71
71
|
| Verify ALL ARXML (ECUC rules + extract/SWC schema, references, DEST) | `ecuc_validate` (unfiltered) |
|
|
72
72
|
| Edit System extract / SWC / interface XML | `arxml_edit` (DEST inferred, schema-ordered, refs checked) |
|
|
73
73
|
| Add a signal end to end (ECUC + extract + DBC) | `autosar_add_signal` |
|
|
74
|
+
| Add a new message / port / runnable+event / Dem event / NvM block / interface **like an existing one**, in every file at once | `autosar_add_like like=<existing stem> name=<new stem> also={member: new} set={...}` |
|
|
74
75
|
| What may be set/added here (types, ranges, literals, multiplicity) | `ecuc_read path=… schema=true`, or a def path `/AUTOSAR/EcucDefs/<Mod>/…` |
|
|
75
76
|
| Change config | `ecuc_edit ops=[…]` (`dry_run` first for large edits) |
|
|
76
77
|
| Check config | `ecuc_validate` |
|
|
@@ -110,7 +111,13 @@ Do not decide what to change from memory or from the prompt alone. Each change g
|
|
|
110
111
|
3. **Change** with the structured tools only (`ecuc_edit`, `arxml_edit` incl. `new_file`, `autosar_add_signal`). Prefer
|
|
111
112
|
one call per coherent change; nothing is written if any op fails.
|
|
112
113
|
4. **Verify in two steps → fix → verify, until clean.**
|
|
113
|
-
- Validation 1 (deterministic, the hard gate): `ecuc_validate` (ECUC and extract/SWC files alike
|
|
114
|
+
- Validation 1 (deterministic, the hard gate): `ecuc_validate` (ECUC and extract/SWC files alike, including the
|
|
115
|
+
COMPLETENESS rules: H2L-CHAIN-001 Com I-PDU routed by PduR to a bus PDU, -002 ComSignal attached to an I-PDU,
|
|
116
|
+
-003 extract signal implemented by a ComSignal, -004 triggered I-PDU in a triggered frame, -005 sent signal mapped
|
|
117
|
+
to a port data element, -006 port accessed by a runnable, -007 no CAN id used twice on a channel, H2L-SYS-003 CanIf
|
|
118
|
+
CAN id = frame id; -005/-006 report only what this working copy's changes added). An H2L-CHAIN finding means the
|
|
119
|
+
change is UNFINISHED: do the missing part, then validate again. `arxml_edit`, `autosar_add_like` and
|
|
120
|
+
`autosar_add_signal` validate after every write and say what is still to do.
|
|
114
121
|
`autosar_generate` + `autosar_codegen_diff` / `autosar_build` and `autosar_verify_rte` where the project has them.
|
|
115
122
|
- Validation 2 (against the documents): `ecuc_validate docs=true` checks every element changed in this session
|
|
116
123
|
against the ingested specs/manuals and returns a checklist; answer it line by line (OK / VIOLATED / NOT COVERED,
|
|
@@ -118,11 +125,26 @@ Do not decide what to change from memory or from the prompt alone. Each change g
|
|
|
118
125
|
On any problem: read it, go back to step 1 for that fact, fix with the structured tools, verify again. Repeat until
|
|
119
126
|
every check is clean. Then re-read the request and tick each todo against the evidence. Never report done with a
|
|
120
127
|
failing or unrun check; if something cannot be made clean, say exactly what and why.
|
|
121
|
-
The pack enforces this: the first edit of a session is refused until a grounding tool ran
|
|
128
|
+
The pack enforces this: the first edit of a session is refused until a grounding tool ran (and the refusal
|
|
129
|
+
carries the context engine's facts for that edit's target, run for you), C edits need
|
|
122
130
|
`autosar_build_context` (or `autosar_audit`) first, and the system prompt lists every open verification item
|
|
123
131
|
(unvalidated edits, failing validation, unverified C, changed elements not yet checked against the documents)
|
|
124
132
|
until it is resolved.
|
|
125
133
|
|
|
134
|
+
## A new thing that resembles an existing one: autosar_add_like (preferred)
|
|
135
|
+
Most changes add something the project already has one of: a message, a port, a runnable with its timing event, a
|
|
136
|
+
Dem event with its DTC, an NvM block with its Fee block. Do not build those op by op. Pick the existing one most like
|
|
137
|
+
the new one (same direction, bus, kind), then:
|
|
138
|
+
1. `autosar_add_like like=<its stem> name=<new stem> dry_run=true`: read the Created list (every file it touches)
|
|
139
|
+
and the "Not copied" / "single-valued" lists.
|
|
140
|
+
2. Name the members you want too: `also={"DrvDoorOpenSts": "SettingsAckCounter"}` gives the new message one signal
|
|
141
|
+
cloned from that one.
|
|
142
|
+
3. Set the values the request gives with `set` (ECUC param name, XML tag, or `Owner:key` when several clones have
|
|
143
|
+
it): CAN id (`IDENTIFIER`, `CanIfTxPduCanId`), lengths (`PduLength`, `FRAME-LENGTH`, `I-SIGNAL-I-PDU:LENGTH`),
|
|
144
|
+
period (`ComTxModeTimePeriod`), signal layout (`<sig>:ComBitPosition`, `<sig>:ComBitSize`, `START-POSITION`) ...
|
|
145
|
+
4. Run it without dry_run. It validates and rolls back on any new finding. Then fix what is listed as single-valued
|
|
146
|
+
(e.g. the S/R-to-signal mapping of a new port) with arxml_edit, and verify as below.
|
|
147
|
+
|
|
126
148
|
## New ARXML file (SWC, interface, data types, extract) from nothing
|
|
127
149
|
1. ONE `arxml_edit` call. Its first op creates the file:
|
|
128
150
|
`{op:"new_file", file:"Input/SWC_X.arxml", packages:["/DataTypes", "/Interfaces", "/Swcs"]}` (R25-11 header and empty
|