dsh-model-everything 0.0.0-stage → 0.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.
Files changed (133) hide show
  1. package/LICENSE +21 -0
  2. package/README.en-US.md +332 -0
  3. package/README.md +332 -3
  4. package/cordis.patch.yml +20 -0
  5. package/examples/README.md +48 -0
  6. package/examples/control-dispatcher.json +113 -0
  7. package/examples/control-dispatcher.overbudget.json +113 -0
  8. package/examples/ecommerce-data-model.json +185 -0
  9. package/examples/order-state-machine.after.json +116 -0
  10. package/examples/order-state-machine.before.json +101 -0
  11. package/examples/user-migration.after.json +29 -0
  12. package/examples/user-migration.before.json +29 -0
  13. package/gate/locale/en.json +6 -0
  14. package/gate/locale/zh.json +6 -0
  15. package/gate/package.json +5 -0
  16. package/lib/baseline.js +249 -0
  17. package/lib/compose-tool.js +80 -0
  18. package/lib/concurrency-tool.js +40 -0
  19. package/lib/concurrency.js +206 -0
  20. package/lib/data-engine.js +1141 -0
  21. package/lib/data-tool.js +87 -0
  22. package/lib/design-engine.js +1149 -0
  23. package/lib/design-tool.js +63 -0
  24. package/lib/engine.js +4343 -0
  25. package/lib/export-tool.js +78 -0
  26. package/lib/exporters.js +434 -0
  27. package/lib/gate.js +274 -0
  28. package/lib/granularity.js +237 -0
  29. package/lib/index.js +237 -0
  30. package/lib/json-value.js +18 -0
  31. package/lib/model-file.js +48 -0
  32. package/lib/option-shape.js +62 -0
  33. package/lib/report-diff-tool.js +63 -0
  34. package/lib/report-view.js +433 -0
  35. package/lib/structure-tool.js +91 -0
  36. package/lib/structure.js +711 -0
  37. package/lib/tool.js +94 -0
  38. package/lib/types/baseline.d.ts +65 -0
  39. package/lib/types/compose-tool.d.ts +7 -0
  40. package/lib/types/concurrency-tool.d.ts +8 -0
  41. package/lib/types/concurrency.d.ts +37 -0
  42. package/lib/types/data-engine.d.ts +211 -0
  43. package/lib/types/data-tool.d.ts +10 -0
  44. package/lib/types/design-engine.d.ts +215 -0
  45. package/lib/types/design-tool.d.ts +11 -0
  46. package/lib/types/engine.d.ts +494 -0
  47. package/lib/types/export-tool.d.ts +7 -0
  48. package/lib/types/exporters.d.ts +37 -0
  49. package/lib/types/gate.d.ts +91 -0
  50. package/lib/types/granularity.d.ts +106 -0
  51. package/lib/types/index.d.ts +45 -0
  52. package/lib/types/json-value.d.ts +21 -0
  53. package/lib/types/model-file.d.ts +7 -0
  54. package/lib/types/option-shape.d.ts +22 -0
  55. package/lib/types/report-diff-tool.d.ts +8 -0
  56. package/lib/types/report-view.d.ts +50 -0
  57. package/lib/types/structure-tool.d.ts +10 -0
  58. package/lib/types/structure.d.ts +152 -0
  59. package/lib/types/tool.d.ts +11 -0
  60. package/lib/types/uml-communication.d.ts +109 -0
  61. package/lib/types/uml-composite.d.ts +107 -0
  62. package/lib/types/uml-interaction-overview.d.ts +91 -0
  63. package/lib/types/uml-profile.d.ts +112 -0
  64. package/lib/types/uml-structure.d.ts +53 -0
  65. package/lib/types/uml-syntax.d.ts +296 -0
  66. package/lib/types/uml-timing.d.ts +152 -0
  67. package/lib/types/uml-tool.d.ts +12 -0
  68. package/lib/types/uml-usecase.d.ts +58 -0
  69. package/lib/types/uml.d.ts +303 -0
  70. package/lib/uml-communication.js +353 -0
  71. package/lib/uml-composite.js +442 -0
  72. package/lib/uml-interaction-overview.js +277 -0
  73. package/lib/uml-profile.js +373 -0
  74. package/lib/uml-structure.js +306 -0
  75. package/lib/uml-syntax.js +545 -0
  76. package/lib/uml-timing.js +974 -0
  77. package/lib/uml-tool.js +213 -0
  78. package/lib/uml-usecase.js +532 -0
  79. package/lib/uml.js +2110 -0
  80. package/locale/en.json +6 -0
  81. package/locale/zh.json +6 -0
  82. package/package.json +128 -6
  83. package/skills/model-everything/SKILL.md +450 -0
  84. package/skills/model-everything/references/dsh-model-schema.md +437 -0
  85. package/skills/model-everything/references/gap-routing-guide.md +42 -0
  86. package/skills/model-everything/references/hash-spec.md +152 -0
  87. package/skills/model-everything/references/logic-verification-guide.md +422 -0
  88. package/skills/model-everything-datamodel/SKILL.md +130 -0
  89. package/skills/model-everything-datamodel/references/data-model-guide.md +84 -0
  90. package/skills/model-everything-datamodel/references/data-model-schema.md +128 -0
  91. package/skills/model-everything-design/SKILL.md +115 -0
  92. package/skills/model-everything-interleaving/SKILL.md +63 -0
  93. package/skills/model-everything-interleaving/SKILL.zh-CN.md +60 -0
  94. package/skills/model-everything-interleaving/references/concurrency-risk-guide.md +63 -0
  95. package/skills/model-everything-report-reading/SKILL.md +137 -0
  96. package/skills/model-everything-report-reading/references/reading-a-report.md +166 -0
  97. package/skills/model-everything-report-reading/references/reading-a-report.mmd +59 -0
  98. package/skills/model-everything-structure/SKILL.md +96 -0
  99. package/skills/model-everything-structure/references/structure-review-guide.md +201 -0
  100. package/skills/model-everything-uml/SKILL.md +60 -0
  101. package/skills/model-everything-uml/references/uml-modeling-guide.md +551 -0
  102. package/src/baseline.ts +288 -0
  103. package/src/compose-tool.ts +82 -0
  104. package/src/concurrency-tool.ts +44 -0
  105. package/src/concurrency.ts +254 -0
  106. package/src/data-engine.ts +1208 -0
  107. package/src/data-tool.ts +91 -0
  108. package/src/design-engine.ts +1263 -0
  109. package/src/design-tool.ts +68 -0
  110. package/src/engine.ts +4564 -0
  111. package/src/export-tool.ts +81 -0
  112. package/src/exporters.ts +399 -0
  113. package/src/gate.ts +330 -0
  114. package/src/granularity.ts +312 -0
  115. package/src/index.ts +289 -0
  116. package/src/json-value.ts +20 -0
  117. package/src/model-file.ts +49 -0
  118. package/src/option-shape.ts +64 -0
  119. package/src/report-diff-tool.ts +67 -0
  120. package/src/report-view.ts +483 -0
  121. package/src/structure-tool.ts +93 -0
  122. package/src/structure.ts +786 -0
  123. package/src/tool.ts +103 -0
  124. package/src/uml-communication.ts +428 -0
  125. package/src/uml-composite.ts +520 -0
  126. package/src/uml-interaction-overview.ts +321 -0
  127. package/src/uml-profile.ts +439 -0
  128. package/src/uml-structure.ts +317 -0
  129. package/src/uml-syntax.ts +764 -0
  130. package/src/uml-timing.ts +1106 -0
  131. package/src/uml-tool.ts +230 -0
  132. package/src/uml-usecase.ts +566 -0
  133. package/src/uml.ts +2139 -0
@@ -0,0 +1,254 @@
1
+ import { REPORT_SCHEMAS, refusalVerdict, verdictOfFindings, type ReportSchema, type Verdict } from './engine.js'
2
+ export interface ConcurrencyFinding {
3
+ code: 'CONCURRENCY_KEYWORD' | 'CONCURRENCY_ABSOLUTE_CLAIM'
4
+ severity: 'warning' | 'error'
5
+ message: string
6
+ line?: number
7
+ snippet?: string
8
+ keyword: string
9
+ /** Which execution model the claim belongs to; a reader routes on it, and `nextSteps` reads it. */
10
+ family: 'threads' | 'synchronization' | 'async' | 'interrupts' | 'shared-state'
11
+ /** Route to dedicated verification tools when an absolute claim is detected (this plugin does not prove concurrency safety). */
12
+ suggestions?: string[]
13
+ }
14
+
15
+ export interface ConcurrencyScanReport {
16
+ ok: boolean
17
+ ran: boolean
18
+ verdict: Verdict
19
+ verdictReason: string
20
+ /** The versioned report contract this result follows. */
21
+ schema: ReportSchema
22
+ findings: ConcurrencyFinding[]
23
+ summary: {
24
+ lines: number
25
+ keywords: number
26
+ absoluteClaims: number
27
+ warnings: number
28
+ errors: number
29
+ }
30
+ /** This scan hashes no model; the field is present so every report has the same shape. */
31
+ hashes: Record<string, string>
32
+ /** Present only on a refusal: the code a caller routes on, and the message behind it. */
33
+ errorCode?: string
34
+ error?: string
35
+ /** What to do next, derived from the findings — never empty. */
36
+ nextSteps: string[]
37
+ }
38
+
39
+ type ClaimFamily = 'threads' | 'synchronization' | 'async' | 'interrupts' | 'shared-state'
40
+
41
+ interface KeywordRule {
42
+ pattern: RegExp
43
+ label: string
44
+ absolute: boolean
45
+ /** What kind of concurrency claim this is: it decides the routing in `suggestionsFor`. */
46
+ family: ClaimFamily
47
+ }
48
+
49
+ /** How a finding names its family in prose. */
50
+ const FAMILY_WORD: Record<ClaimFamily, string> = {
51
+ threads: 'Concurrency',
52
+ synchronization: 'Synchronization',
53
+ async: 'Async',
54
+ interrupts: 'Interrupt',
55
+ 'shared-state': 'Shared-state',
56
+ }
57
+
58
+ const KEYWORD_RULES: KeywordRule[] = [
59
+ // Threads: the vocabulary the scan started with.
60
+ { pattern: /\bthread\s*-?\s*safe\b/i, label: 'thread-safe', absolute: true, family: 'threads' },
61
+ { pattern: /\block\s*-?\s*free\b/i, label: 'lock-free', absolute: true, family: 'threads' },
62
+ { pattern: /\bwait\s*-?\s*free\b/i, label: 'wait-free', absolute: true, family: 'threads' },
63
+ { pattern: /\bno\s+data\s+race\b/i, label: 'no data race', absolute: true, family: 'threads' },
64
+ { pattern: /\brace\s*-?\s*free\b/i, label: 'race-free', absolute: true, family: 'threads' },
65
+ { pattern: /\bdata\s+race\b/i, label: 'data race', absolute: false, family: 'threads' },
66
+ { pattern: /\brace\s+condition\b/i, label: 'race condition', absolute: false, family: 'threads' },
67
+ { pattern: /\bthread\s*-?\s*safety\b/i, label: 'thread safety', absolute: false, family: 'threads' },
68
+ { pattern: /\bconcurrent\b/i, label: 'concurrent', absolute: false, family: 'threads' },
69
+ { pattern: /\bparallel\b/i, label: 'parallel', absolute: false, family: 'threads' },
70
+ { pattern: /\bmulti-?threaded\b/i, label: 'multi-threaded', absolute: false, family: 'threads' },
71
+ { pattern: /\bmultithreaded\b/i, label: 'multithreaded', absolute: false, family: 'threads' },
72
+ // Synchronization primitives: a claim that names one is a claim about what it protects.
73
+ { pattern: /\batomic\b/i, label: 'atomic', absolute: false, family: 'synchronization' },
74
+ { pattern: /\bsynchronized\b/i, label: 'synchronized', absolute: false, family: 'synchronization' },
75
+ { pattern: /\bmutex\b/i, label: 'mutex', absolute: false, family: 'synchronization' },
76
+ { pattern: /\bsemaphore\b/i, label: 'semaphore', absolute: false, family: 'synchronization' },
77
+ { pattern: /\bspinlock\b/i, label: 'spinlock', absolute: false, family: 'synchronization' },
78
+ { pattern: /\breentrant\b/i, label: 'reentrant', absolute: false, family: 'synchronization' },
79
+ // Shared state: no primitive named, so the review asks what guards it.
80
+ { pattern: /\bshared\s+variable\b/i, label: 'shared variable', absolute: false, family: 'shared-state' },
81
+ { pattern: /\bshared\s+memory\b/i, label: 'shared memory', absolute: false, family: 'shared-state' },
82
+ { pattern: /\bglobal\s+state\b/i, label: 'global state', absolute: false, family: 'shared-state' },
83
+ // Interrupts.
84
+ { pattern: /\binterrupt\s*-?\s*safe\b/i, label: 'interrupt-safe', absolute: true, family: 'interrupts' },
85
+ { pattern: /\bISR\s*-?\s*safe\b/i, label: 'ISR-safe', absolute: true, family: 'interrupts' },
86
+ { pattern: /\binterrupt\s+safety\b/i, label: 'interrupt safety', absolute: false, family: 'interrupts' },
87
+ { pattern: /\binterrupt\s+context\b/i, label: 'interrupt context', absolute: false, family: 'interrupts' },
88
+ { pattern: /\bISR\b/i, label: 'ISR', absolute: false, family: 'interrupts' },
89
+ { pattern: /\bIRQ\b/i, label: 'IRQ', absolute: false, family: 'interrupts' },
90
+ { pattern: /\bNMI\b/i, label: 'NMI', absolute: false, family: 'interrupts' },
91
+ { pattern: /\bcritical\s+section\b/i, label: 'critical section', absolute: false, family: 'interrupts' },
92
+ { pattern: /\bdisable_irq\b/i, label: 'disable_irq', absolute: false, family: 'interrupts' },
93
+ { pattern: /\benable_irq\b/i, label: 'enable_irq', absolute: false, family: 'interrupts' },
94
+ { pattern: /\bspin_lock_irqsave\b/i, label: 'spin_lock_irqsave', absolute: false, family: 'interrupts' },
95
+ // Async: a second execution model, with its own failure modes and its own verifiers. These terms were
96
+ // absent, so an async design mined nothing and was refused as "no claim" - a blind spot that reads as a
97
+ // clean scan, the same defect the Chinese vocabulary had.
98
+ { pattern: /\bnon-?\s*blocking\b/i, label: 'non-blocking', absolute: true, family: 'async' },
99
+ { pattern: /\bnever\s+blocks?\b/i, label: 'never blocks', absolute: true, family: 'async' },
100
+ { pattern: /\bno\s+blocking\b/i, label: 'no blocking', absolute: true, family: 'async' },
101
+ { pattern: /\basync\b/i, label: 'async', absolute: false, family: 'async' },
102
+ { pattern: /\bawait(?:s|ing)?\b/i, label: 'await', absolute: false, family: 'async' },
103
+ { pattern: /\bpromises?\b/i, label: 'promise', absolute: false, family: 'async' },
104
+ { pattern: /\bevent\s+loop\b/i, label: 'event loop', absolute: false, family: 'async' },
105
+ { pattern: /\bcallbacks?\b/i, label: 'callback', absolute: false, family: 'async' },
106
+ { pattern: /\bmicrotasks?\b/i, label: 'microtask', absolute: false, family: 'async' },
107
+ { pattern: /\bcoroutines?\b/i, label: 'coroutine', absolute: false, family: 'async' },
108
+ { pattern: /\bfire\s*-?\s*and\s*-?\s*forget\b/i, label: 'fire-and-forget', absolute: false, family: 'async' },
109
+ { pattern: /\bbackpressure\b/i, label: 'backpressure', absolute: false, family: 'async' },
110
+ { pattern: /\btask\s+queue\b/i, label: 'task queue', absolute: false, family: 'async' },
111
+ // Chinese terms. The vocabulary was English-only, so a Chinese document about the same subject mined
112
+ // nothing and landed in the no-claim path - a blind spot that read as a clean scan. A negated form is an
113
+ // absolute claim, and the lookbehind keeps the bare term from firing on the same line as well.
114
+ { pattern: /线程安全/, label: '线程安全 (thread-safe)', absolute: true, family: 'threads' },
115
+ { pattern: /无数据竞争/, label: '无数据竞争 (no data race)', absolute: true, family: 'threads' },
116
+ { pattern: /(?<!无)数据竞争/, label: '数据竞争 (data race)', absolute: false, family: 'threads' },
117
+ { pattern: /无竞态/, label: '无竞态 (race-free)', absolute: true, family: 'threads' },
118
+ { pattern: /(?:无锁|免锁)/, label: '无锁 (lock-free)', absolute: true, family: 'threads' },
119
+ { pattern: /(?:中断安全|ISR安全)/, label: '中断安全 (interrupt-safe)', absolute: true, family: 'interrupts' },
120
+ { pattern: /竞态条件/, label: '竞态条件 (race condition)', absolute: false, family: 'threads' },
121
+ { pattern: /并发/, label: '并发 (concurrent)', absolute: false, family: 'threads' },
122
+ { pattern: /并行/, label: '并行 (parallel)', absolute: false, family: 'threads' },
123
+ { pattern: /多线程/, label: '多线程 (multi-threaded)', absolute: false, family: 'threads' },
124
+ { pattern: /原子/, label: '原子 (atomic)', absolute: false, family: 'synchronization' },
125
+ { pattern: /互斥/, label: '互斥 (mutex)', absolute: false, family: 'synchronization' },
126
+ { pattern: /信号量/, label: '信号量 (semaphore)', absolute: false, family: 'synchronization' },
127
+ { pattern: /自旋锁/, label: '自旋锁 (spinlock)', absolute: false, family: 'synchronization' },
128
+ { pattern: /共享变量/, label: '共享变量 (shared variable)', absolute: false, family: 'shared-state' },
129
+ { pattern: /共享内存/, label: '共享内存 (shared memory)', absolute: false, family: 'shared-state' },
130
+ { pattern: /全局状态/, label: '全局状态 (global state)', absolute: false, family: 'shared-state' },
131
+ { pattern: /可重入/, label: '可重入 (reentrant)', absolute: false, family: 'synchronization' },
132
+ { pattern: /临界区/, label: '临界区 (critical section)', absolute: false, family: 'interrupts' },
133
+ { pattern: /关中断/, label: '关中断 (disable interrupt)', absolute: false, family: 'interrupts' },
134
+ { pattern: /(?:非阻塞|不阻塞|无阻塞|零阻塞)/, label: '非阻塞 (non-blocking)', absolute: true, family: 'async' },
135
+ { pattern: /异步/, label: '异步 (async)', absolute: false, family: 'async' },
136
+ { pattern: /回调/, label: '回调 (callback)', absolute: false, family: 'async' },
137
+ { pattern: /事件循环/, label: '事件循环 (event loop)', absolute: false, family: 'async' },
138
+ { pattern: /微任务/, label: '微任务 (microtask)', absolute: false, family: 'async' },
139
+ { pattern: /协程/, label: '协程 (coroutine)', absolute: false, family: 'async' },
140
+ { pattern: /背压/, label: '背压 (backpressure)', absolute: false, family: 'async' },
141
+ ]
142
+
143
+ /**
144
+ * The text to scan, or a refusal naming what arrived instead.
145
+ *
146
+ * `runConcurrencyScan(undefined)` threw a read of `split` on undefined. The scan proves nothing
147
+ * either way, so a caller who passed the wrong variable must be told which one was wrong rather
148
+ * than left with a crash.
149
+ */
150
+ function requireScanText(value: unknown): string {
151
+ if (typeof value !== 'string') {
152
+ const arrived = value === null ? 'null' : Array.isArray(value) ? 'an array' : typeof value
153
+ throw new Error('text must be a string to scan, got ' + arrived + '. Pass the source or the notes to scan for concurrency claims.')
154
+ }
155
+ return value
156
+ }
157
+
158
+ export function runConcurrencyScan(text: unknown): ConcurrencyScanReport {
159
+ const source = requireScanText(text)
160
+ const lines = source.split(/\r?\n/)
161
+ const findings: ConcurrencyFinding[] = []
162
+ const seen = new Set<string>()
163
+ const suggestionsFor = (family: ClaimFamily): string[] => {
164
+ if (family === 'async') {
165
+ return [
166
+ 'TLA+ (exhaustive interleaving), or model_compose_verify for a handshake between two tasks',
167
+ 'model_verify with maxTicks + tickEvents (a timeout is a deadline), or model_export -> UPPAAL for clocks',
168
+ 'A runtime or static check for the three things a model cannot see: a blocking call on the event loop, a missing await (fire-and-forget), a lock held across an await',
169
+ ]
170
+ }
171
+ if (family === 'threads') {
172
+ return ['TSan / Helgrind (runtime data-race detection)', 'CBMC / static analysis (proof-oriented)', 'TLA+ (exhaustive interleaving model)']
173
+ }
174
+ if (family === 'interrupts') {
175
+ return ['RTOS-aware analysis (interrupt latency, priority inversion)', 'CBMC (state-machine + ISR interleaving proof)', 'TLA+ (preemption/exclusion model)']
176
+ }
177
+ return ['Dedicated concurrency verification (TSan, CBMC, TLA+, or RTOS-specific analysis)']
178
+ }
179
+ lines.forEach((line, index) => {
180
+ const lineNumber = index + 1
181
+ const lower = line.toLowerCase()
182
+ for (const rule of KEYWORD_RULES) {
183
+ if (!rule.pattern.test(line)) continue
184
+ const key = rule.label + ':' + lineNumber
185
+ if (seen.has(key)) continue
186
+ seen.add(key)
187
+ const finding: ConcurrencyFinding = {
188
+ code: rule.absolute ? 'CONCURRENCY_ABSOLUTE_CLAIM' : 'CONCURRENCY_KEYWORD',
189
+ severity: rule.absolute ? 'error' : 'warning',
190
+ message: rule.absolute
191
+ ? FAMILY_WORD[rule.family] + ' safety claim "' + rule.label + '" detected; this plugin does not verify it — it needs dedicated verification.'
192
+ : FAMILY_WORD[rule.family] + '-related term "' + rule.label + '" detected; review whether the plan addresses this risk.',
193
+ line: lineNumber,
194
+ snippet: line.trim().slice(0, 200),
195
+ keyword: rule.label,
196
+ family: rule.family,
197
+ ...(rule.absolute ? { suggestions: suggestionsFor(rule.family) } : {}),
198
+ }
199
+ findings.push(finding)
200
+ }
201
+ })
202
+ const families = new Set(findings.map((finding) => finding.family))
203
+ const errors = findings.filter((finding) => finding.severity === 'error').length
204
+ const warnings = findings.filter((finding) => finding.severity === 'warning').length
205
+ const absoluteClaims = findings.filter((finding) => finding.code === 'CONCURRENCY_ABSOLUTE_CLAIM').length
206
+ const nextSteps: string[] = []
207
+ if (absoluteClaims > 0) {
208
+ nextSteps.push('Every absolute claim must either cite dedicated evidence (a TSan/Helgrind run, a CBMC harness, a TLA+ model of the interleaving) or be restated as a bounded, testable property.')
209
+ }
210
+ if (findings.length > absoluteClaims) {
211
+ nextSteps.push('Review each flagged keyword: a plan that names shared state, a mutex or an interrupt without a synchronization argument is incomplete, not merely verbose.')
212
+ }
213
+ if (families.has('async')) {
214
+ nextSteps.push('For an async design, three defects are invisible to this scan and to any model: a blocking call on the event loop, an await that never happens (fire-and-forget), and a lock held across an await. Name them in the model, or check them at runtime.')
215
+ }
216
+ nextSteps.push('This scan mines claims; it does not prove concurrency safety — route each one to the tool named in its suggestions.')
217
+ if (findings.length === 0) {
218
+ // Nothing was mined, so nothing was examined. Reporting `pass` here said "no concurrency problem"
219
+ // about a text this scan could not see: the same false green as a check that examined nothing, and the
220
+ // common case, because a plan that is not about concurrency at all lands here too.
221
+ const reason = 'the text contains no concurrency claim this scan can mine, so there is nothing to scan. It is either not about concurrency, or it is written in a vocabulary this scan does not match: it reads English terms such as mutex, atomic, thread-safe, async, await and event loop, and Chinese ones such as 互斥, 原子, 线程安全 and 事件循环'
222
+ return {
223
+ ok: false,
224
+ ran: false,
225
+ ...refusalVerdict(reason),
226
+ schema: REPORT_SCHEMAS.concurrency,
227
+ errorCode: 'CONCURRENCY_NO_CLAIM',
228
+ error: reason,
229
+ findings: [],
230
+ summary: { lines: lines.length, keywords: 0, absoluteClaims: 0, warnings: 0, errors: 0 },
231
+ hashes: {},
232
+ nextSteps: [
233
+ 'If the text does discuss concurrency, name the claim explicitly — the resource, the guard, and what it protects — and scan it again.',
234
+ 'This scan mines claims; it does not prove concurrency safety — route each one to the tool named in its suggestions.',
235
+ ],
236
+ }
237
+ }
238
+ return {
239
+ ok: true,
240
+ ran: true,
241
+ ...verdictOfFindings(findings),
242
+ schema: REPORT_SCHEMAS.concurrency,
243
+ findings,
244
+ summary: {
245
+ lines: lines.length,
246
+ keywords: findings.length,
247
+ absoluteClaims,
248
+ warnings,
249
+ errors,
250
+ },
251
+ hashes: {},
252
+ nextSteps,
253
+ }
254
+ }