dsh-logicprobe 1.2.0 → 1.4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-logicprobe",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "Verifies claims about designs and code. Facts go against the source. Behaviour goes through an executable model: state machines, protocols, composition, data models, migrations. Structure diagrams are read as dependency graphs, at one level or several. Two reports can be compared for regression. Exports UPPAAL, TLA+, PRISM and SPIN input. Ships a native DeepSeek Harness bundle that injects the verification gate into the first model step.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -161,6 +161,24 @@ UML modelling variant (the task is to draw a flow, not to check a claim):
161
161
  → logicprobe_verify → the behavioural checks (S1-S8 / A1-A14)
162
162
  ```
163
163
 
164
+ ### Budgeting the Model Before You Run It
165
+
166
+ Search cost is the declared domain times the number of states — and a declared range is
167
+ not a bound on the search.
168
+
169
+ - **`inc`/`dec` are never clamped to `min`/`max`.** The declaration is a property the
170
+ checks verify, not a limit the engine enforces. A counter whose guard does not pin it
171
+ ranges past its own declaration, so the reachable set is unbounded however small the
172
+ declared range looks; S8 reports that as `S8_DOMAIN_NOT_ENFORCED`.
173
+ - **Bound a counter with a guard, not a narrower declaration.** `max: 4` with a guard
174
+ `retry < 4` closes the loop. `max: 4` alone does not.
175
+ - **Say the budget before running**: values per variable x states. A product in the
176
+ 10^5-and-up range will hit the cap and come back undecided.
177
+ - **Model the check where the code performs it.** A cap check that lives inside the tick
178
+ handler is a guarded branch of the tick event, not a separate event: as a separate event
179
+ the environment can starve it, and the leads-to failure you then read is an artefact of
180
+ the model rather than a property of the code.
181
+
164
182
  ### Native dsh Tools
165
183
 
166
184
  | Tool | Covers |
@@ -203,6 +221,30 @@ Turn the verdict into an answer, never into a summary of `ok`:
203
221
 
204
222
  Exit codes: `0` for `pass` / `pass_with_findings`, `2` for `fail`, a refusal, or an unreadable input. In DSH the native tools return the report as data — read `verdict`, and treat `errorCode`/`error` as the refusal path.
205
223
 
224
+ `summary.truncated: true`, or a finding whose code ends in `_UNDECIDED`
225
+ (`S7_UNDECIDED`, `A7_UNDECIDED`, `A9_LEADS_TO_UNDECIDED`, `D2_UNDECIDED`), means the search
226
+ stopped before deciding. That is **not** a pass: name the invariant that went undecided,
227
+ and say the property is undecided rather than reading the silence as success.
228
+
229
+ ### Writing the Answer: Name the Finding, Not Its Code
230
+
231
+ `S2_NO_TRANSITIONS`, `UML019`, `DA2` are values of `findings[].code`, not names. A reader
232
+ who has not memorized the schema learns nothing from one, and cannot tell whether their
233
+ machine is sound or stuck.
234
+
235
+ - **Say what it means**, in the user's language and in the terms of their own code:
236
+ 「启动超时后 ERROR 没有任何出口迁移,机器会停在那里」, not 「S2 命中」. The finding's
237
+ `message` is written for the report; the sentence for the user is written from it.
238
+ - **Keep the identifier where the identifier is the subject**: a report field quoted
239
+ verbatim, a baseline diff, a test name, or a question about the check itself.
240
+ - **Name the property, not the checking.** "This path locks without unlocking" beats
241
+ "A4 failed". A check's name earns its place when the reader asked about the checking.
242
+ - **Carry the verdict in words.** "Not verified" or "verified, within the scope of this
243
+ model" — never a bare pass, and never a warning presented without its label.
244
+
245
+ A reader who cannot tell what failed will either trust a report they cannot read, or
246
+ ignore one they should act on.
247
+
206
248
  ### Slice Acceptance: Baseline Diffs
207
249
 
208
250
  The acceptance criterion for a refactoring slice is usually "no **new** findings", not
@@ -418,6 +460,8 @@ If you catch yourself thinking any of these, stop:
418
460
  | "I'll check while implementing" | Check before the decision, not after. |
419
461
  | "Reasoning is enough" | Reasoning is not a run. One counter-example kills a universal claim. |
420
462
  | "The report passed, so the code is fine" | The report covers the model you gave it. Reconcile it, and say what you did not check. |
463
+ | "The declared range limits the search" | The declaration is a property to verify. `inc`/`dec` are never clamped to it; only a guard bounds the value. |
464
+ | "The check found nothing" | Read the verdict: `truncated` and `_UNDECIDED` mean the search stopped, not that the property holds. |
421
465
 
422
466
  ## Rules
423
467
 
@@ -430,4 +474,5 @@ If you catch yourself thinking any of these, stop:
430
474
  7. **Don't verify what the code already checks.** If the existing codebase has compile-time assertions, static analysis, or runtime checks for a property, cite those — don't re-verify in a model.
431
475
  8. **Hand a neighbouring domain to its own skill.** A diagram is a model, not evidence: modelling or auditing one belongs to `logicprobe-uml`. A concurrency claim is mined and routed, never proven here: that is `logicprobe-concurrency`. Schema, data invariants and migrations are `logicprobe-datamodel`. This skill keeps the doctrine and the behavioural checks.
432
476
  9. **Say what this does not prove.** For dimensions logicprobe does not verify — hard real time (deadlines, periods), preemptive concurrency, hybrid-control stability, probabilistic reliability, execution-cost budgets — route to the dedicated tool named in `references/gap-routing-guide.md`. `logicprobe_verify` reports carry the same routing as informational `coverageNotes` on matching models.
433
- 10. **Write-site analysis is out of scope, and stays there.** "Only the ISR writes this flag", "one owner per register" — logicprobe verifies models and diagrams, not *who writes what in the source*. That claim needs a source-side AST/rule check or a static analyzer; this plugin's contribution is to flag the claim as unverified and route it (`logicprobe_concurrency_scan` does exactly that). Do not imply the diagram or the model settled it.
477
+ 10. **A declared range is a property, not a bound, and a capped search is not a pass.** `inc`/`dec` are never clamped to `min`/`max`; only a guard that pins the value bounds it. When a report is `truncated` or a check is `_UNDECIDED`, say the property is undecided and narrow the model — never report it as verified.
478
+ 11. **Write-site analysis is out of scope, and stays there.** "Only the ISR writes this flag", "one owner per register" — logicprobe verifies models and diagrams, not *who writes what in the source*. That claim needs a source-side AST/rule check or a static analyzer; this plugin's contribution is to flag the claim as unverified and route it (`logicprobe_concurrency_scan` does exactly that). Do not imply the diagram or the model settled it.
@@ -225,6 +225,17 @@ A guard is exactly one of:
225
225
 
226
226
  `inc`/`dec` default to 1 when `value` is omitted. `set` defaults to 0.
227
227
 
228
+ `inc` and `dec` are **never clamped** to the variable's declared `min`/`max`. The declared
229
+ range is a property the checks verify (S7 `var-in-range`), not a bound on the search: a
230
+ counter whose guard does not pin it ranges past its own declaration, so the reachable set
231
+ is unbounded however narrow the declaration looks. S8 reports that as
232
+ `S8_DOMAIN_NOT_ENFORCED` and names the transition, rather than clamping the value and
233
+ deleting the states a range violation is witnessed by. A `set` constant outside the
234
+ declared range contradicts the declaration and is `S8_DOMAIN_SET_OUTSIDE` (error).
235
+
236
+ To bound a counter, add a guard that pins it — `"max": 4` with a guard
237
+ `{ "variable": "retry", "op": "<", "value": 4 }` — not a narrower `max`.
238
+
228
239
  ## Invariants
229
240
 
230
241
  Every invariant carries `id` and **`description`** — both required strings — plus a
@@ -396,9 +407,14 @@ The report's `comparison` object includes both model hashes, state/transition co
396
407
 
397
408
  List events that must be idempotent in `idempotentEvents`. For every reachable state, applying the event twice must produce the same state as applying it once. This is useful for retries, webhook redelivery, and migration replay.
398
409
 
399
- ## Advanced constraints (S8, A9-A14)
410
+ ## Advanced constraints (S6, S8, A9-A14)
400
411
 
401
- - **S8 Monotonic Variables**: declare `monotonic: "inc"|"dec"` on a variable; updates must not move in the opposite direction.
412
+ - **S6 Guard Completeness** also reports `S6_DEAD_GUARD`: a guarded branch no reachable
413
+ variable value can satisfy is a branch that can never fire. The test runs over the
414
+ values the model can produce — each variable's `init` plus every `set` constant — not
415
+ over the declared domain, so a guard on a value the model never writes is satisfiable on
416
+ paper and dead in practice.
417
+ - **S8 Variable Declarations**: declare `monotonic: "inc"|"dec"` on a variable; updates must not move in the opposite direction. The same check reads the declared range: `S8_DOMAIN_NOT_ENFORCED` reports an `inc`/`dec` no guard keeps inside `min`/`max`, and `S8_DOMAIN_SET_OUTSIDE` (error) reports a `set` constant outside it.
402
418
  - **A9 Leads-To**: `{ kind: "leads-to", from, to }` — every path from `from` must eventually reach `to`. `to` is one state id or a non-empty array of state ids for "reach any one of these"; a duplicated member is a validation error rather than a shorthand. Either way the property stays universal: one branch that loops forever, or that stops before every target, refutes it.
403
419
  - **A10 Sequence**: `{ kind: "sequence", events }` — events must appear in order.
404
420
  - **A11 Atomicity**: `{ kind: "atomicity", events, commit, rollback? }` — once an atomic event starts, the machine must reach commit/rollback before leaving the atomic scope or terminating.
@@ -408,7 +424,8 @@ List events that must be idempotent in `idempotentEvents`. For every reachable s
408
424
 
409
425
  ## Limits
410
426
 
411
- - State-space exploration caps at `maxStates` (default 10000); larger guards/domains may report truncation instead of a false pass.
427
+ - Each breadth-first exploration caps at `maxStates` (default 10000). The leads-to walk caps at twenty times that, **shared across every start state of one check**: it is depth-first over run states, and an unbounded counter makes every one of them distinct, so it has no shorter bound available.
428
+ - A cap that bites is **reported, never silently passed**. The report sets `summary.truncated`, and the check that stopped carries a finding whose code ends in `_UNDECIDED` — `S7_UNDECIDED`, `A7_UNDECIDED`, `A9_LEADS_TO_UNDECIDED`, `D2_UNDECIDED`. An undecided check has proven neither the violation nor its absence; read it as "not decided", never as a pass.
412
429
  - A3 samples the first `maxPermutationEvents` events (default 5).
413
430
  - The engine is a finite-state model checker. It cannot prove properties of the real implementation; follow with code-level review.
414
431
  - `cost` values are modeler-provided static labels — A12 verifies against them; real execution time/WCET needs binary-level timing analysis.
@@ -42,3 +42,4 @@ The full routing table, including hard-real-time, hybrid-control and execution-c
42
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
43
  4. **Sequential targets are out of scope.** If nothing runs concurrently, say so and stop.
44
44
  5. **The scanner is dsh-only.** `logicprobe_concurrency_scan` has no mirror subcommand. Without it, mine the text by hand against the keyword table above and route each claim the same way.
45
+ 6. **Name the claim and its routing in words.** 「QoS 1 只投递一次」plus the tool that decides it, never a bare check name. A reader who has not memorized this skill's tables learns nothing from an enumeration. The full rule is under "Writing the Answer" in the `logicprobe` skill.
@@ -92,6 +92,11 @@ The **DataModelV1 shape** is in [`references/data-model-schema.md`](references/d
92
92
  | DD3 | Delta summary | Warning |
93
93
  | DD4 | Breaking change regression | Error/Warning |
94
94
 
95
+ Every `#` in these tables is a report field, not a name. Write what the check found for the
96
+ reader (「回滚与备份不对称」), in their language; keep the id for a report field quoted
97
+ verbatim, a baseline diff or a test name. The full rule is under "Writing the Answer" in the
98
+ `logicprobe` skill.
99
+
95
100
  ## Extraction Rule
96
101
 
97
102
  Before writing any verification code, output a data-model table:
@@ -91,5 +91,6 @@ This tool audits the **diagram**; an include or dependency scan audits the **cod
91
91
  2. **Quote the rule id for every judged edge.** "Not allowed" without the rule is an opinion; `matchedRules` makes it a finding.
92
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
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
+ 5. **Report the rule id with its meaning, never the id alone.** Rule 2 keeps the id on every judged edge, so the finding is defensible; the sentence the reader gets says what the edge violated (「HAL 反向依赖了上层」), in their language. The full rule is under "Writing the Answer" in the `logicprobe` skill.
94
95
 
95
96
  The full matrix schema, a worked example and the reconciliation worksheet are in [`references/structure-review-guide.md`](references/structure-review-guide.md).
@@ -32,6 +32,8 @@ code flow → model (citation per element) → logicprobe_uml action=render →
32
32
  - **review** — it answers one of three questions. Give it a model: is the machine well-modelled? Give it a diagram: what does the diagram say? Give it both: does the diagram match the model?
33
33
  - **explain-labels** — the parser's own account of labels and comments: the accepted spellings, the form the renderer writes, and which lines it ignores. No diagram needed. It answers "why does my hand-drawn diagram report drift or ignored lines?" without reading the source.
34
34
 
35
+ The view follows the question, not the machine, and no tool picks it for you. Declare the question with `intent` (`present` / `verify` / `fidelity`) and a view that cannot serve it is refused before any work, with a code that names why. The ordered procedure, the intent-by-action table and those codes are under [Choosing the view](references/uml-modeling-guide.md#choosing-the-view).
36
+
35
37
  A host without the tools runs the same four actions through the repository engine. The engine is deliberately single-source — do not copy it into this skill.
36
38
 
37
39
  ## A Structure Diagram Is the Dangerous Case, Because It Parses
@@ -53,5 +55,6 @@ Structural defects: unreachable states, dead ends, ambiguous or non-exhaustive b
53
55
  3. **Check the verdict before the picture.** `ok: true` with `verdict: "fail"` means the review failed; a diagram whose review failed is not something to show as if it were checked.
54
56
  4. **The review never replaces verification.** It covers the modelling. S1-S8 and A1-A14 cover the behaviour, and each finding names the check that settles it — hand those over to the `logicprobe` skill.
55
57
  5. **Keep the narrative with the model.** Write `narrative.states`, `narrative.events` and `narrative.scenarios`. Then the diagram stays readable against the code, and label drift shows up as a finding instead of as a stale picture.
58
+ 6. **Name a finding for the reader, not by its code.** `UML017_ROUND_TRIP_MISMATCH` is a report field; the sentence the reader gets says what disagreed (「图里少了 ERROR→COOLDOWN 这条迁移」), in their language. Keep the code for a report field quoted verbatim, a diff or a test name. The full rule is under "Writing the Answer" in the `logicprobe` skill.
56
59
 
57
60
  The full checklist (codes `UML001` to `UML019` plus `UML_NOT_A_STATE_DIAGRAM`), the directive format generated diagrams carry, a worked example, and the limits of each view are in [`references/uml-modeling-guide.md`](references/uml-modeling-guide.md).
@@ -4,7 +4,17 @@ This guide covers two jobs: drawing a code flow as UML, and reviewing that drawi
4
4
 
5
5
  One rule runs through the whole guide. **A diagram is a model, and a model can be wrong.** A tidy flow chart is not evidence about the code. Worse, a diagram that disagrees with the model it came from will mislead every reader who trusts it.
6
6
 
7
- ## When to use this
7
+ ## Choosing the view
8
+
9
+ The view follows the question, not the machine. Decide in this order.
10
+
11
+ **1. Is the question about where dependencies point?** Architecture, layering, module
12
+ structure, "may this call that" — that question belongs to the `logicprobe-structure`
13
+ skill, which reads the same component, package, class or deployment text as a dependency
14
+ graph. The state front end refuses that text by name
15
+ (`UML_NOT_A_STATE_DIAGRAM`) instead of reviewing it.
16
+
17
+ **2. Otherwise fix the boundary first, then pick the view by the reader's question.**
8
18
 
9
19
  | Situation | View that answers it |
10
20
  |---|---|
@@ -14,7 +24,65 @@ One rule runs through the whole guide. **A diagram is a model, and a model can b
14
24
  | "Somebody gave me a diagram. Is it right?" | Any view. Feed it to the review and compare it against the code. |
15
25
  | "This diagram and this model disagree." | Feed both to the review. The round-trip check names every difference. |
16
26
 
17
- Do not use this guide for a behavioural claim about a specific machine. That is the job of `logicprobe_verify` (S1-S8 and A1-A14). The review here covers the modelling, not the behaviour. Every finding it produces names the engine check that settles the behavioural half.
27
+ **3. Check the choice against the next step.** A view that cannot feed the check you are
28
+ about to run is the wrong view, however readable it looks.
29
+
30
+ | Next step | Views that can feed it |
31
+ |---|---|
32
+ | `logicprobe_verify` (S1-S8 and A1-A14) | State or activity: `parse` reads either into a LogicModelV1 |
33
+ | `logicprobe_uml action=review` with a model, for the fidelity check | State or activity: only those round-trip |
34
+ | `logicprobe_uml action=review` with a model alone | Any view. With `kind: "sequence"` the trace is rendered and the fidelity check is reported inapplicable (`UML019`) |
35
+ | A human reading a protocol exchange | Sequence, which is the honest view there |
36
+
37
+ **4. Say that out loud, and the tool refuses a pairing that cannot serve it.** `intent`
38
+ declares what the artifact is for. The check runs before any work, so an impossible
39
+ pairing is a refusal with a code rather than a report about the wrong artifact.
40
+
41
+ | Intent | What it means | Actions that serve it | View it needs |
42
+ |---|---|---|---|
43
+ | `present` | a human is going to read the diagram | render, review | any view the notation can draw |
44
+ | `verify` | it must be parseable into a LogicModelV1 for the engine | render, parse, review | state or activity: a trace cannot be parsed back |
45
+ | `fidelity` | the point is the render-and-reparse proof | review | state or activity: only those round-trip |
46
+
47
+ | `errorCode` | What happened |
48
+ |---|---|
49
+ | `UML_INTENT_UNSERVABLE` | The view exists but cannot serve the intent — a sequence view for `verify` or `fidelity`. |
50
+ | `UML_VIEW_UNREPRESENTABLE` | The `(notation, view)` cell does not exist — PlantUML activity. |
51
+ | `UML_INTENT_MISMATCH` | The intent names something this action does not do — `fidelity` on render (render proves nothing), `present` or `fidelity` on parse (parse returns a model). |
52
+
53
+ Omitting `intent` changes nothing: a sequence view still renders, and a review of one
54
+ still reports `UML019`. The intent is an opt-in statement of purpose, and nothing infers
55
+ it. The CLI mirror takes the same values: `--intent present|verify|fidelity` on
56
+ `uml-render`, `uml-parse` and `uml-review`.
57
+
58
+ When the question is still unclear, model the state machine. It is the safe default, and
59
+ the other views render from the same model afterwards.
60
+
61
+ ### Nothing picks the view for you
62
+
63
+ `render` takes `kind` from the caller and defaults to `state`; it never guesses. A guess
64
+ between state and activity would be a presentation decision, and the machine cannot make
65
+ it: the same guarded branch is a state machine to one reader and an error path to another.
66
+ `parse` does detect the notation and the diagram family from the text, and it refuses the
67
+ families it cannot model, by name.
68
+
69
+ Mechanically decidable, and nothing else:
70
+
71
+ - the notation and the family the text declares — a Mermaid header (`stateDiagram`,
72
+ `flowchart`, `sequenceDiagram`, or a known other-family header) or a PlantUML body
73
+ keyword;
74
+ - whether the requested notation-and-kind combination exists at all (PlantUML has no
75
+ activity view here);
76
+ - whether a view can carry the fidelity check (a sequence cannot).
77
+
78
+ Everything else is a fact about the question, not about the artifact. A model can tell you
79
+ its shape — its state count, its guarded branches, its cycles — and its shape does not
80
+ choose the view.
81
+
82
+ Do not use this guide for a behavioural claim about a specific machine. That is the job of
83
+ `logicprobe_verify` (S1-S8 and A1-A14). The review here covers the modelling, not the
84
+ behaviour. Every finding it produces names the engine check that settles the behavioural
85
+ half.
18
86
 
19
87
  ## The three views
20
88