dsh-logicprobe 1.1.0 → 1.2.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.
@@ -1,419 +1,419 @@
1
- export declare const ENGINE_SCHEMA_VERSION = 1;
2
- export type VarValue = number | boolean;
3
- export type GuardOp = '==' | '!=' | '<' | '<=' | '>' | '>=';
4
- export interface LeafGuard {
5
- variable: string;
6
- op: GuardOp;
7
- value: VarValue;
8
- }
9
- export interface AllGuard {
10
- all: GuardNode[];
11
- }
12
- export interface AnyGuard {
13
- any: GuardNode[];
14
- }
15
- export interface NotGuard {
16
- not: GuardNode;
17
- }
18
- export type GuardNode = LeafGuard | AllGuard | AnyGuard | NotGuard;
19
- export interface StateSpec {
20
- id: string;
21
- terminal?: boolean;
22
- /** Actions fired automatically when the state is entered. They do not change state or variables; checks such as A4 Pair Symmetry treat them as implicit acquire/release events so lock/unlock hidden inside entry actions is verified. */
23
- onEntry?: string[];
24
- /** Actions fired automatically when the state is left. Same semantics as onEntry. */
25
- onExit?: string[];
26
- /** Deadline (A14): the state must be left within this many declared tick events of entering it. */
27
- maxTicks?: number;
28
- }
29
- export interface UpdateSpec {
30
- variable: string;
31
- op: 'set' | 'inc' | 'dec';
32
- value?: number;
33
- }
34
- export interface TransitionSpec {
35
- from: string;
36
- event: string;
37
- to: string;
38
- /** Absent guard is the else/default branch for the same (from, event) group. */
39
- guard?: GuardNode;
40
- updates?: UpdateSpec[];
41
- /** Execution cost of firing this transition (e.g. cycles, microseconds). Absent cost defaults to 1, so an unannotated machine keeps step-count semantics. Checked by A12 against budget invariants. */
42
- cost?: number;
43
- /** Relative probability weight when a probability invariant is declared (DTMC interpretation). Absent weight = 1; weight 0 means the branch never fires probabilistically. */
44
- weight?: number;
45
- }
46
- export interface TransitionScenarioSpec {
47
- from: string;
48
- event: string;
49
- /** Natural language: what this (state, event) combination represents in the real scenario. */
50
- scenario: string;
51
- }
52
- export interface ModelNarrative {
53
- /** Natural-language meaning of each state id. */
54
- states?: Record<string, string>;
55
- /** Natural-language meaning of each event id. */
56
- events?: Record<string, string>;
57
- /** Natural-language scenario for each distinct (from, event) combination. */
58
- scenarios?: TransitionScenarioSpec[];
59
- }
60
- export interface VariableSpec {
61
- name: string;
62
- kind: 'integer' | 'boolean';
63
- init: number | boolean;
64
- min?: number;
65
- max?: number;
66
- monotonic?: 'inc' | 'dec';
67
- }
68
- /**
69
- * Scope filter for a state-predicate invariant (see `InvariantSpec`): the range is
70
- * only required to hold in runtime states that satisfy the filter. Exactly one of
71
- * `state` / guard-node form must be present.
72
- */
73
- export interface StateGuard {
74
- /** Only runtime states whose id is this state are checked. */
75
- state: string;
76
- }
77
- /** A `when` filter: either a single-state scope or the ordinary guard language. */
78
- export type InvariantWhen = StateGuard | GuardNode;
79
- export declare function isStateGuard(when: InvariantWhen): when is StateGuard;
80
- export type InvariantSpec = {
81
- id: string;
82
- description: string;
83
- kind: 'never-states';
84
- states: string[];
85
- } | {
86
- id: string;
87
- description: string;
88
- kind: 'var-in-range';
89
- variable: string;
90
- min?: number;
91
- max?: number;
92
- /**
93
- * Optional scope. `when` is evaluated against the POST-state of every transition
94
- * (and, for the `{ state }` form, against the initial state regardless), so a
95
- * `{ state }` scope is sound: for every reachable runtime state inside the scope
96
- * the variable is in range. A guard-node scope that references the constrained
97
- * variable can mask its own violation — prefer `{ state }` or an independent
98
- * control variable. Not offered on trace-property kinds (leads-to, sequence, ...),
99
- * whose scope over a path would be ambiguous.
100
- */
101
- when?: InvariantWhen;
102
- } | {
103
- id: string;
104
- description: string;
105
- kind: 'event-before-state';
106
- event: string;
107
- state: string;
108
- } | {
109
- id: string;
110
- description: string;
111
- kind: 'leads-to';
112
- from: string;
113
- to: string | string[];
114
- } | {
115
- id: string;
116
- description: string;
117
- kind: 'sequence';
118
- events: string[];
119
- } | {
120
- id: string;
121
- description: string;
122
- kind: 'atomicity';
123
- events: string[];
124
- commit: string;
125
- rollback?: string;
126
- } | {
127
- id: string;
128
- description: string;
129
- kind: 'budget';
130
- budget: number;
131
- } | {
132
- id: string;
133
- description: string;
134
- kind: 'probability';
135
- target: string;
136
- op: '>=' | '<=' | '>' | '<';
137
- p: number;
138
- };
139
- export interface ResourcePairSpec {
140
- resource: string;
141
- acquireEvent: string;
142
- releaseEvent: string;
143
- failEvent?: string;
144
- }
145
- export interface BoundaryCheckSpec {
146
- variable: string;
147
- values: number[];
148
- }
149
- export interface LogicModelV1 {
150
- schemaVersion: 1;
151
- init: string;
152
- states: StateSpec[];
153
- transitions: TransitionSpec[];
154
- variables?: VariableSpec[];
155
- invariants?: InvariantSpec[];
156
- concurrentPairs?: [string, string][];
157
- boundaryChecks?: BoundaryCheckSpec[];
158
- resourcePairs?: ResourcePairSpec[];
159
- idempotentEvents?: string[];
160
- /** Events that advance the discrete clock by one tick; used by A14 deadline checks. */
161
- tickEvents?: string[];
162
- /** Natural-language descriptions of states, events, and (state, event) scenarios. */
163
- narrative?: ModelNarrative;
164
- }
165
- export interface VerificationOptions {
166
- maxStates?: number;
167
- maxPermutationEvents?: number;
168
- beforeModel?: unknown;
169
- stateMapping?: Record<string, string>;
170
- /** Hash specification used for modelHash. Defaults to the current published spec (`v1`). */
171
- hashSpec?: HashSpec;
172
- }
173
- export interface PathStep {
174
- from: string;
175
- event: string;
176
- to: string;
177
- }
178
- export interface Finding {
179
- code: string;
180
- /**
181
- * `info` is a note, not a defect: it is reported so a machine-readable reader can see it,
182
- * and it never contributes to `verdict`, `summary.errors` or `summary.warnings`.
183
- */
184
- severity: 'error' | 'warning' | 'info';
185
- message: string;
186
- /** Longer explanation: the shortest-path note, the per-machine reasons, the quoted source lines. */
187
- detail?: string;
188
- /** The artefact the finding is about (a diagram name, a file path) — set when one report covers several. */
189
- file?: string;
190
- /** 1-based line in that artefact, when the check knows it. */
191
- line?: number;
192
- path?: PathStep[];
193
- evidence?: Record<string, unknown>;
194
- }
195
- export interface CheckResult {
196
- id: string;
197
- name: string;
198
- status: 'pass' | 'fail' | 'skip';
199
- detail: string;
200
- findings: Finding[];
201
- }
202
- export interface VerificationReport {
203
- /**
204
- * The engine ran and the input was well-formed enough to produce this report.
205
- * It does NOT mean the review passed — read `verdict` for that.
206
- */
207
- ok: boolean;
208
- /** The tool executed and produced a report. False only when a tool refused the request before the engine ran. */
209
- ran: boolean;
210
- /** The review outcome. `ok: true` with `verdict: "fail"` is a failing review, not a passing one. */
211
- verdict: Verdict;
212
- /** Why the verdict came out that way, e.g. `2 error finding(s) (first: S2_NO_TRANSITIONS)`. */
213
- verdictReason: string;
214
- /** The versioned report contract this result follows. */
215
- schema: ReportSchema;
216
- schemaVersion: 1;
217
- /** Which published specification `modelHash` follows (see references/hash-spec.md). */
218
- hashSpec: HashSpec;
219
- modelHash: string;
220
- /** Every hash this report carries, in one place, for a baseline diff or an archive record. */
221
- hashes: {
222
- hashSpec: HashSpec;
223
- modelHash: string;
224
- beforeModelHash?: string;
225
- afterModelHash?: string;
226
- };
227
- /** Paths of the `_`-prefixed metadata keys found in the input and ignored by the schema. */
228
- metadataKeys?: string[];
229
- /** How much of the model the narrative documents; absent when there is no narrative. */
230
- narrativeCoverage?: NarrativeCoverage;
231
- /** Echo of the model's natural-language narrative, when present. */
232
- narrative?: ModelNarrative;
233
- summary: {
234
- states: number;
235
- transitions: number;
236
- errors: number;
237
- warnings: number;
238
- checksRun: number;
239
- truncated?: boolean;
240
- };
241
- checks: CheckResult[];
242
- comparison?: ComparisonSummary;
243
- /** What to do next, derived from the findings — never empty. */
244
- nextSteps: string[];
245
- /** Informational notes about semantic dimensions this model references (timing, preemption)
246
- * that this engine does not verify. Heuristic, vocabulary-based — never a substitute for the checks. */
247
- coverageNotes?: string[];
248
- }
249
- export interface ComparisonSummary {
250
- beforeModelHash: string;
251
- afterModelHash: string;
252
- stateMapping: Record<string, string>;
253
- beforeStates: number;
254
- beforeTransitions: number;
255
- afterStates: number;
256
- afterTransitions: number;
257
- addedStates: string[];
258
- removedStates: string[];
259
- addedEvents: string[];
260
- removedEvents: string[];
261
- addedTransitions: TransitionSpec[];
262
- removedTransitions: TransitionSpec[];
263
- }
264
- export interface RuntimeState {
265
- state: string;
266
- vars: Record<string, VarValue>;
267
- }
268
- export declare function modelHash(model: LogicModelV1, spec?: HashSpec): string;
269
- /**
270
- * Deterministic JSON (sorted keys, no insignificant whitespace) — the serialization the
271
- * hash specification is defined over. Exported so every report hashes inputs the same way.
272
- */
273
- export declare function canonicalJson(value: unknown): string;
274
- /**
275
- * Published model-hash specifications. The full normalization rules, the list of
276
- * excluded keys and a one-line recompute command are in `references/hash-spec.md`.
277
- *
278
- * - `v1` (1.0.0) drops every `_`-prefixed metadata key at every level before hashing,
279
- * so archiving `_source` / `_verified` next to a model cannot change its hash.
280
- * - `v0` is the pre-1.0.0 behaviour (no metadata filtering), kept so an archived hash
281
- * can still be checked against the old engine.
282
- *
283
- * For a model that carries no `_` keys the two specs are byte-identical.
284
- */
285
- export type HashSpec = 'v0' | 'v1';
286
- export declare const PUBLISHED_HASH_SPECS: readonly HashSpec[];
287
- export declare const DEFAULT_HASH_SPEC: HashSpec;
288
- /**
289
- * Versioned report contracts. Every tool result carries `schema`, so a consumer can
290
- * branch on the contract instead of sniffing fields, and a change to a report's shape
291
- * is a version bump here rather than a silent break. `findings[]` keeps the same
292
- * stable core in every family: `{code, severity, message, detail?, evidence?, path?}`.
293
- */
294
- export declare const REPORT_SCHEMAS: {
295
- readonly verify: 'logicprobe/verify/v1';
296
- readonly compose: 'logicprobe/compose/v1';
297
- readonly datamodel: 'logicprobe/datamodel/v1';
298
- readonly concurrency: 'logicprobe/concurrency/v1';
299
- readonly export: 'logicprobe/export/v1';
300
- readonly umlRender: 'logicprobe/uml/render/v1';
301
- readonly umlParse: 'logicprobe/uml/parse/v1';
302
- readonly umlReview: 'logicprobe/uml/review/v1';
303
- readonly umlExplainLabels: 'logicprobe/uml/explain-labels/v1';
304
- readonly structure: 'logicprobe/structure/v1';
305
- readonly granularity: 'logicprobe/granularity/v1';
306
- readonly baseline: 'logicprobe/baseline/v1';
307
- };
308
- export type ReportSchema = typeof REPORT_SCHEMAS[keyof typeof REPORT_SCHEMAS];
309
- /**
310
- * How much of the model the `narrative` block actually documents, as `covered/total`
311
- * per dimension. A partial narrative is valid — writing the states first and the
312
- * events later is the natural order — so this is a coverage report, not a gate.
313
- * `scenarios` counts distinct modelled (from, event) groups: one scenario per group.
314
- */
315
- export interface NarrativeCoverage {
316
- states: string;
317
- events: string;
318
- scenarios: string;
319
- }
320
- export declare function narrativeCoverageOf(model: LogicModelV1): NarrativeCoverage | undefined;
321
- /** True when every dimension of the coverage is fully described. */
322
- export declare function narrativeComplete(coverage: NarrativeCoverage | undefined): boolean;
323
- /**
324
- * The review outcome, kept separate from `ok` (= "the tool ran"). Collapsing the two
325
- * is how a failed review gets read as a passing one.
326
- */
327
- export type Verdict = 'pass' | 'pass_with_findings' | 'fail';
328
- export interface VerdictSummary {
329
- verdict: Verdict;
330
- verdictReason: string;
331
- }
332
- /** A tool that refused the request before running anything: it neither passed nor failed a review. */
333
- export declare function refusalVerdict(reason: string): VerdictSummary;
334
- /**
335
- * Decide the verdict from finding counts. Any error-severity finding fails the review
336
- * even though the tool ran successfully — that gap is exactly what `verdict` closes.
337
- */
338
- export declare function verdictOf(errors: number, warnings: number, firstCode?: string): VerdictSummary;
339
- export declare function verdictOfFindings(findings: Array<{
340
- code: string;
341
- severity: string;
342
- }>): VerdictSummary;
343
- /**
344
- * Paths of every `_`-prefixed key in the input, sorted (`_source`, `_verified`,
345
- * `states[0]._note`; a metadata key's own children are not listed, since the subtree is
346
- * metadata as a whole). The schema ignores these keys as annotation metadata; echoing
347
- * them in the report makes the ignored metadata auditable instead of invisible.
348
- */
349
- export declare function metadataKeysOf(input: unknown): string[];
350
- /**
351
- * The exact payload a hash spec hashes. Exported so the specification has one
352
- * implementation and the documentation can be checked against it.
353
- */
354
- export declare function hashPayload(model: unknown, spec?: HashSpec): unknown;
355
- export declare function validateModel(input: unknown): {
356
- ok: true;
357
- model: LogicModelV1;
358
- } | {
359
- ok: false;
360
- errors: string[];
361
- };
362
- export declare function guardVariables(guard: GuardNode | undefined): string[];
363
- export declare function runVerification(input: unknown, options?: VerificationOptions): VerificationReport;
364
- export interface CompositionStep {
365
- event: string;
366
- /** Indices of the machines that advanced together on this step. */
367
- machines: number[];
368
- }
369
- export interface CompositionOptions {
370
- /** Events that require a synchronized multi-machine step (handshake). */
371
- rendezvous?: string[];
372
- maxStates?: number;
373
- /** Hash specification used for the per-machine modelHash. Defaults to the current published spec (`v1`). */
374
- hashSpec?: HashSpec;
375
- }
376
- export interface CompositionSummary {
377
- machineCount: number;
378
- machines: Array<{
379
- modelHash: string;
380
- states: number;
381
- transitions: number;
382
- }>;
383
- compositeStates: number;
384
- errors: number;
385
- warnings: number;
386
- truncated: boolean;
387
- }
388
- export interface CompositionReport {
389
- /** Historical meaning: the composition has no error-severity finding. Read `verdict` for the review outcome. */
390
- ok: boolean;
391
- ran: boolean;
392
- verdict: Verdict;
393
- verdictReason: string;
394
- /** The versioned report contract this result follows. */
395
- schema: ReportSchema;
396
- /** Which published specification the per-machine `modelHash` values follow. */
397
- hashSpec: HashSpec;
398
- /** Every hash this report carries, in one place. */
399
- hashes: {
400
- hashSpec: HashSpec;
401
- machines: string[];
402
- };
403
- summary: CompositionSummary;
404
- checks: CheckResult[];
405
- /** What to do next, derived from the findings — never empty. */
406
- nextSteps: string[];
407
- }
408
- /**
409
- * N-machine composition semantics (documented in dsh-model-schema.md):
410
- * - a non-rendezvous event advances exactly ONE non-terminal machine that fires it;
411
- * - a rendezvous event (handshake) fires only when AT LEAST TWO machines declare it in
412
- * their alphabet and EVERY such non-terminal machine has it enabled (guards held);
413
- * all participants then advance simultaneously. A machine that is terminal, or whose
414
- * alphabet does not include the event, does not participate;
415
- * - a terminal machine is stopped and takes no further part.
416
- * Checks: C1 composition deadlock (reachable node where no machine can advance while at
417
- * least one is not terminal) and C2 rendezvous that can never fire.
418
- */
419
- export declare function runCompositionVerification(machinesInput: unknown[], options?: CompositionOptions): CompositionReport;
1
+ export declare const ENGINE_SCHEMA_VERSION = 1;
2
+ export type VarValue = number | boolean;
3
+ export type GuardOp = '==' | '!=' | '<' | '<=' | '>' | '>=';
4
+ export interface LeafGuard {
5
+ variable: string;
6
+ op: GuardOp;
7
+ value: VarValue;
8
+ }
9
+ export interface AllGuard {
10
+ all: GuardNode[];
11
+ }
12
+ export interface AnyGuard {
13
+ any: GuardNode[];
14
+ }
15
+ export interface NotGuard {
16
+ not: GuardNode;
17
+ }
18
+ export type GuardNode = LeafGuard | AllGuard | AnyGuard | NotGuard;
19
+ export interface StateSpec {
20
+ id: string;
21
+ terminal?: boolean;
22
+ /** Actions fired automatically when the state is entered. They do not change state or variables; checks such as A4 Pair Symmetry treat them as implicit acquire/release events so lock/unlock hidden inside entry actions is verified. */
23
+ onEntry?: string[];
24
+ /** Actions fired automatically when the state is left. Same semantics as onEntry. */
25
+ onExit?: string[];
26
+ /** Deadline (A14): the state must be left within this many declared tick events of entering it. */
27
+ maxTicks?: number;
28
+ }
29
+ export interface UpdateSpec {
30
+ variable: string;
31
+ op: 'set' | 'inc' | 'dec';
32
+ value?: number;
33
+ }
34
+ export interface TransitionSpec {
35
+ from: string;
36
+ event: string;
37
+ to: string;
38
+ /** Absent guard is the else/default branch for the same (from, event) group. */
39
+ guard?: GuardNode;
40
+ updates?: UpdateSpec[];
41
+ /** Execution cost of firing this transition (e.g. cycles, microseconds). Absent cost defaults to 1, so an unannotated machine keeps step-count semantics. Checked by A12 against budget invariants. */
42
+ cost?: number;
43
+ /** Relative probability weight when a probability invariant is declared (DTMC interpretation). Absent weight = 1; weight 0 means the branch never fires probabilistically. */
44
+ weight?: number;
45
+ }
46
+ export interface TransitionScenarioSpec {
47
+ from: string;
48
+ event: string;
49
+ /** Natural language: what this (state, event) combination represents in the real scenario. */
50
+ scenario: string;
51
+ }
52
+ export interface ModelNarrative {
53
+ /** Natural-language meaning of each state id. */
54
+ states?: Record<string, string>;
55
+ /** Natural-language meaning of each event id. */
56
+ events?: Record<string, string>;
57
+ /** Natural-language scenario for each distinct (from, event) combination. */
58
+ scenarios?: TransitionScenarioSpec[];
59
+ }
60
+ export interface VariableSpec {
61
+ name: string;
62
+ kind: 'integer' | 'boolean';
63
+ init: number | boolean;
64
+ min?: number;
65
+ max?: number;
66
+ monotonic?: 'inc' | 'dec';
67
+ }
68
+ /**
69
+ * Scope filter for a state-predicate invariant (see `InvariantSpec`): the range is
70
+ * only required to hold in runtime states that satisfy the filter. Exactly one of
71
+ * `state` / guard-node form must be present.
72
+ */
73
+ export interface StateGuard {
74
+ /** Only runtime states whose id is this state are checked. */
75
+ state: string;
76
+ }
77
+ /** A `when` filter: either a single-state scope or the ordinary guard language. */
78
+ export type InvariantWhen = StateGuard | GuardNode;
79
+ export declare function isStateGuard(when: InvariantWhen): when is StateGuard;
80
+ export type InvariantSpec = {
81
+ id: string;
82
+ description: string;
83
+ kind: 'never-states';
84
+ states: string[];
85
+ } | {
86
+ id: string;
87
+ description: string;
88
+ kind: 'var-in-range';
89
+ variable: string;
90
+ min?: number;
91
+ max?: number;
92
+ /**
93
+ * Optional scope. `when` is evaluated against the POST-state of every transition
94
+ * (and, for the `{ state }` form, against the initial state regardless), so a
95
+ * `{ state }` scope is sound: for every reachable runtime state inside the scope
96
+ * the variable is in range. A guard-node scope that references the constrained
97
+ * variable can mask its own violation — prefer `{ state }` or an independent
98
+ * control variable. Not offered on trace-property kinds (leads-to, sequence, ...),
99
+ * whose scope over a path would be ambiguous.
100
+ */
101
+ when?: InvariantWhen;
102
+ } | {
103
+ id: string;
104
+ description: string;
105
+ kind: 'event-before-state';
106
+ event: string;
107
+ state: string;
108
+ } | {
109
+ id: string;
110
+ description: string;
111
+ kind: 'leads-to';
112
+ from: string;
113
+ to: string | string[];
114
+ } | {
115
+ id: string;
116
+ description: string;
117
+ kind: 'sequence';
118
+ events: string[];
119
+ } | {
120
+ id: string;
121
+ description: string;
122
+ kind: 'atomicity';
123
+ events: string[];
124
+ commit: string;
125
+ rollback?: string;
126
+ } | {
127
+ id: string;
128
+ description: string;
129
+ kind: 'budget';
130
+ budget: number;
131
+ } | {
132
+ id: string;
133
+ description: string;
134
+ kind: 'probability';
135
+ target: string;
136
+ op: '>=' | '<=' | '>' | '<';
137
+ p: number;
138
+ };
139
+ export interface ResourcePairSpec {
140
+ resource: string;
141
+ acquireEvent: string;
142
+ releaseEvent: string;
143
+ failEvent?: string;
144
+ }
145
+ export interface BoundaryCheckSpec {
146
+ variable: string;
147
+ values: number[];
148
+ }
149
+ export interface LogicModelV1 {
150
+ schemaVersion: 1;
151
+ init: string;
152
+ states: StateSpec[];
153
+ transitions: TransitionSpec[];
154
+ variables?: VariableSpec[];
155
+ invariants?: InvariantSpec[];
156
+ concurrentPairs?: [string, string][];
157
+ boundaryChecks?: BoundaryCheckSpec[];
158
+ resourcePairs?: ResourcePairSpec[];
159
+ idempotentEvents?: string[];
160
+ /** Events that advance the discrete clock by one tick; used by A14 deadline checks. */
161
+ tickEvents?: string[];
162
+ /** Natural-language descriptions of states, events, and (state, event) scenarios. */
163
+ narrative?: ModelNarrative;
164
+ }
165
+ export interface VerificationOptions {
166
+ maxStates?: number;
167
+ maxPermutationEvents?: number;
168
+ beforeModel?: unknown;
169
+ stateMapping?: Record<string, string>;
170
+ /** Hash specification used for modelHash. Defaults to the current published spec (`v1`). */
171
+ hashSpec?: HashSpec;
172
+ }
173
+ export interface PathStep {
174
+ from: string;
175
+ event: string;
176
+ to: string;
177
+ }
178
+ export interface Finding {
179
+ code: string;
180
+ /**
181
+ * `info` is a note, not a defect: it is reported so a machine-readable reader can see it,
182
+ * and it never contributes to `verdict`, `summary.errors` or `summary.warnings`.
183
+ */
184
+ severity: 'error' | 'warning' | 'info';
185
+ message: string;
186
+ /** Longer explanation: the shortest-path note, the per-machine reasons, the quoted source lines. */
187
+ detail?: string;
188
+ /** The artefact the finding is about (a diagram name, a file path) — set when one report covers several. */
189
+ file?: string;
190
+ /** 1-based line in that artefact, when the check knows it. */
191
+ line?: number;
192
+ path?: PathStep[];
193
+ evidence?: Record<string, unknown>;
194
+ }
195
+ export interface CheckResult {
196
+ id: string;
197
+ name: string;
198
+ status: 'pass' | 'fail' | 'skip';
199
+ detail: string;
200
+ findings: Finding[];
201
+ }
202
+ export interface VerificationReport {
203
+ /**
204
+ * The engine ran and the input was well-formed enough to produce this report.
205
+ * It does NOT mean the review passed — read `verdict` for that.
206
+ */
207
+ ok: boolean;
208
+ /** The tool executed and produced a report. False only when a tool refused the request before the engine ran. */
209
+ ran: boolean;
210
+ /** The review outcome. `ok: true` with `verdict: "fail"` is a failing review, not a passing one. */
211
+ verdict: Verdict;
212
+ /** Why the verdict came out that way, e.g. `2 error finding(s) (first: S2_NO_TRANSITIONS)`. */
213
+ verdictReason: string;
214
+ /** The versioned report contract this result follows. */
215
+ schema: ReportSchema;
216
+ schemaVersion: 1;
217
+ /** Which published specification `modelHash` follows (see references/hash-spec.md). */
218
+ hashSpec: HashSpec;
219
+ modelHash: string;
220
+ /** Every hash this report carries, in one place, for a baseline diff or an archive record. */
221
+ hashes: {
222
+ hashSpec: HashSpec;
223
+ modelHash: string;
224
+ beforeModelHash?: string;
225
+ afterModelHash?: string;
226
+ };
227
+ /** Paths of the `_`-prefixed metadata keys found in the input and ignored by the schema. */
228
+ metadataKeys?: string[];
229
+ /** How much of the model the narrative documents; absent when there is no narrative. */
230
+ narrativeCoverage?: NarrativeCoverage;
231
+ /** Echo of the model's natural-language narrative, when present. */
232
+ narrative?: ModelNarrative;
233
+ summary: {
234
+ states: number;
235
+ transitions: number;
236
+ errors: number;
237
+ warnings: number;
238
+ checksRun: number;
239
+ truncated?: boolean;
240
+ };
241
+ checks: CheckResult[];
242
+ comparison?: ComparisonSummary;
243
+ /** What to do next, derived from the findings — never empty. */
244
+ nextSteps: string[];
245
+ /** Informational notes about semantic dimensions this model references (timing, preemption)
246
+ * that this engine does not verify. Heuristic, vocabulary-based — never a substitute for the checks. */
247
+ coverageNotes?: string[];
248
+ }
249
+ export interface ComparisonSummary {
250
+ beforeModelHash: string;
251
+ afterModelHash: string;
252
+ stateMapping: Record<string, string>;
253
+ beforeStates: number;
254
+ beforeTransitions: number;
255
+ afterStates: number;
256
+ afterTransitions: number;
257
+ addedStates: string[];
258
+ removedStates: string[];
259
+ addedEvents: string[];
260
+ removedEvents: string[];
261
+ addedTransitions: TransitionSpec[];
262
+ removedTransitions: TransitionSpec[];
263
+ }
264
+ export interface RuntimeState {
265
+ state: string;
266
+ vars: Record<string, VarValue>;
267
+ }
268
+ export declare function modelHash(model: LogicModelV1, spec?: HashSpec): string;
269
+ /**
270
+ * Deterministic JSON (sorted keys, no insignificant whitespace) — the serialization the
271
+ * hash specification is defined over. Exported so every report hashes inputs the same way.
272
+ */
273
+ export declare function canonicalJson(value: unknown): string;
274
+ /**
275
+ * Published model-hash specifications. The full normalization rules, the list of
276
+ * excluded keys and a one-line recompute command are in `references/hash-spec.md`.
277
+ *
278
+ * - `v1` (1.0.0) drops every `_`-prefixed metadata key at every level before hashing,
279
+ * so archiving `_source` / `_verified` next to a model cannot change its hash.
280
+ * - `v0` is the pre-1.0.0 behaviour (no metadata filtering), kept so an archived hash
281
+ * can still be checked against the old engine.
282
+ *
283
+ * For a model that carries no `_` keys the two specs are byte-identical.
284
+ */
285
+ export type HashSpec = 'v0' | 'v1';
286
+ export declare const PUBLISHED_HASH_SPECS: readonly HashSpec[];
287
+ export declare const DEFAULT_HASH_SPEC: HashSpec;
288
+ /**
289
+ * Versioned report contracts. Every tool result carries `schema`, so a consumer can
290
+ * branch on the contract instead of sniffing fields, and a change to a report's shape
291
+ * is a version bump here rather than a silent break. `findings[]` keeps the same
292
+ * stable core in every family: `{code, severity, message, detail?, evidence?, path?}`.
293
+ */
294
+ export declare const REPORT_SCHEMAS: {
295
+ readonly verify: 'logicprobe/verify/v1';
296
+ readonly compose: 'logicprobe/compose/v1';
297
+ readonly datamodel: 'logicprobe/datamodel/v1';
298
+ readonly concurrency: 'logicprobe/concurrency/v1';
299
+ readonly export: 'logicprobe/export/v1';
300
+ readonly umlRender: 'logicprobe/uml/render/v1';
301
+ readonly umlParse: 'logicprobe/uml/parse/v1';
302
+ readonly umlReview: 'logicprobe/uml/review/v1';
303
+ readonly umlExplainLabels: 'logicprobe/uml/explain-labels/v1';
304
+ readonly structure: 'logicprobe/structure/v1';
305
+ readonly granularity: 'logicprobe/granularity/v1';
306
+ readonly baseline: 'logicprobe/baseline/v1';
307
+ };
308
+ export type ReportSchema = typeof REPORT_SCHEMAS[keyof typeof REPORT_SCHEMAS];
309
+ /**
310
+ * How much of the model the `narrative` block actually documents, as `covered/total`
311
+ * per dimension. A partial narrative is valid — writing the states first and the
312
+ * events later is the natural order — so this is a coverage report, not a gate.
313
+ * `scenarios` counts distinct modelled (from, event) groups: one scenario per group.
314
+ */
315
+ export interface NarrativeCoverage {
316
+ states: string;
317
+ events: string;
318
+ scenarios: string;
319
+ }
320
+ export declare function narrativeCoverageOf(model: LogicModelV1): NarrativeCoverage | undefined;
321
+ /** True when every dimension of the coverage is fully described. */
322
+ export declare function narrativeComplete(coverage: NarrativeCoverage | undefined): boolean;
323
+ /**
324
+ * The review outcome, kept separate from `ok` (= "the tool ran"). Collapsing the two
325
+ * is how a failed review gets read as a passing one.
326
+ */
327
+ export type Verdict = 'pass' | 'pass_with_findings' | 'fail';
328
+ export interface VerdictSummary {
329
+ verdict: Verdict;
330
+ verdictReason: string;
331
+ }
332
+ /** A tool that refused the request before running anything: it neither passed nor failed a review. */
333
+ export declare function refusalVerdict(reason: string): VerdictSummary;
334
+ /**
335
+ * Decide the verdict from finding counts. Any error-severity finding fails the review
336
+ * even though the tool ran successfully — that gap is exactly what `verdict` closes.
337
+ */
338
+ export declare function verdictOf(errors: number, warnings: number, firstCode?: string): VerdictSummary;
339
+ export declare function verdictOfFindings(findings: Array<{
340
+ code: string;
341
+ severity: string;
342
+ }>): VerdictSummary;
343
+ /**
344
+ * Paths of every `_`-prefixed key in the input, sorted (`_source`, `_verified`,
345
+ * `states[0]._note`; a metadata key's own children are not listed, since the subtree is
346
+ * metadata as a whole). The schema ignores these keys as annotation metadata; echoing
347
+ * them in the report makes the ignored metadata auditable instead of invisible.
348
+ */
349
+ export declare function metadataKeysOf(input: unknown): string[];
350
+ /**
351
+ * The exact payload a hash spec hashes. Exported so the specification has one
352
+ * implementation and the documentation can be checked against it.
353
+ */
354
+ export declare function hashPayload(model: unknown, spec?: HashSpec): unknown;
355
+ export declare function validateModel(input: unknown): {
356
+ ok: true;
357
+ model: LogicModelV1;
358
+ } | {
359
+ ok: false;
360
+ errors: string[];
361
+ };
362
+ export declare function guardVariables(guard: GuardNode | undefined): string[];
363
+ export declare function runVerification(input: unknown, options?: VerificationOptions): VerificationReport;
364
+ export interface CompositionStep {
365
+ event: string;
366
+ /** Indices of the machines that advanced together on this step. */
367
+ machines: number[];
368
+ }
369
+ export interface CompositionOptions {
370
+ /** Events that require a synchronized multi-machine step (handshake). */
371
+ rendezvous?: string[];
372
+ maxStates?: number;
373
+ /** Hash specification used for the per-machine modelHash. Defaults to the current published spec (`v1`). */
374
+ hashSpec?: HashSpec;
375
+ }
376
+ export interface CompositionSummary {
377
+ machineCount: number;
378
+ machines: Array<{
379
+ modelHash: string;
380
+ states: number;
381
+ transitions: number;
382
+ }>;
383
+ compositeStates: number;
384
+ errors: number;
385
+ warnings: number;
386
+ truncated: boolean;
387
+ }
388
+ export interface CompositionReport {
389
+ /** Historical meaning: the composition has no error-severity finding. Read `verdict` for the review outcome. */
390
+ ok: boolean;
391
+ ran: boolean;
392
+ verdict: Verdict;
393
+ verdictReason: string;
394
+ /** The versioned report contract this result follows. */
395
+ schema: ReportSchema;
396
+ /** Which published specification the per-machine `modelHash` values follow. */
397
+ hashSpec: HashSpec;
398
+ /** Every hash this report carries, in one place. */
399
+ hashes: {
400
+ hashSpec: HashSpec;
401
+ machines: string[];
402
+ };
403
+ summary: CompositionSummary;
404
+ checks: CheckResult[];
405
+ /** What to do next, derived from the findings — never empty. */
406
+ nextSteps: string[];
407
+ }
408
+ /**
409
+ * N-machine composition semantics (documented in dsh-model-schema.md):
410
+ * - a non-rendezvous event advances exactly ONE non-terminal machine that fires it;
411
+ * - a rendezvous event (handshake) fires only when AT LEAST TWO machines declare it in
412
+ * their alphabet and EVERY such non-terminal machine has it enabled (guards held);
413
+ * all participants then advance simultaneously. A machine that is terminal, or whose
414
+ * alphabet does not include the event, does not participate;
415
+ * - a terminal machine is stopped and takes no further part.
416
+ * Checks: C1 composition deadlock (reachable node where no machine can advance while at
417
+ * least one is not terminal) and C2 rendezvous that can never fire.
418
+ */
419
+ export declare function runCompositionVerification(machinesInput: unknown[], options?: CompositionOptions): CompositionReport;