dsh-logicprobe 0.4.0 → 0.5.1

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 (33) hide show
  1. package/README.en-US.md +10 -3
  2. package/README.md +10 -3
  3. package/lib/concurrency-tool.js +34 -0
  4. package/lib/concurrency.js +76 -0
  5. package/lib/data-engine.js +930 -0
  6. package/lib/data-tool.js +61 -0
  7. package/lib/engine.js +258 -0
  8. package/lib/index.js +37 -23
  9. package/lib/tool.js +1 -1
  10. package/lib/types/concurrency-tool.d.ts +8 -0
  11. package/lib/types/concurrency.d.ts +20 -0
  12. package/lib/types/data-engine.d.ts +199 -0
  13. package/lib/types/data-tool.d.ts +10 -0
  14. package/lib/types/engine.d.ts +20 -0
  15. package/package.json +82 -81
  16. package/skills/logicprobe/SKILL.md +285 -268
  17. package/skills/logicprobe/references/__pycache__/verification-harness.cpython-312.pyc +0 -0
  18. package/skills/logicprobe/references/concurrency-risk-guide.md +54 -0
  19. package/skills/logicprobe/references/dsh-model-schema.md +145 -129
  20. package/skills/logicprobe/references/logic-verification-guide.md +463 -413
  21. package/skills/logicprobe/references/verification-harness.py +806 -582
  22. package/skills/logicprobe-datamodel/SKILL.md +124 -0
  23. package/skills/logicprobe-datamodel/references/__pycache__/data-model-harness.cpython-312.pyc +0 -0
  24. package/skills/logicprobe-datamodel/references/data-model-guide.md +62 -0
  25. package/skills/logicprobe-datamodel/references/data-model-harness.py +528 -0
  26. package/skills/logicprobe-datamodel/references/data-model-schema.md +128 -0
  27. package/src/concurrency-tool.ts +37 -0
  28. package/src/concurrency.ts +102 -0
  29. package/src/data-engine.ts +1001 -0
  30. package/src/data-tool.ts +65 -0
  31. package/src/engine.ts +234 -0
  32. package/src/index.ts +315 -301
  33. package/src/tool.ts +60 -60
@@ -0,0 +1,128 @@
1
+ # DataModelV1 — logicprobe-datamodel Schema
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`.
4
+
5
+ ## Top-level model
6
+
7
+ ```json
8
+ {
9
+ "schemaVersion": 1,
10
+ "entities": [
11
+ {
12
+ "name": "User",
13
+ "fields": [
14
+ { "name": "id", "type": "uuid", "required": true, "unique": true },
15
+ { "name": "email", "type": "string", "required": true, "unique": true }
16
+ ],
17
+ "primaryKey": ["id"]
18
+ }
19
+ ],
20
+ "relationships": [
21
+ { "fromEntity": "Order", "fromField": "userId", "toEntity": "User", "toField": "id", "onDelete": "restrict" }
22
+ ],
23
+ "invariants": [],
24
+ "boundaryChecks": []
25
+ }
26
+ ```
27
+
28
+ | Field | Required | Meaning |
29
+ |---|---|---|
30
+ | `schemaVersion` | yes | Must be `1` |
31
+ | `entities` | yes | `{ name, fields, primaryKey?, uniqueKeys?, indexes? }` |
32
+ | `relationships` | no | `{ fromEntity, fromField, toEntity, toField?, onDelete? }` |
33
+ | `invariants` | no | See invariant kinds below |
34
+ | `boundaryChecks` | no | `{ entity, field, values? }` for DA2 |
35
+
36
+ ## Field
37
+
38
+ ```json
39
+ {
40
+ "name": "age",
41
+ "type": "integer",
42
+ "required": true,
43
+ "unique": false,
44
+ "nullable": false,
45
+ "default": 0,
46
+ "min": 0,
47
+ "max": 150,
48
+ "minLength": null,
49
+ "maxLength": null,
50
+ "pattern": null,
51
+ "enum": null,
52
+ "ref": null,
53
+ "items": null,
54
+ "monotonic": null
55
+ }
56
+ ```
57
+
58
+ Supported `type` values: `string`, `integer`, `number`, `boolean`, `uuid`, `date`, `datetime`, `timestamp`, `json`, `enum`, `array`, `object`, `binary`, or any custom non-empty type name.
59
+
60
+ ## Invariants
61
+
62
+ | Kind | Shape | Checks |
63
+ |---|---|---|
64
+ | `field-required` | `{ entity, field }` | Field must be required |
65
+ | `unique` | `{ entity, fields: [] }` | Unique constraint must exist |
66
+ | `referential-integrity` | `{ entity, field, refEntity, refField? }` | Reference target must exist |
67
+ | `range` | `{ entity, field, min?, max? }` | Range must be respected |
68
+ | `count-equal` | `{ sourceEntity, targetEntity }` | Source/target counts must match |
69
+ | `field-equal` | `{ sourceEntity, sourceField, targetEntity, targetField }` | Two fields must be equal |
70
+ | `no-orphan` | `{ entity, field, refEntity }` | No dangling references |
71
+ | `idempotent-copy` | `{ sourceEntity, targetEntity }` | A matching copy pair must exist; the copy is intended to be replay-safe |
72
+ | `idempotent-migration` | `{ from, to }` | A matching migration mapping must exist; split/merge/drop are flagged as non-idempotent |
73
+ | `monotonic` | `{ entity, field, direction: "inc"\|"dec" }` | Field must move only in the declared direction |
74
+ | `sequence` | `{ steps: ["c1", "m1"] }` | Referenced copy/migration step ids must exist |
75
+ | `leads-to` | `{ entity, field, from, to }` | For enum/status fields, both values must exist |
76
+ | `atomicity` | `{ steps: ["c1", "m1"] }` | Steps must exist; non-atomic transforms and missing backups are flagged |
77
+
78
+ ## Tool arguments
79
+
80
+ ```json
81
+ {
82
+ "model": { "...": "AFTER DataModelV1" },
83
+ "beforeModel": { "...": "BEFORE DataModelV1" },
84
+ "fieldMapping": { "User.name": "User.fullName" },
85
+ "copyPairs": [
86
+ { "id": "c1", "sourceEntity": "Source", "targetEntity": "Target", "mapping": { "a": "x" } }
87
+ ],
88
+ "migrationMappings": [
89
+ { "id": "m1", "from": "User.name", "to": "User.fullName", "transform": "rename" }
90
+ ],
91
+ "backupPairs": [
92
+ { "id": "b1", "sourceEntity": "Source", "targetEntity": "Target", "mapping": { "x": "a" } }
93
+ ]
94
+ }
95
+ ```
96
+
97
+ ## Checks
98
+
99
+ - **DS1-DS4**: schema well-formedness, required-field completeness, relationship integrity, type/nullability consistency
100
+ - **DA1-DA12**: null/empty injection, boundary blast, uniqueness, referential integrity, migration coverage, copy consistency, rollback/backup symmetry, idempotent constraints, data monotonic, data sequence, data leads-to, data atomicity
101
+ - **DD1-DD4**: data behavior preservation, data invariant continuity, delta summary, breaking-change regression
102
+
103
+ ## Minimal example
104
+
105
+ ```json
106
+ {
107
+ "schemaVersion": 1,
108
+ "entities": [
109
+ {
110
+ "name": "User",
111
+ "fields": [
112
+ { "name": "id", "type": "uuid", "required": true },
113
+ { "name": "age", "type": "integer", "min": 0, "max": 150 }
114
+ ],
115
+ "primaryKey": ["id"]
116
+ }
117
+ ],
118
+ "boundaryChecks": [
119
+ { "entity": "User", "field": "age", "values": [-1, 0, 150, 151] }
120
+ ]
121
+ }
122
+ ```
123
+
124
+ ## Limits
125
+
126
+ - The engine is a design-time static verifier. It does not execute against a live database.
127
+ - Data invariants like `count-equal` and `field-equal` are checked structurally (referenced entities/fields exist) unless sample data is provided in a future version.
128
+ - Follow with runtime tools (Great Expectations, Soda, Pandera) for live data validation.
@@ -0,0 +1,37 @@
1
+ import { defineTool, type JsonValue } from '@deepseek-ai/dsh-tools'
2
+ import { runConcurrencyScan } from './concurrency.js'
3
+
4
+ export const LOGICPROBE_CONCURRENCY_SCAN_TOOL_NAME = 'logicprobe_concurrency_scan'
5
+
6
+ /**
7
+ * Model-visible DSH tool that mines design documents/plans for concurrency-related
8
+ * claims and risk keywords. It does not prove concurrency safety; it flags terms
9
+ * such as "thread-safe", "lock-free", "race condition", "atomic", "mutex", etc.,
10
+ * so the model can either provide dedicated evidence or mark the claim unverified.
11
+ */
12
+ export const logicProbeConcurrencyScanTool = defineTool({
13
+ name: LOGICPROBE_CONCURRENCY_SCAN_TOOL_NAME,
14
+ description:
15
+ 'Scan a document or plan text for concurrency risk points. Use ONLY after confirming the target actually has concurrency requirements or behavior (threads, async tasks, interrupts, shared state, parallel execution). If the target is purely sequential, do not call this tool. Returns findings for keywords like thread-safe, lock-free, data race, race condition, atomic, synchronized, mutex, semaphore, shared variable, reentrant, interrupt-safe. Absolute claims (thread-safe, lock-free, no data race) are flagged as errors requiring dedicated verification.',
16
+ parameters: {
17
+ text: {
18
+ type: 'string',
19
+ required: true,
20
+ description: 'Document or plan text to scan for concurrency-related claims.',
21
+ },
22
+ },
23
+ output: {
24
+ schema: {
25
+ type: 'json',
26
+ description: 'Concurrency scan report with findings and summary.',
27
+ },
28
+ render(_args, value) {
29
+ return [{ type: 'text' as const, text: JSON.stringify(value, null, 2) }]
30
+ },
31
+ },
32
+ timeoutMs: 10_000,
33
+ isConcurrencySafe: () => true,
34
+ async execute(args) {
35
+ return runConcurrencyScan(args.text) as unknown as JsonValue
36
+ },
37
+ })
@@ -0,0 +1,102 @@
1
+ export interface ConcurrencyFinding {
2
+ code: 'CONCURRENCY_KEYWORD' | 'CONCURRENCY_ABSOLUTE_CLAIM'
3
+ severity: 'warning' | 'error'
4
+ message: string
5
+ line?: number
6
+ snippet?: string
7
+ keyword: string
8
+ }
9
+
10
+ export interface ConcurrencyScanReport {
11
+ ok: boolean
12
+ findings: ConcurrencyFinding[]
13
+ summary: {
14
+ lines: number
15
+ keywords: number
16
+ absoluteClaims: number
17
+ warnings: number
18
+ errors: number
19
+ }
20
+ }
21
+
22
+ interface KeywordRule {
23
+ pattern: RegExp
24
+ label: string
25
+ absolute: boolean
26
+ }
27
+
28
+ const KEYWORD_RULES: KeywordRule[] = [
29
+ { pattern: /\bthread\s*-?\s*safe\b/i, label: 'thread-safe', absolute: true },
30
+ { pattern: /\block\s*-?\s*free\b/i, label: 'lock-free', absolute: true },
31
+ { pattern: /\bwait\s*-?\s*free\b/i, label: 'wait-free', absolute: true },
32
+ { pattern: /\bno\s+data\s+race\b/i, label: 'no data race', absolute: true },
33
+ { pattern: /\brace\s*-?\s*free\b/i, label: 'race-free', absolute: true },
34
+ { pattern: /\bdata\s+race\b/i, label: 'data race', absolute: false },
35
+ { pattern: /\brace\s+condition\b/i, label: 'race condition', absolute: false },
36
+ { pattern: /\bthread\s*-?\s*safety\b/i, label: 'thread safety', absolute: false },
37
+ { pattern: /\bconcurrent\b/i, label: 'concurrent', absolute: false },
38
+ { pattern: /\bparallel\b/i, label: 'parallel', absolute: false },
39
+ { pattern: /\bmulti-?threaded\b/i, label: 'multi-threaded', absolute: false },
40
+ { pattern: /\bmultithreaded\b/i, label: 'multithreaded', absolute: false },
41
+ { pattern: /\batomic\b/i, label: 'atomic', absolute: false },
42
+ { pattern: /\bsynchronized\b/i, label: 'synchronized', absolute: false },
43
+ { pattern: /\bmutex\b/i, label: 'mutex', absolute: false },
44
+ { pattern: /\bsemaphore\b/i, label: 'semaphore', absolute: false },
45
+ { pattern: /\bspinlock\b/i, label: 'spinlock', absolute: false },
46
+ { pattern: /\bshared\s+variable\b/i, label: 'shared variable', absolute: false },
47
+ { pattern: /\bshared\s+memory\b/i, label: 'shared memory', absolute: false },
48
+ { pattern: /\bglobal\s+state\b/i, label: 'global state', absolute: false },
49
+ { pattern: /\breentrant\b/i, label: 'reentrant', absolute: false },
50
+ { pattern: /\binterrupt\s*-?\s*safe\b/i, label: 'interrupt-safe', absolute: true },
51
+ { pattern: /\bISR\s*-?\s*safe\b/i, label: 'ISR-safe', absolute: true },
52
+ { pattern: /\binterrupt\s+safety\b/i, label: 'interrupt safety', absolute: false },
53
+ { pattern: /\binterrupt\s+context\b/i, label: 'interrupt context', absolute: false },
54
+ { pattern: /\bISR\b/i, label: 'ISR', absolute: false },
55
+ { pattern: /\bIRQ\b/i, label: 'IRQ', absolute: false },
56
+ { pattern: /\bNMI\b/i, label: 'NMI', absolute: false },
57
+ { pattern: /\bcritical\s+section\b/i, label: 'critical section', absolute: false },
58
+ { pattern: /\bdisable_irq\b/i, label: 'disable_irq', absolute: false },
59
+ { pattern: /\benable_irq\b/i, label: 'enable_irq', absolute: false },
60
+ { pattern: /\bspin_lock_irqsave\b/i, label: 'spin_lock_irqsave', absolute: false },
61
+ ]
62
+
63
+ export function runConcurrencyScan(text: string): ConcurrencyScanReport {
64
+ const lines = text.split(/\r?\n/)
65
+ const findings: ConcurrencyFinding[] = []
66
+ const seen = new Set<string>()
67
+ lines.forEach((line, index) => {
68
+ const lineNumber = index + 1
69
+ const lower = line.toLowerCase()
70
+ for (const rule of KEYWORD_RULES) {
71
+ if (!rule.pattern.test(line)) continue
72
+ const key = rule.label + ':' + lineNumber
73
+ if (seen.has(key)) continue
74
+ seen.add(key)
75
+ const finding: ConcurrencyFinding = {
76
+ code: rule.absolute ? 'CONCURRENCY_ABSOLUTE_CLAIM' : 'CONCURRENCY_KEYWORD',
77
+ severity: rule.absolute ? 'error' : 'warning',
78
+ message: rule.absolute
79
+ ? 'Concurrency safety claim "' + rule.label + '" detected; this requires dedicated verification (TSan, model checker, or explicit proof).'
80
+ : 'Concurrency-related term "' + rule.label + '" detected; review whether the plan addresses this risk.',
81
+ line: lineNumber,
82
+ snippet: line.trim().slice(0, 200),
83
+ keyword: rule.label,
84
+ }
85
+ findings.push(finding)
86
+ }
87
+ })
88
+ const errors = findings.filter((finding) => finding.severity === 'error').length
89
+ const warnings = findings.filter((finding) => finding.severity === 'warning').length
90
+ const absoluteClaims = findings.filter((finding) => finding.code === 'CONCURRENCY_ABSOLUTE_CLAIM').length
91
+ return {
92
+ ok: true,
93
+ findings,
94
+ summary: {
95
+ lines: lines.length,
96
+ keywords: findings.length,
97
+ absoluteClaims,
98
+ warnings,
99
+ errors,
100
+ },
101
+ }
102
+ }