dsh-logicprobe 0.9.0 → 1.0.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.
Files changed (63) hide show
  1. package/README.en-US.md +79 -14
  2. package/README.md +79 -14
  3. package/lib/baseline.js +142 -0
  4. package/lib/client.js +56 -1
  5. package/lib/compose-tool.js +1 -1
  6. package/lib/concurrency.js +14 -0
  7. package/lib/data-engine.js +42 -8
  8. package/lib/engine.js +379 -79
  9. package/lib/export-tool.js +27 -2
  10. package/lib/granularity.js +237 -0
  11. package/lib/index.js +63 -41
  12. package/lib/report-diff-tool.js +39 -0
  13. package/lib/structure-tool.js +64 -0
  14. package/lib/structure.js +648 -0
  15. package/lib/tool.js +3 -3
  16. package/lib/types/baseline.d.ts +69 -0
  17. package/lib/types/concurrency.d.ts +10 -0
  18. package/lib/types/data-engine.d.ts +13 -1
  19. package/lib/types/engine.d.ts +138 -1
  20. package/lib/types/granularity.d.ts +106 -0
  21. package/lib/types/index.d.ts +9 -8
  22. package/lib/types/report-diff-tool.d.ts +8 -0
  23. package/lib/types/structure-tool.d.ts +10 -0
  24. package/lib/types/structure.d.ts +156 -0
  25. package/lib/types/tool.d.ts +2 -2
  26. package/lib/types/uml.d.ts +89 -0
  27. package/lib/uml-tool.js +36 -7
  28. package/lib/uml.js +217 -23
  29. package/locale/en.json +6 -0
  30. package/locale/zh.json +6 -0
  31. package/package.json +16 -14
  32. package/skills/logicprobe/SKILL.md +84 -58
  33. package/skills/logicprobe/references/dsh-model-schema.md +81 -10
  34. package/skills/logicprobe/references/gap-routing-guide.md +37 -36
  35. package/skills/logicprobe/references/hash-spec.md +128 -0
  36. package/skills/logicprobe/references/logic-verification-guide.md +27 -250
  37. package/skills/logicprobe-concurrency/SKILL.md +43 -0
  38. package/skills/{logicprobe → logicprobe-concurrency}/references/concurrency-risk-guide.md +1 -1
  39. package/skills/logicprobe-datamodel/SKILL.md +5 -4
  40. package/skills/logicprobe-datamodel/references/data-model-guide.md +1 -1
  41. package/skills/logicprobe-datamodel/references/data-model-schema.md +1 -1
  42. package/skills/logicprobe-structure/SKILL.md +95 -0
  43. package/skills/logicprobe-structure/references/structure-review-guide.md +177 -0
  44. package/skills/logicprobe-uml/SKILL.md +55 -0
  45. package/skills/{logicprobe → logicprobe-uml}/references/uml-modeling-guide.md +120 -8
  46. package/src/baseline.ts +180 -0
  47. package/src/client.js +56 -1
  48. package/src/compose-tool.ts +47 -47
  49. package/src/concurrency.ts +23 -0
  50. package/src/data-engine.ts +51 -9
  51. package/src/engine.ts +441 -67
  52. package/src/export-tool.ts +73 -48
  53. package/src/granularity.ts +312 -0
  54. package/src/index.ts +63 -41
  55. package/src/report-diff-tool.ts +43 -0
  56. package/src/structure-tool.ts +68 -0
  57. package/src/structure.ts +724 -0
  58. package/src/tool.ts +3 -3
  59. package/src/uml-tool.ts +38 -8
  60. package/src/uml.ts +275 -24
  61. package/skills/logicprobe/references/logicprobe-engine.py +0 -4943
  62. package/skills/logicprobe/references/verification-harness.py +0 -959
  63. package/skills/logicprobe-datamodel/references/data-model-harness.py +0 -528
@@ -90,288 +90,67 @@ which wastes more time than no verification at all.
90
90
 
91
91
  ## Probe Design Patterns
92
92
 
93
+ Every probe below is implemented by `logicprobe_verify`. These patterns are here to read a finding, not to reimplement it.
94
+
93
95
  ### A1: Unexpected Event Injection
94
96
 
95
- ```python
96
- # For each state S, list all events defined anywhere in the machine
97
- # For each event E not handled by S, test: what happens?
98
- def unexpected_event_probe(states):
99
- all_events = set()
100
- for trans in states.values():
101
- all_events.update(trans.keys())
102
-
103
- findings = []
104
- for state, transitions in states.items():
105
- unhandled = all_events - set(transitions.keys())
106
- if unhandled:
107
- findings.append({
108
- "state": state,
109
- "unhandled_events": list(unhandled),
110
- "risk": "Silent ignore or undefined behavior"
111
- })
112
- return findings
113
- ```
97
+ For every state, list the events declared anywhere in the machine that this state does not handle. An unhandled event is a silent ignore or undefined behaviour.
114
98
 
115
99
  ### A2: Race Interleaving
116
100
 
117
- For pairs of events that can arrive within the same tick (e.g., timer expiry + message reception):
118
-
119
- ```python
120
- def race_interleaving(states, init, event_pairs):
121
- # event_pairs = [("timeout", "ack"), ("error", "done"), ...]
122
- findings = []
123
- for e1, e2 in event_pairs:
124
- # Path 1: e1 then e2
125
- s1 = step(init, [e1, e2])
126
- # Path 2: e2 then e1
127
- s2 = step(init, [e2, e1])
128
- if s1 != s2:
129
- findings.append({
130
- "events": (e1, e2),
131
- "order_e1_e2_ends_in": s1,
132
- "order_e2_e1_ends_in": s2,
133
- "risk": "Order-dependent outcome not documented"
134
- })
135
- return findings
136
- ```
101
+ For event pairs that can arrive in the same tick, compare `e1` then `e2` against `e2` then `e1`. A different outcome is order dependence the design must state.
137
102
 
138
103
  ### A3: Order Permutation
139
104
 
140
- Extends A2 to N events. For small N (≤5), do full permutation. For larger N, sample:
141
-
142
- ```python
143
- from itertools import permutations
144
-
145
- def order_permutation_probe(states, init, events, invariant_check=None):
146
- findings = []
147
- terminals = set()
148
- for perm in permutations(events):
149
- final = step(init, list(perm))
150
- terminals.add(final)
151
- if invariant_check and not invariant_check(final):
152
- findings.append({
153
- "sequence": list(perm),
154
- "final_state": final,
155
- "violates": "invariant"
156
- })
157
- if len(terminals) > 1:
158
- findings.append({
159
- "terminal_states": list(terminals),
160
- "risk": f"Same events produce {len(terminals)} different outcomes depending on order"
161
- })
162
- return findings
163
- ```
105
+ The same question at N events. Up to 5 events permute fully; beyond that the probe samples. More than one terminal state means the outcome depends on arrival order.
164
106
 
165
107
  ### A4: Pair Symmetry
166
108
 
167
- ```python
168
- # Define paired operations
169
- PAIRS = [
170
- ("lock", "unlock"),
171
- ("start", "stop"),
172
- ("alloc", "free"),
173
- ("enable_irq", "disable_irq"),
174
- ("open", "close"),
175
- ]
176
-
177
- def pair_symmetry_probe(states, init):
178
- findings = []
179
- for acquire, release in PAIRS:
180
- if acquire not in all_events(states) and release not in all_events(states):
181
- continue # This pair type is not used
182
- # DFS from init: every path that calls acquire must eventually call release
183
- # before reaching a terminal state (or acquire again)
184
- unbalanced = find_unbalanced_paths(states, init, acquire, release)
185
- if unbalanced:
186
- findings.append({
187
- "pair": (acquire, release),
188
- "unbalanced_paths": unbalanced,
189
- "risk": "Resource leak or deadlock"
190
- })
191
- return findings
192
- ```
109
+ For paired operations (lock/unlock, start/stop, alloc/free, enable_irq/disable_irq, open/close), search from the initial state for a path that acquires without releasing before it terminates or acquires again. That is a leak or a deadlock.
193
110
 
194
111
  ### A5: Boundary Blast
195
112
 
196
- ```python
197
- def boundary_blast(counters, timestamps):
198
- findings = []
199
- for name, val in counters.items():
200
- test_values = [0, 1, val-1, val, val+1, 2**32-1, 2**32]
201
- for tv in test_values:
202
- if tv < 0 or tv > val:
203
- findings.append({
204
- "variable": name,
205
- "value": tv,
206
- "risk": f"Counter overflow/underflow at {tv} (max defined: {val})"
207
- })
208
- # Timestamp wraparound (millis() / sys_tick())
209
- # On 32-bit ARM: wraparound at ~49.7 days for 1ms tick
210
- for name, val in timestamps.items():
211
- findings.append({
212
- "variable": name,
213
- "risk": f"Timestamp wraparound not handled — check elapsed_ms() / elapsed_ticks() pattern"
214
- })
215
- return findings
216
- ```
113
+ Test each counter at 0, 1, max-1, max and max+1. Flag overflow and underflow. Timestamps wrap too: a 32-bit millisecond clock wraps after about 49.7 days, so check every `elapsed_*()` pattern.
217
114
 
218
115
  ### A6: Resource Injection
219
116
 
220
- ```python
221
- RESOURCE_FAILURES = {
222
- "malloc": "returns NULL",
223
- "queue_send": "queue full",
224
- "semaphore_take": "timeout",
225
- "message_alloc": "pool exhausted",
226
- }
227
-
228
- def resource_injection_probe(states):
229
- findings = []
230
- for state, transitions in states.items():
231
- for event in transitions:
232
- for resource, failure in RESOURCE_FAILURES.items():
233
- if resource in event.lower() or resource in state.lower():
234
- # Simulate: what if this resource call fails in this state?
235
- # Does the machine have a recovery transition?
236
- has_recovery = any(
237
- "error" in t.lower() or "fail" in t.lower() or "retry" in t.lower()
238
- for t in transitions.values()
239
- )
240
- if not has_recovery:
241
- findings.append({
242
- "state": state,
243
- "resource": resource,
244
- "failure_mode": failure,
245
- "risk": f"No recovery path if {resource} {failure} in state {state}"
246
- })
247
- return findings
248
- ```
117
+ For every state and transition, ask what happens when the resource call fails: `malloc` returns NULL, a queue is full, a semaphore times out, a pool is exhausted. No error, fail or retry transition means no recovery path.
249
118
 
250
119
  ### A7: Minimal Counter-Example
251
120
 
252
- ```python
253
- from collections import deque
254
-
255
- def shortest_violating_path(states, init, invariant_check):
256
- """BFS to find the shortest event sequence that violates an invariant."""
257
- queue = deque([(init, [])])
258
- visited = set()
259
- while queue:
260
- state, path = queue.popleft()
261
- if state in visited:
262
- continue
263
- visited.add(state)
264
-
265
- if not invariant_check(state):
266
- return path # Shortest violating path found
267
-
268
- for event, next_state in states.get(state, {}).items():
269
- queue.append((next_state, path + [event]))
270
-
271
- return None # Invariant holds for all reachable states
272
- ```
273
-
274
- That template decides one runtime state at a time, so it only fits the state-predicate kinds. A `leads-to`, `sequence`, `atomicity`, `budget` or `probability` invariant asserts a property of a whole run: reuse the search in its own section (A9-A13) as the witness finder, or A7 will report "all invariants hold" for a model where one of them fails.
121
+ Breadth-first search for the shortest event sequence that violates an invariant. It decides one runtime state at a time, so it fits the state-predicate kinds only. `leads-to`, `sequence`, `atomicity`, `budget` and `probability` assert a property of a whole run and use the search in their own sections (A9-A13).
275
122
 
276
123
  ### S8: Monotonic Variables
277
124
 
278
- For counters or progress variables that must only move in one direction, list the events that increase/decrease them. Any event that moves against the declared direction is a finding.
279
-
280
- ```python
281
- MONOTONIC_VARS = [
282
- {"name": "retry_count", "direction": "inc", "increase_events": ["retry"], "decrease_events": []},
283
- ]
284
- ```
125
+ For counters that must move one way only, list the events that change them. An event that moves against the declared direction is a finding.
285
126
 
286
127
  ### A8: Idempotent Replay
287
128
 
288
- For events that must be safe to retry/replay, apply the event twice from every reachable state. If the second application changes state or is not possible, flag it.
289
-
290
- ```python
291
- IDEMPOTENT_EVENTS = {"retry", "sync", "webhook_delivery"}
292
- ```
129
+ Apply each retryable event twice from every reachable state. A second application that changes state, or cannot happen, is a finding.
293
130
 
294
131
  ### A9: Leads-To
295
132
 
296
- For progress claims ("from MIGRATING, eventually DONE"), check every path from the source state. If a path dead-ends or loops before reaching the target, flag it. When the code has several acceptable outcomes, give the invariant a target set and check every path reaches at least one member; that is still a universal claim, so a branch that reaches none of them fails.
297
-
298
- ```python
299
- LEADS_TO = [
300
- ("MIGRATING", "DONE"),
301
- # or a set of acceptable outcomes
302
- ("DRAINING", ["IDLE", "FAULT"]),
303
- ]
304
- ```
133
+ For a progress claim ("from MIGRATING, eventually DONE"), check every path from the source state. A path that dead-ends or loops before the target fails. A target set is still a universal claim: a branch that reaches none of its members fails.
305
134
 
306
135
  ### A10: Sequence Order
307
136
 
308
- For ordered event sequences ("backup before modify before commit"), search for any path where a later event occurs before an earlier one.
309
-
310
- ```python
311
- SEQUENCES = [
312
- ["backup", "modify", "commit"],
313
- ]
314
- ```
137
+ For an ordered sequence ("backup, then modify, then commit"), search for a path where a later event occurs before an earlier one.
315
138
 
316
139
  ### A11: Atomicity
317
140
 
318
- For all-or-nothing groups, track whether an atomic event has started and whether commit/rollback has occurred. Leaving the atomic scope or reaching a terminal state before commit/rollback is a violation.
319
-
320
- ```python
321
- ATOMIC_GROUPS = [
322
- {"events": ["write"], "commit": "commit", "rollback": "rollback"},
323
- ]
324
- ```
141
+ For all-or-nothing groups, track whether the atomic event started and whether commit or rollback followed. Leaving the scope, or terminating, before commit or rollback is a violation.
325
142
 
326
143
  ### A12: Budget (Worst-Case Path Cost)
327
144
 
328
- For performance-sensitive claims ("the dispatch path stays within 100 cycles", "worst-case latency ≤ budget"), model each transition with an execution cost and declare a budget:
329
-
330
- ```python
331
- TRANSITION_COSTS = { # (from, event): cost; absent entries count as 1
332
- ("BOOT", "calibrate_done"): 40,
333
- ("IDLE", "enable"): 5,
334
- ("RUN", "watchdog_expiry"): 15,
335
- }
336
- BUDGETS = [
337
- {"id": "dispatch-budget", "description": "worst-case path within budget", "budget": 100},
338
- ]
339
- ```
340
-
341
- Probe: find the shortest path from init whose accumulated cost exceeds the budget; report it verbatim. A reachable cycle with positive total cost means cost can grow without bound — flag it as a budget violation regardless of the declared budget.
342
-
343
- In DSH (`logicprobe_verify`), `cost` is an optional field on transitions (absent = 1) and `budget` is an invariant kind; A12 runs automatically. Cost values are modeler-provided static labels — they verify the model against the declared budget, not the real WCET, which needs binary-level timing analysis.
145
+ Give each transition a `cost` and declare a `budget`. The probe reports the shortest path whose accumulated cost exceeds it. A reachable cycle with positive cost means cost grows without bound, which is a violation whatever the budget says. Costs are modeler labels: this checks the model against its budget, not real WCET.
344
146
 
345
147
  ### A13: Probability Reachability (DTMC)
346
148
 
347
- For claims with a reliability/probability flavor ("at least 90% of runs reach SAFE", "P(broke first) ≥ 0.75"), attach a relative weight to each branch and declare the bound. Absent weight = 1; weight 0 means the branch never fires probabilistically.
348
-
349
- ```python
350
- TRANSITION_WEIGHTS = { # (from_state, event): weight; absent entries count as 1
351
- ("RUN", "ok"): 9,
352
- ("RUN", "bad"): 1,
353
- }
354
- PROBABILITY_INVARIANTS = [
355
- {"id": "reliability", "target": "SAFE", "op": ">=", "p": 0.9},
356
- ]
357
- ```
358
-
359
- Probe: solve P(ever reaching `target`) from INIT by value iteration over the reachable absorbing chain; compare against the bound. In DSH (`logicprobe_verify`), `weight` on transitions plus a `probability` invariant runs A13 automatically. This is a qualitative DTMC check of the modeler's weights — real MTBF/failure-rate numbers need PRISM/Storm or fault-tree analysis.
149
+ Give each branch a relative `weight` and declare a `probability` bound. The probe solves P(ever reaching the target) by value iteration and compares it with the bound. Absent weight is 1; weight 0 never fires. This is a qualitative check of the weights, not a measured failure rate.
360
150
 
361
151
  ### A14: Deadline (Discrete Tick Clock)
362
152
 
363
- For real-time-sounding claims ("must leave BUSY within 3 ticks", "watchdog resets before deadline"), declare which events advance the clock and cap per-state residency:
364
-
365
- ```python
366
- TICK_EVENTS = {"tick"}
367
- STATE_MAX_TICKS = {"BUSY": 3}
368
- ```
369
-
370
- Probe: explore residency — a `tick` step that keeps the machine resident past `maxTicks` is a deadline miss; report the over-residency path. In DSH (`logicprobe_verify`), state `maxTicks` plus top-level `tickEvents` run A14 automatically. This checks the model's declared deadlines, not real execution time (see Known Limitations below; hard real-time semantics route to UPPAAL).
371
-
372
- ### Standalone engine (non-DSH, JSON models)
373
-
374
- `references/logicprobe-engine.py` is an exact Python mirror of the DSH tools: `verify model.json` runs all 22 checks + D1-D4 with a `--before-model`/optional `--state-mapping`; `compose m1.json m2.json ... --rendezvous a,b` runs C1/C2 composition; `export model.json --format uppaal|tla|prism|spin` reproduces the four exporters byte-for-byte. It reads the same LogicModelV1 JSON as DSH, so a model verified in one host verifies identically in the other (checked by tests/python/run.mjs).
153
+ Declare which events advance the clock (`tickEvents`) and cap per-state residency with `maxTicks`. A tick that keeps the machine resident past its cap is a deadline miss, and the probe reports the over-residency path. This checks declared deadlines, not execution time.
375
154
 
376
155
  ## Counter-Example Interpretation
377
156
 
@@ -409,7 +188,7 @@ When a probe finds a counter-example, classify it:
409
188
  ### What This Method CANNOT Detect
410
189
 
411
190
  - **Implementation bugs**: The C code may have errors not present in the model
412
- - **Timing-dependent bugs**: Python model does not simulate real-time constraints
191
+ - **Timing-dependent bugs**: the model does not simulate real-time constraints
413
192
  - **Compiler/optimization issues**: Volatile omission, reordering, inlining effects
414
193
  - **Hardware-specific behavior**: Memory-mapped I/O timing, DMA races, cache coherency
415
194
  - **Undocumented behavior**: If the plan doesn't describe a transition, the model can't either
@@ -423,7 +202,7 @@ When a probe finds a counter-example, classify it:
423
202
 
424
203
  ### Model Fidelity Warning
425
204
 
426
- The Python model is an APPROXIMATION. It models state transitions, not execution semantics. A model that passes all 22 checks (S1-S8 structural, A1-A14) means the plan's LOGIC is consistent — NOT that the implementation will work. Always follow logic verification with code-level review.
205
+ The model is an APPROXIMATION. It models state transitions, not execution semantics. A model that passes all 22 checks (S1-S8 structural, A1-A14) means the plan's LOGIC is consistent — NOT that the implementation will work. Always follow logic verification with code-level review.
427
206
 
428
207
  ## Refactoring Verification
429
208
 
@@ -455,7 +234,7 @@ If the plan claims "simplification" but the numbers don't decrease, flag as UNSU
455
234
 
456
235
  **Deadlock regression**: A refactoring that splits one state into two should not introduce a deadlock path that didn't exist before. Run S2 on AFTER and compare to BEFORE's deadlock report.
457
236
 
458
- In the Python harness, set `BEFORE_STATES`, `BEFORE_INIT`, `BEFORE_TERMINALS`, and `STATE_MAPPING` to run D1-D4 automatically after the standard checks.
237
+ For before/after comparison, pass the earlier model as `beforeModel` and an optional `stateMapping`; the D1-D4 checks then run after the standard ones.
459
238
 
460
239
  ### Common Refactoring Bugs Caught by This Method
461
240
 
@@ -466,20 +245,20 @@ In the Python harness, set `BEFORE_STATES`, `BEFORE_INIT`, `BEFORE_TERMINALS`, a
466
245
 
467
246
  ## Manual Verification Mode
468
247
 
469
- When Python is NOT available (air-gapped embedded dev machine, locked-down Windows), execute each check manually. The agent performs the verification using its own reasoning — the methodology is identical, only the execution engine changes.
248
+ When no engine can run (air-gapped machine, locked-down host), execute each check manually. The agent performs the verification using its own reasoning — the methodology is identical, only the execution engine changes.
470
249
 
471
250
  ### Size Limit
472
251
 
473
- Manual verification is reliable for state machines with **≤ 10 states and ≤ 30 transitions**. For larger machines, manual BFS and cycle detection become error-prone. If the machine exceeds this threshold and Python is unavailable, either:
252
+ Manual verification is reliable for state machines with **≤ 10 states and ≤ 30 transitions**. For larger machines, manual BFS and cycle detection become error-prone. If the machine exceeds this threshold and no engine can run, either:
474
253
 
475
254
  - Decompose the machine into sub-machines and verify each independently, then check cross-machine contracts manually
476
- - Flag the size limitation as a finding and recommend the user run the Python harness offline
255
+ - Flag the size limitation as a finding and recommend running the engine offline from the repository
477
256
 
478
257
  ### Prerequisites
479
258
 
480
259
  - Transition table and model narrative have been extracted and confirmed with the user
481
260
  - You have the full table in context (from Phase 2 extraction step)
482
- - **Harness validation** (Python mode only): After filling in `verification-harness.py`, translate the Python `STATES` dict BACK into a transition table and compare it against the confirmed extraction table. If they differ, fix the harness. This catches typo and whitespace errors in manual dict construction.
261
+ - **Model validation**: when a host with an engine becomes available, run the extracted model there and compare its findings with the hand-checked ones.
483
262
 
484
263
  ### Phase 2a — Manual Structural Checks
485
264
 
@@ -515,7 +294,7 @@ Manual verification is reliable for state machines with **≤ 10 states and ≤
515
294
 
516
295
  ### Manual Mode Output Format
517
296
 
518
- For each finding, output the same structured format as the Python harness:
297
+ For each finding, output the engine structured format:
519
298
 
520
299
  ```text
521
300
  [CHECK] S1 Reachability
@@ -537,9 +316,7 @@ Manual verification takes longer but produces identical-quality findings. The ke
537
316
  2. Read plan → Phase 1 (enumerate claims)
538
317
  3. Detect behavioral claims → trigger logic-primitive escalation
539
318
  4. Load this guide
540
- 5. Check Python: `python3 --version` or `python --version`
541
- 6a. Python ≥ 3.6 → load verification-harness.py → fill in MODEL → run
542
- 6b. No Python → use Manual Verification Mode (see above) — execute each check step by step
319
+ 5. Run the checks: the native tool when the host has it, otherwise Manual Verification Mode (see above), step by step
543
320
  7. Extract model → show transition table → GET USER CONFIRMATION
544
321
  8. Run Phase 2a (8 structural primitives) → log results
545
322
  9. Run Phase 2b (14 adversarial probes) → log results
@@ -548,4 +325,4 @@ Manual verification takes longer but produces identical-quality findings. The ke
548
325
  12. Include in Phase 5 (structured output)
549
326
  ```
550
327
 
551
- Never skip step 5. A verified model based on wrong extraction is worse than no verification — it creates false confidence.
328
+ Never skip the check. A verified model based on wrong extraction is worse than no verification — it creates false confidence.
@@ -0,0 +1,43 @@
1
+ ---
2
+ name: logicprobe-concurrency
3
+ description: "Use when a design or plan claims concurrency safety (thread-safe, lock-free, no data race, ISR-safe) or names shared state, mutexes, atomics or interrupts. Mines the claims and routes each to dedicated verification (TSan, Helgrind, CBMC, TLA+); it does not prove concurrency safety. Doctrine: the logicprobe skill."
4
+ ---
5
+
6
+ # Logic Probe — Concurrency Claims
7
+
8
+ This skill does **not** prove concurrency safety, and no report from it should ever be read as a proof. It mines a document, plan or code review for concurrency claims, marks them `UNVERIFIED`, and routes each one to the tool that can actually decide it.
9
+
10
+ The claim-verification doctrine it inherits — enumerate verifiable claims, cite `file:line`, read `verdict` and never `ok` — lives in the `logicprobe` skill. Load that one when the task is behavioural verification of a state machine rather than a judgement about concurrency claims.
11
+
12
+ ## When This Skill Applies
13
+
14
+ **Only after confirming the target actually has concurrency requirements or behaviour** — multiple threads, async tasks, interrupts, shared state, or parallel execution. If the target is purely sequential, do not invoke concurrency mining; there is nothing to mine.
15
+
16
+ | The claim | How it is treated |
17
+ |---|---|
18
+ | Absolute: "thread-safe", "lock-free", "no data race", "interrupt-safe", "ISR-safe" | error / `UNVERIFIED` unless dedicated evidence is provided |
19
+ | Risk keyword: "race condition", "shared variable", "mutex", "atomic", "shared memory", "critical section", `ISR`, `IRQ`, `NMI`, `disable_irq` / `enable_irq` | warning — review whether the plan actually addresses it |
20
+ | Lock/unlock, alloc/free, start/stop pairing where ordering matters | routed to `logicprobe` model verification (A4 pair symmetry), which can decide balance inside the modelled machine |
21
+
22
+ In DSH, run the `logicprobe_concurrency_scan` tool. For manual review, follow [`references/concurrency-risk-guide.md`](references/concurrency-risk-guide.md).
23
+
24
+ ## Routing: What Decides Each Claim
25
+
26
+ Mining a claim is not verifying it. Hand each one to a tool that can:
27
+
28
+ | Dimension | Tool that decides it |
29
+ |---|---|
30
+ | Data races, lock-ordering, lock-free algorithms | ThreadSanitizer, Helgrind, or a model checker over the real code (CBMC) |
31
+ | Interrupt/ISR races, critical sections | RTOS-aware interrupt analysis, or a TLA+ model of the ISR/task interleaving |
32
+ | Protocol-level interleaving of discrete events | `logicprobe_verify` (S1-S8 / A1-A14) and `logicprobe_compose_verify` (C1/C2) |
33
+ | Hard real time: deadlines, periods, preemption | Scheduling analysis (RMA/EDF) and a timing tool — not logicprobe |
34
+ | Probability or reliability claims | PRISM/Storm, fault-tree analysis |
35
+
36
+ The full routing table, including hard-real-time, hybrid-control and execution-cost dimensions, is [`../logicprobe/references/gap-routing-guide.md`](../logicprobe/references/gap-routing-guide.md). `logicprobe_verify` reports carry the same routing as informational `coverageNotes` on matching models.
37
+
38
+ ## Rules for This Mode
39
+
40
+ 1. **Mining, not proof.** Report a concurrency claim as `UNVERIFIED` with the routing target; never as passing.
41
+ 2. **No absolute language in the answer.** "This is thread-safe" is exactly the claim being mined, not a result this skill can produce.
42
+ 3. **One counterexample is enough to refute.** If a race is suspected, the deliverable is the interleaving that breaks it plus the harness that demonstrates it (TSan/Helgrind output), not an argument.
43
+ 4. **Sequential targets are out of scope.** If nothing runs concurrently, say so and stop.
@@ -53,4 +53,4 @@ Manual checklist:
53
53
  - General: TLA+, SPIN, Alloy
54
54
  - Rust: loom, Shuttle
55
55
 
56
- Other semantic dimensions (hard real time, hybrid control, probability/reliability, execution cost) route through the same pattern — see `references/gap-routing-guide.md`.
56
+ Other semantic dimensions (hard real time, hybrid control, probability/reliability, execution cost) route through the same pattern — see the full table in the `logicprobe` skill: `../logicprobe/references/gap-routing-guide.md`.
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: logicprobe-datamodel
3
- description: "Use when reviewing design documents, architecture specs, technical proposals, schema changes, data contracts, or refactoring plans that make claims about entities, fields, constraints, relationships, data invariants, migration coverage, copy consistency, rollback symmetry, or before/after data-model equivalence. When the document contains schema changes, data migration logic, or data-behavioural assertions ('all records must have X', 'target count equals source count', 'no orphan rows', 'ids never move backwards', 'exactly one row per event', 'rollback restores the old shape', 'migration is non-breaking'), escalate into data-model verification — generate and run executable checks for structural consistency (DS1-DS4), data invariants and adversarial probes (DA1-DA12: null/boundary injection, uniqueness, referential integrity, migration coverage, copy consistency, rollback symmetry, idempotency, monotonicity, ordering, leads-to, atomicity), and before/after data regression (DD1-DD4) before trusting any claim. Behavioural, state-machine, protocol and concurrency claims belong to the sibling logicprobe skill. Also proactively SUGGEST this skill for code-level data/schema behavioural questions."
3
+ description: "Use when reviewing design docs, specs, plans or migrations claiming entities, fields, constraints, relationships, data invariants, or before/after data-model equivalence: schema changes, migration logic, or data guarantees ('all records must have X', 'no orphan rows', 'ids unique') want executable data-model verification (migration coverage, copy consistency, rollback symmetry, DD1-DD4 regression). State machines and protocols: logicprobe; diagrams: logicprobe-uml."
4
4
  ---
5
5
 
6
6
  # Logic Probe Data
7
7
 
8
- Documents are not truth — data models are. Verify every verifiable data claim before accepting or acting on a design.
8
+ An unverified data model fails later. Model the schema and the migration, run them, and it becomes clear. Check every data claim before you act on it.
9
9
 
10
10
  ## Methodology
11
11
 
@@ -44,8 +44,7 @@ Escalate immediately if the document contains ANY of:
44
44
  ```text
45
45
  Document claims → Extract DataModelV1 → Runtime check:
46
46
  ├── DSH + `logicprobe_datamodel_verify` tool available → build DataModelV1 → call tool → structured report
47
- ├── Python available → fill references/data-model-harness.py → run → report
48
- └── No Python → Manual Verification Mode (references/data-model-guide.md)
47
+ └── No engine available → Manual Verification Mode (references/data-model-guide.md)
49
48
 
50
49
  Refactoring variant:
51
50
  Extract BEFORE DataModelV1 + AFTER DataModelV1
@@ -54,6 +53,8 @@ Refactoring variant:
54
53
  → Flag any invariant that held in BEFORE but fails in AFTER
55
54
  ```
56
55
 
56
+ The **DataModelV1 shape** is in [`references/data-model-schema.md`](references/data-model-schema.md): entities, fields, keys, relationships, invariants, boundary checks, and the optional before/mapping/copy/migration/backup blocks. Those field names are the tool contract. A hand-written model that disagrees with them is rejected, not guessed at.
57
+
57
58
  ## Checks
58
59
 
59
60
  ### DS — Data structure
@@ -1,6 +1,6 @@
1
1
  # Data Model Manual Verification Mode
2
2
 
3
- Use this when neither the DSH `logicprobe_datamodel_verify` tool nor Python is available. It mirrors the automated DS/DA/DD checks as a structured checklist.
3
+ Use this when the DSH `logicprobe_datamodel_verify` tool is not available. It mirrors the automated DS/DA/DD checks as a structured checklist.
4
4
 
5
5
  ## 1. Extract the DataModelV1 table
6
6
 
@@ -1,6 +1,6 @@
1
1
  # DataModelV1 — logicprobe-datamodel Schema
2
2
 
3
- The `logicprobe_datamodel_verify` tool accepts a structured JSON data model. The engine runs DS/DA checks, and DD1-DD4 when `beforeModel` is supplied. It is host-agnostic; DSH uses the native tool, non-DSH hosts use `data-model-harness.py`.
3
+ The `logicprobe_datamodel_verify` tool accepts a structured JSON data model. The engine runs DS/DA checks, and DD1-DD4 when `beforeModel` is supplied. The schema is host-agnostic: the native tool and the repository engine take the same JSON.
4
4
 
5
5
  ## Top-level model
6
6
 
@@ -0,0 +1,95 @@
1
+ ---
2
+ name: logicprobe-structure
3
+ description: "Use when reviewing architecture, module structure or a dependency diagram: parse a component, package, class or deployment diagram into a dependency graph and check isolated nodes, dangling endpoints, cycles, allowed-dependency violations, layer violations and missing required edges. Audits the diagram, not the code — reconcile it with a source-side scan. Doctrine: the logicprobe skill."
4
+ ---
5
+
6
+ # Logic Probe — Structure and Dependency Review
7
+
8
+ A component diagram is a model of an architecture, and a model can be wrong. This skill audits the graph the diagram actually draws — nodes and dependency arrows — against a declared dependency matrix, and says plainly what that does and does not prove.
9
+
10
+ The claim-verification doctrine it inherits — cite evidence, read `verdict` and never `ok` — lives in the `logicprobe` skill. Load that one for behavioural claims (state machines, protocols, timing); load this one when the question is *where the dependencies point*.
11
+
12
+ ## When This Skill Applies
13
+
14
+ | The task involves… | What it does |
15
+ |---|---|
16
+ | "Review this architecture", "are the module dependencies sound", "is the layering right" | Parse the structure diagram into a graph, then run the structural checks below |
17
+ | A component / package / class / deployment diagram (PlantUML) or a Mermaid class diagram | `logicprobe_structure_verify` reads nodes, containers, labels and arrows; nothing is discarded |
18
+ | A rule table of allowed dependencies (allow/deny, layers, required edges) | Every edge is judged against it and reported with the rule ids it matched |
19
+ | One architecture drawn at several levels (repository / module / subdirectory) | `diagrams: [{name, diagram, parent}]`: each level is checked on its own, then against its declared parent, with the per-pair node/edge/expansion counts (`UML028`-`UML031`) |
20
+ | A diagram that must be reconciled with an include/graph check over the source | The per-edge rule ids are the meeting point: a difference between the two is the finding worth chasing |
21
+
22
+ A host without the tools runs the repository engine on the same diagram text, and on a `granularity` manifest for a multi-level set.
23
+
24
+ ## What It Checks
25
+
26
+ | Code | Severity | What it means |
27
+ |---|---|---|
28
+ | `UML020_ISOLATED_NODE` | warning | A declared, non-container node with no edge at all. A dead entry, or a dependency the diagram forgot to draw. |
29
+ | `UML021_DANGLING_REFERENCE` | error | An arrow endpoint that no declaration ever introduced. In a component diagram this is how a dependency on a component that does not exist stays invisible. |
30
+ | `UML022_CYCLE` | error | A directed cycle, reported as the **shortest** cycle path. A cycle means no build order and no layering can hold. |
31
+ | `UML023_DISALLOWED_EDGE` | error | An edge the matrix forbids — or, with `default: "deny"`, an edge no rule permits. The denying rule id is named. |
32
+ | `UML024_LAYER_VIOLATION` | error | An edge from a lower declared layer into a higher one (a child depending on its parent). Downward edges are allowed. |
33
+ | `UML025_MISSING_EXPECTED_EDGE` | warning | A rule with `require: true` whose edge the diagram does not draw — including the case where no node matches the source pattern at all. The matrix and the diagram disagree. |
34
+ | `UML026_NO_NODES` | error | The text declares no node, so there is no structure to review. |
35
+ | `UML028_UNKNOWN_PARENT` | error | A level names a parent that is not in the set. |
36
+ | `UML029_REFINEMENT_VIOLATION` | error | Multi-level only: a child invents a dependency at the parent level, or drops a parent edge instead of expanding it into a path. |
37
+ | `UML030_PARENT_CYCLE` | error | The declared parent relation is not a forest. |
38
+ | `UML031_NO_DIAGRAMS` | error | Nothing was supplied to compare. |
39
+
40
+ ## The Dependency Matrix
41
+
42
+ The matrix is the single source of truth the diagram is checked against. Keep it next to the rules it encodes, in JSON:
43
+
44
+ ```json
45
+ {
46
+ "rules": [
47
+ { "id": "R1-app-may-use-hal", "source": "app.*", "allow": ["hal.*"], "deny": ["hal.internal"] },
48
+ { "id": "R2-adapter-owns-hal", "source": "PAY", "allow": ["DRV"], "deny": ["SVC"] },
49
+ { "id": "R3-no-back-edges", "source": "*", "deny": ["SVC"] },
50
+ { "id": "R4-persistence-required", "source": "PAY", "allow": ["DB"], "require": true }
51
+ ],
52
+ "layers": [
53
+ { "name": "app", "members": ["SVC", "PAY"] },
54
+ { "name": "hal", "members": ["DRV", "DB"] }
55
+ ],
56
+ "default": "deny"
57
+ }
58
+ ```
59
+
60
+ - `source`, `allow`, `deny` and `layers[].members` accept **node ids or globs** (`*` any run, `?` one character).
61
+ - **`deny` wins globally.** A later rule's allow never resurrects an edge an earlier rule forbade; the result cannot depend on rule order.
62
+ - `default: "allow"` (the default) means only the rules judge: an edge no rule mentions is `default`. `default: "deny"` closes the matrix — anything unlisted is `UML023`.
63
+ - `require: true` turns a rule into an expectation about the diagram (`UML025`); it needs `allow` to say which edges must exist.
64
+ - Layers are ordered **top first**: an edge from an earlier layer to a later one is allowed, the reverse is `UML024`.
65
+ - Every edge is echoed in `edgeVerdicts` as `{from, to, line, matchedRules, allowed, basis}`, where `basis` is `allow` / `deny` / `default` / `unlisted`. That is the audit trail for the reconciliation below.
66
+
67
+ ## Reconciling With a Source-Side Check
68
+
69
+ This tool audits the **diagram**; an include or dependency scan audits the **code**. They answer the same question from two sides, and each side can be the one that is wrong:
70
+
71
+ 1. Run both. Keep the rule ids identical on both sides — that is what makes a difference explainable.
72
+ 2. For every rule id, compare: does the diagram draw the edge, and does the source contain the dependency?
73
+ 3. Classify each difference instead of averaging it away:
74
+ - **Diagram missing an edge the code has** — the diagram is stale (`UML025` fires when the rule is `require: true`).
75
+ - **Diagram drawing an edge the code does not have** — either the diagram is aspirational, or the source scan's scope is narrower (different file set, generated code, conditional compilation).
76
+ - **Code violating an allowed dependency the diagram never showed** — the architecture defect the diagram was hiding. This is the one worth acting on.
77
+ 4. Report the difference with both citations. Never present the diagram as evidence about the code without saying which side produced which half.
78
+
79
+ ## Limits — Say These Out Loud
80
+
81
+ - **The diagram may be wrong.** A clean `pass` means the drawn graph is internally consistent and satisfies the matrix — not that the code matches it. Only the reconciliation above touches the code.
82
+ - **No transitive closure by default.** `A → B → C` does not report `A` reaching `C`; write the rule you mean, or add the edge the code actually has.
83
+ - **No runtime behaviour.** Calls through function pointers, DI containers, registries, plugin loading and dynamic dispatch do not appear as edges. A missing edge is not proof of a missing dependency.
84
+ - **Containers are not dependencies.** Package/rectangle nodes carry members, not arrows; they are exempt from `UML020`.
85
+ - **The matrix is a claim too.** A matrix nobody maintains produces confident, wrong verdicts. Treat a rule change as a code change: review, version, cite.
86
+ - **State machines are a different question.** For guards, deadlocks, budget, probability or deadlines, that is `logicprobe` (verification) and `logicprobe-uml` (the diagram round trip).
87
+
88
+ ## Rules for This Mode
89
+
90
+ 1. **Never read a structure diagram as a state machine.** The state-machine front end refuses such a file (`UML_NOT_A_STATE_DIAGRAM`); this skill reads it as the dependency graph it is. The two must not be mixed in one report.
91
+ 2. **Quote the rule id for every judged edge.** "Not allowed" without the rule is an opinion; `matchedRules` makes it a finding.
92
+ 3. **Run the numbers, never the impression.** An isolated node and a back edge are exactly what a reader misses by eye; that is why the check exists.
93
+ 4. **Report the diagram-code difference, not just the diagram.** A structure review that never touches the source is a review of a drawing.
94
+
95
+ The full matrix schema, a worked example and the reconciliation worksheet are in [`references/structure-review-guide.md`](references/structure-review-guide.md).