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.
- package/LICENSE +21 -0
- package/README.en-US.md +332 -0
- package/README.md +332 -3
- package/cordis.patch.yml +20 -0
- package/examples/README.md +48 -0
- package/examples/control-dispatcher.json +113 -0
- package/examples/control-dispatcher.overbudget.json +113 -0
- package/examples/ecommerce-data-model.json +185 -0
- package/examples/order-state-machine.after.json +116 -0
- package/examples/order-state-machine.before.json +101 -0
- package/examples/user-migration.after.json +29 -0
- package/examples/user-migration.before.json +29 -0
- package/gate/locale/en.json +6 -0
- package/gate/locale/zh.json +6 -0
- package/gate/package.json +5 -0
- package/lib/baseline.js +249 -0
- package/lib/compose-tool.js +80 -0
- package/lib/concurrency-tool.js +40 -0
- package/lib/concurrency.js +206 -0
- package/lib/data-engine.js +1141 -0
- package/lib/data-tool.js +87 -0
- package/lib/design-engine.js +1149 -0
- package/lib/design-tool.js +63 -0
- package/lib/engine.js +4343 -0
- package/lib/export-tool.js +78 -0
- package/lib/exporters.js +434 -0
- package/lib/gate.js +274 -0
- package/lib/granularity.js +237 -0
- package/lib/index.js +237 -0
- package/lib/json-value.js +18 -0
- package/lib/model-file.js +48 -0
- package/lib/option-shape.js +62 -0
- package/lib/report-diff-tool.js +63 -0
- package/lib/report-view.js +433 -0
- package/lib/structure-tool.js +91 -0
- package/lib/structure.js +711 -0
- package/lib/tool.js +94 -0
- package/lib/types/baseline.d.ts +65 -0
- package/lib/types/compose-tool.d.ts +7 -0
- package/lib/types/concurrency-tool.d.ts +8 -0
- package/lib/types/concurrency.d.ts +37 -0
- package/lib/types/data-engine.d.ts +211 -0
- package/lib/types/data-tool.d.ts +10 -0
- package/lib/types/design-engine.d.ts +215 -0
- package/lib/types/design-tool.d.ts +11 -0
- package/lib/types/engine.d.ts +494 -0
- package/lib/types/export-tool.d.ts +7 -0
- package/lib/types/exporters.d.ts +37 -0
- package/lib/types/gate.d.ts +91 -0
- package/lib/types/granularity.d.ts +106 -0
- package/lib/types/index.d.ts +45 -0
- package/lib/types/json-value.d.ts +21 -0
- package/lib/types/model-file.d.ts +7 -0
- package/lib/types/option-shape.d.ts +22 -0
- package/lib/types/report-diff-tool.d.ts +8 -0
- package/lib/types/report-view.d.ts +50 -0
- package/lib/types/structure-tool.d.ts +10 -0
- package/lib/types/structure.d.ts +152 -0
- package/lib/types/tool.d.ts +11 -0
- package/lib/types/uml-communication.d.ts +109 -0
- package/lib/types/uml-composite.d.ts +107 -0
- package/lib/types/uml-interaction-overview.d.ts +91 -0
- package/lib/types/uml-profile.d.ts +112 -0
- package/lib/types/uml-structure.d.ts +53 -0
- package/lib/types/uml-syntax.d.ts +296 -0
- package/lib/types/uml-timing.d.ts +152 -0
- package/lib/types/uml-tool.d.ts +12 -0
- package/lib/types/uml-usecase.d.ts +58 -0
- package/lib/types/uml.d.ts +303 -0
- package/lib/uml-communication.js +353 -0
- package/lib/uml-composite.js +442 -0
- package/lib/uml-interaction-overview.js +277 -0
- package/lib/uml-profile.js +373 -0
- package/lib/uml-structure.js +306 -0
- package/lib/uml-syntax.js +545 -0
- package/lib/uml-timing.js +974 -0
- package/lib/uml-tool.js +213 -0
- package/lib/uml-usecase.js +532 -0
- package/lib/uml.js +2110 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +128 -6
- package/skills/model-everything/SKILL.md +450 -0
- package/skills/model-everything/references/dsh-model-schema.md +437 -0
- package/skills/model-everything/references/gap-routing-guide.md +42 -0
- package/skills/model-everything/references/hash-spec.md +152 -0
- package/skills/model-everything/references/logic-verification-guide.md +422 -0
- package/skills/model-everything-datamodel/SKILL.md +130 -0
- package/skills/model-everything-datamodel/references/data-model-guide.md +84 -0
- package/skills/model-everything-datamodel/references/data-model-schema.md +128 -0
- package/skills/model-everything-design/SKILL.md +115 -0
- package/skills/model-everything-interleaving/SKILL.md +63 -0
- package/skills/model-everything-interleaving/SKILL.zh-CN.md +60 -0
- package/skills/model-everything-interleaving/references/concurrency-risk-guide.md +63 -0
- package/skills/model-everything-report-reading/SKILL.md +137 -0
- package/skills/model-everything-report-reading/references/reading-a-report.md +166 -0
- package/skills/model-everything-report-reading/references/reading-a-report.mmd +59 -0
- package/skills/model-everything-structure/SKILL.md +96 -0
- package/skills/model-everything-structure/references/structure-review-guide.md +201 -0
- package/skills/model-everything-uml/SKILL.md +60 -0
- package/skills/model-everything-uml/references/uml-modeling-guide.md +551 -0
- package/src/baseline.ts +288 -0
- package/src/compose-tool.ts +82 -0
- package/src/concurrency-tool.ts +44 -0
- package/src/concurrency.ts +254 -0
- package/src/data-engine.ts +1208 -0
- package/src/data-tool.ts +91 -0
- package/src/design-engine.ts +1263 -0
- package/src/design-tool.ts +68 -0
- package/src/engine.ts +4564 -0
- package/src/export-tool.ts +81 -0
- package/src/exporters.ts +399 -0
- package/src/gate.ts +330 -0
- package/src/granularity.ts +312 -0
- package/src/index.ts +289 -0
- package/src/json-value.ts +20 -0
- package/src/model-file.ts +49 -0
- package/src/option-shape.ts +64 -0
- package/src/report-diff-tool.ts +67 -0
- package/src/report-view.ts +483 -0
- package/src/structure-tool.ts +93 -0
- package/src/structure.ts +786 -0
- package/src/tool.ts +103 -0
- package/src/uml-communication.ts +428 -0
- package/src/uml-composite.ts +520 -0
- package/src/uml-interaction-overview.ts +321 -0
- package/src/uml-profile.ts +439 -0
- package/src/uml-structure.ts +317 -0
- package/src/uml-syntax.ts +764 -0
- package/src/uml-timing.ts +1106 -0
- package/src/uml-tool.ts +230 -0
- package/src/uml-usecase.ts +566 -0
- package/src/uml.ts +2139 -0
package/lib/tool.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
2
|
+
import { runVerification } from './engine.js';
|
|
3
|
+
import { renderReportText } from './report-view.js';
|
|
4
|
+
import { OptionShapeError, optionRefusal, requireObjectOption } from './option-shape.js';
|
|
5
|
+
import { oneOf } from './model-file.js';
|
|
6
|
+
export const MODEL_VERIFY_TOOL_NAME = 'model_verify';
|
|
7
|
+
/**
|
|
8
|
+
* Model-visible DSH tool wrapping the bundled TypeScript verification engine.
|
|
9
|
+
* The model passes a LogicModelV1 object; the engine validates it and returns
|
|
10
|
+
* the 22-check report (S1-S8 structural, A1-A14), or a 26-check
|
|
11
|
+
* report when beforeModel is supplied (D1 behavioral preservation, D2
|
|
12
|
+
* invariant continuity, D3 regression delta, D4 deadlock/liveness regression).
|
|
13
|
+
* This is the dsh-native path: the model is built and run here, with no
|
|
14
|
+
* template to fill in by hand.
|
|
15
|
+
*/
|
|
16
|
+
export const modelVerifyTool = defineTool({
|
|
17
|
+
name: MODEL_VERIFY_TOOL_NAME,
|
|
18
|
+
description: 'Verify one state machine: does this LogicModelV1 reach a state it must not, deadlock, livelock, or leave an invariant undecided. Model shape: schemaVersion 1, init, states ({id, terminal?}), transitions ({from, event, to, guard?, updates?, cost?}), plus optional variables, invariants, concurrentPairs, boundaryChecks, resourcePairs, idempotentEvents and narrative. Guards are structured: {variable, op, value}, {all}, {any} or {not}. Invariants cover never-states, var-in-range, event-before-state, leads-to, sequence, atomicity, budget, probability and deadline. `cost` defaults to 1 and drives the path-cost-against-budget check. `weight` defaults to 1 and makes the model a DTMC, which is what the probabilistic-reachability check needs. A state with `maxTicks` needs top-level `tickEvents`, or the deadline check reports it as over-resident. The schema is closed: an undeclared key is a validation error. Keys starting with `_` are metadata: ignored, kept out of `modelHash`, and echoed as `metadataKeys`. Variables support monotonic inc and dec. Pass `beforeModel` and `stateMapping` to compare the two models of a refactor for behaviour preservation, invariant continuity and deadlock regression. For two or more machines interacting, use `model_compose_verify` instead: this tool decides one machine at a time. Read `verdict`, never `ok`. The full schema is in the `model_verify` skill: references/dsh-model-schema.md.',
|
|
19
|
+
parameters: {
|
|
20
|
+
model: {
|
|
21
|
+
type: 'json',
|
|
22
|
+
description: 'LogicModelV1 state machine model to verify. Pass this, or modelPath, not both.',
|
|
23
|
+
},
|
|
24
|
+
modelPath: {
|
|
25
|
+
type: 'string',
|
|
26
|
+
description: 'Path to a JSON file holding the model, instead of inlining it. Formatting does not matter: whitespace, a BOM and pretty printing all parse. Relative paths resolve against the process working directory, which is the session workspace.',
|
|
27
|
+
},
|
|
28
|
+
maxStates: {
|
|
29
|
+
type: 'integer',
|
|
30
|
+
description: 'Maximum runtime states to explore. Default 10000.',
|
|
31
|
+
},
|
|
32
|
+
maxPermutationEvents: {
|
|
33
|
+
type: 'integer',
|
|
34
|
+
description: 'Maximum event count for A3 order permutation. Default 5.',
|
|
35
|
+
},
|
|
36
|
+
beforeModel: {
|
|
37
|
+
type: 'json',
|
|
38
|
+
description: 'Optional BEFORE LogicModelV1 state machine model for refactoring/migration regression comparison.',
|
|
39
|
+
},
|
|
40
|
+
stateMapping: {
|
|
41
|
+
type: 'json',
|
|
42
|
+
description: 'Optional object mapping BEFORE state ids to AFTER state ids. Omit for identity mapping (same state names).',
|
|
43
|
+
},
|
|
44
|
+
check: {
|
|
45
|
+
type: 'string',
|
|
46
|
+
description: 'Print one check in full when the digest truncated it. Pass the check id the CHECKS line lists (for example A9); the per-check budget no longer applies, and an id this report does not carry is answered with the ids that it does.',
|
|
47
|
+
},
|
|
48
|
+
reportView: {
|
|
49
|
+
type: 'string',
|
|
50
|
+
enum: ['digest', 'full'],
|
|
51
|
+
description: 'digest (default) prints the readable body; full prints the structured report, which report_diff needs.',
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
output: {
|
|
55
|
+
schema: {
|
|
56
|
+
type: 'json',
|
|
57
|
+
description: 'this plugin verification report with summary and per-check findings.',
|
|
58
|
+
},
|
|
59
|
+
render(args, value) {
|
|
60
|
+
return [{ type: 'text', text: renderReportText(value, { full: args.reportView === 'full', check: args.check }) }];
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
timeoutMs: 10_000,
|
|
64
|
+
isConcurrencySafe: () => true,
|
|
65
|
+
async execute(args) {
|
|
66
|
+
try {
|
|
67
|
+
// `stateMapping` is declared `json`, so a string, a list or a map to non-strings all arrive here, and the
|
|
68
|
+
// engine reads the option with `mapping[state] ?? state`: every one of those quietly became the identity
|
|
69
|
+
// mapping. A renamed state then read as "A was removed and A2 was added" - a D1 error produced by the shape
|
|
70
|
+
// of an option rather than by the refactor, which is the one thing a regression comparison must not do.
|
|
71
|
+
requireObjectOption(args.stateMapping, 'stateMapping', 'VERIFY_OPTION_INVALID');
|
|
72
|
+
const mapping = args.stateMapping;
|
|
73
|
+
if (mapping !== undefined) {
|
|
74
|
+
if (args.beforeModel === undefined) {
|
|
75
|
+
throw new OptionShapeError('VERIFY_OPTION_INVALID', 'stateMapping was passed without beforeModel: the mapping says how BEFORE state ids turn into AFTER ones, so there is nothing to map. Pass beforeModel, or omit the mapping.');
|
|
76
|
+
}
|
|
77
|
+
const [from, to] = Object.entries(mapping).find(([, value]) => typeof value !== 'string' || value === '') ?? [];
|
|
78
|
+
if (from !== undefined) {
|
|
79
|
+
throw new OptionShapeError('VERIFY_OPTION_INVALID', 'stateMapping.' + from + ' is not a state id (it is ' + (Array.isArray(to) ? 'an array' : to === null ? 'null' : 'a ' + typeof to) + '). Every value is the AFTER state id the BEFORE state maps to, so it has to be a non-empty string.');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const model = oneOf(args.model, args.modelPath, 'model', 'modelPath', 'VERIFY_OPTION_INVALID');
|
|
83
|
+
return runVerification(model, {
|
|
84
|
+
maxStates: args.maxStates,
|
|
85
|
+
maxPermutationEvents: args.maxPermutationEvents,
|
|
86
|
+
beforeModel: args.beforeModel,
|
|
87
|
+
stateMapping: mapping,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
return optionRefusal(error, 'VERIFY_OPTION_INVALID');
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Report diffing: "did this slice add findings, or remove them?"
|
|
3
|
+
*
|
|
4
|
+
* The acceptance criterion of a refactoring slice is usually *not* "zero findings" —
|
|
5
|
+
* it is "no NEW findings, and none of the old ones got worse". Comparing two JSON
|
|
6
|
+
* reports by hand is how that gets fudged, so the comparison is code.
|
|
7
|
+
*
|
|
8
|
+
* A finding's identity is its machine-readable locator, never its prose:
|
|
9
|
+
* `check|code|<canonical evidence + path>`. Findings that carry neither evidence nor a
|
|
10
|
+
* path fall back to their message, because an engine that reports nothing structured
|
|
11
|
+
* has nothing stabler to match on. Two runs over the same input must produce zero
|
|
12
|
+
* added and zero removed findings.
|
|
13
|
+
*
|
|
14
|
+
* @module dsh-model-everything-baseline
|
|
15
|
+
*/
|
|
16
|
+
import { type Finding, type Verdict, type VerdictSummary } from './engine.js';
|
|
17
|
+
/** A finding as it appears in any report family, after flattening. */
|
|
18
|
+
export interface ReportFinding extends Finding {
|
|
19
|
+
/** The check that emitted it (verify/datamodel: `S2`, `C1`, …); absent for flat families. */
|
|
20
|
+
check?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface BaselineChange {
|
|
23
|
+
identity: string;
|
|
24
|
+
code: string;
|
|
25
|
+
before: {
|
|
26
|
+
severity: string;
|
|
27
|
+
message: string;
|
|
28
|
+
};
|
|
29
|
+
after: {
|
|
30
|
+
severity: string;
|
|
31
|
+
message: string;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export interface BaselineReport {
|
|
35
|
+
ok: boolean;
|
|
36
|
+
ran: boolean;
|
|
37
|
+
verdict: Verdict;
|
|
38
|
+
verdictReason: string;
|
|
39
|
+
/** The versioned report contract this result follows. */
|
|
40
|
+
schema: string;
|
|
41
|
+
/** The current report's own verdict, echoed so "no new findings" cannot hide a failing run. */
|
|
42
|
+
currentVerdict: Verdict;
|
|
43
|
+
added: ReportFinding[];
|
|
44
|
+
removed: ReportFinding[];
|
|
45
|
+
changed: BaselineChange[];
|
|
46
|
+
summary: {
|
|
47
|
+
baseline: number;
|
|
48
|
+
current: number;
|
|
49
|
+
added: number;
|
|
50
|
+
removed: number;
|
|
51
|
+
changed: number;
|
|
52
|
+
addedErrors: number;
|
|
53
|
+
addedWarnings: number;
|
|
54
|
+
};
|
|
55
|
+
nextSteps: string[];
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The stable identity of a finding: check + file + code + a canonical locator. Prose is
|
|
59
|
+
* excluded so a reworded message reads as `changed`, not as a new finding.
|
|
60
|
+
*/
|
|
61
|
+
export declare function findingIdentity(finding: ReportFinding): string;
|
|
62
|
+
/** Flatten a report of any family into one finding list (`checks[].findings` or `findings[]`). */
|
|
63
|
+
export declare function flattenReportFindings(report: unknown): ReportFinding[];
|
|
64
|
+
export declare function diffReports(baseline: unknown, current: unknown): BaselineReport;
|
|
65
|
+
export type { VerdictSummary };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const COMPOSE_TOOL_NAME = "model_compose_verify";
|
|
2
|
+
/**
|
|
3
|
+
* DSH tool wrapping multi-machine composition verification:
|
|
4
|
+
* product-space reachability with rendezvous handshakes (C1 deadlock, C2 sync), and the shared
|
|
5
|
+
* locations the machines both touch (C3 declarations, C4 unordered write, C5 non-atomic update).
|
|
6
|
+
*/
|
|
7
|
+
export declare const modelComposeTool: import("@deepseek-ai/dsh-tools").ToolDefinition;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const INTERLEAVING_SCAN_TOOL_NAME = "model_interleaving_scan";
|
|
2
|
+
/**
|
|
3
|
+
* Model-visible DSH tool that mines design documents/plans for concurrency-related
|
|
4
|
+
* claims and risk keywords. It does not prove concurrency safety; it flags terms
|
|
5
|
+
* such as "thread-safe", "lock-free", "race condition", "atomic", "mutex", etc.,
|
|
6
|
+
* so the model can either provide dedicated evidence or mark the claim unverified.
|
|
7
|
+
*/
|
|
8
|
+
export declare const modelConcurrencyScanTool: import("@deepseek-ai/dsh-tools").ToolDefinition;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { 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
|
+
export interface ConcurrencyScanReport {
|
|
15
|
+
ok: boolean;
|
|
16
|
+
ran: boolean;
|
|
17
|
+
verdict: Verdict;
|
|
18
|
+
verdictReason: string;
|
|
19
|
+
/** The versioned report contract this result follows. */
|
|
20
|
+
schema: ReportSchema;
|
|
21
|
+
findings: ConcurrencyFinding[];
|
|
22
|
+
summary: {
|
|
23
|
+
lines: number;
|
|
24
|
+
keywords: number;
|
|
25
|
+
absoluteClaims: number;
|
|
26
|
+
warnings: number;
|
|
27
|
+
errors: number;
|
|
28
|
+
};
|
|
29
|
+
/** This scan hashes no model; the field is present so every report has the same shape. */
|
|
30
|
+
hashes: Record<string, string>;
|
|
31
|
+
/** Present only on a refusal: the code a caller routes on, and the message behind it. */
|
|
32
|
+
errorCode?: string;
|
|
33
|
+
error?: string;
|
|
34
|
+
/** What to do next, derived from the findings — never empty. */
|
|
35
|
+
nextSteps: string[];
|
|
36
|
+
}
|
|
37
|
+
export declare function runConcurrencyScan(text: unknown): ConcurrencyScanReport;
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { type CheckResult, type ReportSchema, type Verdict } from './engine.js';
|
|
2
|
+
export declare const DATA_ENGINE_SCHEMA_VERSION = 1;
|
|
3
|
+
export type DataValue = number | string | boolean | null;
|
|
4
|
+
export type DataType = 'string' | 'integer' | 'number' | 'boolean' | 'uuid' | 'date' | 'datetime' | 'timestamp' | 'json' | 'enum' | 'array' | 'object' | 'binary';
|
|
5
|
+
export interface FieldSpec {
|
|
6
|
+
name: string;
|
|
7
|
+
type: DataType | string;
|
|
8
|
+
required?: boolean;
|
|
9
|
+
unique?: boolean;
|
|
10
|
+
nullable?: boolean;
|
|
11
|
+
default?: DataValue;
|
|
12
|
+
min?: number;
|
|
13
|
+
max?: number;
|
|
14
|
+
minLength?: number;
|
|
15
|
+
maxLength?: number;
|
|
16
|
+
pattern?: string;
|
|
17
|
+
enum?: DataValue[];
|
|
18
|
+
ref?: string;
|
|
19
|
+
items?: FieldSpec;
|
|
20
|
+
monotonic?: 'inc' | 'dec';
|
|
21
|
+
}
|
|
22
|
+
export interface EntitySpec {
|
|
23
|
+
name: string;
|
|
24
|
+
fields: FieldSpec[];
|
|
25
|
+
primaryKey?: string[];
|
|
26
|
+
uniqueKeys?: string[][];
|
|
27
|
+
indexes?: string[][];
|
|
28
|
+
}
|
|
29
|
+
export interface RelationshipSpec {
|
|
30
|
+
name?: string;
|
|
31
|
+
fromEntity: string;
|
|
32
|
+
fromField: string;
|
|
33
|
+
toEntity: string;
|
|
34
|
+
toField?: string;
|
|
35
|
+
onDelete?: 'cascade' | 'restrict' | 'set-null' | 'no-action';
|
|
36
|
+
}
|
|
37
|
+
export type DataInvariantSpec = {
|
|
38
|
+
id: string;
|
|
39
|
+
description: string;
|
|
40
|
+
kind: 'field-required';
|
|
41
|
+
entity: string;
|
|
42
|
+
field: string;
|
|
43
|
+
} | {
|
|
44
|
+
id: string;
|
|
45
|
+
description: string;
|
|
46
|
+
kind: 'unique';
|
|
47
|
+
entity: string;
|
|
48
|
+
fields: string[];
|
|
49
|
+
} | {
|
|
50
|
+
id: string;
|
|
51
|
+
description: string;
|
|
52
|
+
kind: 'referential-integrity';
|
|
53
|
+
entity: string;
|
|
54
|
+
field: string;
|
|
55
|
+
refEntity: string;
|
|
56
|
+
refField?: string;
|
|
57
|
+
} | {
|
|
58
|
+
id: string;
|
|
59
|
+
description: string;
|
|
60
|
+
kind: 'range';
|
|
61
|
+
entity: string;
|
|
62
|
+
field: string;
|
|
63
|
+
min?: number;
|
|
64
|
+
max?: number;
|
|
65
|
+
} | {
|
|
66
|
+
id: string;
|
|
67
|
+
description: string;
|
|
68
|
+
kind: 'count-equal';
|
|
69
|
+
sourceEntity: string;
|
|
70
|
+
targetEntity: string;
|
|
71
|
+
} | {
|
|
72
|
+
id: string;
|
|
73
|
+
description: string;
|
|
74
|
+
kind: 'field-equal';
|
|
75
|
+
sourceEntity: string;
|
|
76
|
+
sourceField: string;
|
|
77
|
+
targetEntity: string;
|
|
78
|
+
targetField: string;
|
|
79
|
+
} | {
|
|
80
|
+
id: string;
|
|
81
|
+
description: string;
|
|
82
|
+
kind: 'no-orphan';
|
|
83
|
+
entity: string;
|
|
84
|
+
field: string;
|
|
85
|
+
refEntity: string;
|
|
86
|
+
} | {
|
|
87
|
+
id: string;
|
|
88
|
+
description: string;
|
|
89
|
+
kind: 'idempotent-copy';
|
|
90
|
+
sourceEntity: string;
|
|
91
|
+
targetEntity: string;
|
|
92
|
+
} | {
|
|
93
|
+
id: string;
|
|
94
|
+
description: string;
|
|
95
|
+
kind: 'idempotent-migration';
|
|
96
|
+
from: string;
|
|
97
|
+
to: string;
|
|
98
|
+
} | {
|
|
99
|
+
id: string;
|
|
100
|
+
description: string;
|
|
101
|
+
kind: 'monotonic';
|
|
102
|
+
entity: string;
|
|
103
|
+
field: string;
|
|
104
|
+
direction: 'inc' | 'dec';
|
|
105
|
+
} | {
|
|
106
|
+
id: string;
|
|
107
|
+
description: string;
|
|
108
|
+
kind: 'sequence';
|
|
109
|
+
steps: string[];
|
|
110
|
+
} | {
|
|
111
|
+
id: string;
|
|
112
|
+
description: string;
|
|
113
|
+
kind: 'leads-to';
|
|
114
|
+
entity: string;
|
|
115
|
+
field: string;
|
|
116
|
+
from: DataValue;
|
|
117
|
+
to: DataValue;
|
|
118
|
+
} | {
|
|
119
|
+
id: string;
|
|
120
|
+
description: string;
|
|
121
|
+
kind: 'atomicity';
|
|
122
|
+
steps: string[];
|
|
123
|
+
};
|
|
124
|
+
export interface DataModelV1 {
|
|
125
|
+
schemaVersion: 1;
|
|
126
|
+
entities: EntitySpec[];
|
|
127
|
+
relationships?: RelationshipSpec[];
|
|
128
|
+
invariants?: DataInvariantSpec[];
|
|
129
|
+
boundaryChecks?: Array<{
|
|
130
|
+
entity: string;
|
|
131
|
+
field: string;
|
|
132
|
+
values?: DataValue[];
|
|
133
|
+
}>;
|
|
134
|
+
}
|
|
135
|
+
export interface CopyPairSpec {
|
|
136
|
+
id: string;
|
|
137
|
+
sourceEntity: string;
|
|
138
|
+
targetEntity: string;
|
|
139
|
+
mapping: Record<string, string>;
|
|
140
|
+
}
|
|
141
|
+
export interface BackupPairSpec {
|
|
142
|
+
id: string;
|
|
143
|
+
sourceEntity: string;
|
|
144
|
+
targetEntity: string;
|
|
145
|
+
mapping: Record<string, string>;
|
|
146
|
+
}
|
|
147
|
+
export interface MigrationMappingSpec {
|
|
148
|
+
id?: string;
|
|
149
|
+
from: string;
|
|
150
|
+
to: string;
|
|
151
|
+
transform?: 'copy' | 'rename' | 'split' | 'merge' | 'drop' | 'manual';
|
|
152
|
+
note?: string;
|
|
153
|
+
}
|
|
154
|
+
export interface DataVerificationOptions {
|
|
155
|
+
beforeModel?: unknown;
|
|
156
|
+
fieldMapping?: Record<string, string>;
|
|
157
|
+
copyPairs?: CopyPairSpec[];
|
|
158
|
+
migrationMappings?: MigrationMappingSpec[];
|
|
159
|
+
backupPairs?: BackupPairSpec[];
|
|
160
|
+
}
|
|
161
|
+
export interface DataComparisonSummary {
|
|
162
|
+
beforeModelHash: string;
|
|
163
|
+
afterModelHash: string;
|
|
164
|
+
fieldMapping: Record<string, string>;
|
|
165
|
+
beforeEntities: number;
|
|
166
|
+
beforeFields: number;
|
|
167
|
+
afterEntities: number;
|
|
168
|
+
afterFields: number;
|
|
169
|
+
addedEntities: string[];
|
|
170
|
+
removedEntities: string[];
|
|
171
|
+
addedFields: string[];
|
|
172
|
+
removedFields: string[];
|
|
173
|
+
addedRelationships: string[];
|
|
174
|
+
removedRelationships: string[];
|
|
175
|
+
}
|
|
176
|
+
export interface DataVerificationReport {
|
|
177
|
+
/** The engine ran and the input was well-formed enough to produce this report. Read `verdict` for the review outcome. */
|
|
178
|
+
ok: boolean;
|
|
179
|
+
ran: boolean;
|
|
180
|
+
verdict: Verdict;
|
|
181
|
+
verdictReason: string;
|
|
182
|
+
/** The versioned report contract this result follows. */
|
|
183
|
+
schema: ReportSchema;
|
|
184
|
+
schemaVersion: 1;
|
|
185
|
+
modelHash: string;
|
|
186
|
+
/** Every hash this report carries, in one place. The data-model hash follows the same normalization as the published spec, but is not covered by it. */
|
|
187
|
+
hashes: {
|
|
188
|
+
modelHash: string;
|
|
189
|
+
};
|
|
190
|
+
summary: {
|
|
191
|
+
entities: number;
|
|
192
|
+
fields: number;
|
|
193
|
+
errors: number;
|
|
194
|
+
warnings: number;
|
|
195
|
+
checksRun: number;
|
|
196
|
+
truncated?: boolean;
|
|
197
|
+
};
|
|
198
|
+
checks: CheckResult[];
|
|
199
|
+
comparison?: DataComparisonSummary;
|
|
200
|
+
/** What to do next, derived from the findings — never empty. */
|
|
201
|
+
nextSteps: string[];
|
|
202
|
+
}
|
|
203
|
+
export declare function dataModelHash(model: DataModelV1): string;
|
|
204
|
+
export declare function validateDataModel(input: unknown): {
|
|
205
|
+
ok: true;
|
|
206
|
+
model: DataModelV1;
|
|
207
|
+
} | {
|
|
208
|
+
ok: false;
|
|
209
|
+
errors: string[];
|
|
210
|
+
};
|
|
211
|
+
export declare function runDataVerification(input: unknown, options?: DataVerificationOptions): DataVerificationReport;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { DATA_ENGINE_SCHEMA_VERSION } from './data-engine.js';
|
|
2
|
+
export declare const DATAMODEL_VERIFY_TOOL_NAME = "model_datamodel_verify";
|
|
3
|
+
/**
|
|
4
|
+
* Model-visible DSH tool wrapping the bundled data-model verification engine.
|
|
5
|
+
* The model passes a DataModelV1 object; the engine validates it and returns
|
|
6
|
+
* DS/DA/DD checks. Optional beforeModel/fieldMapping/copyPairs/migrationMappings
|
|
7
|
+
* enable before/after data-model regression and migration coverage checks.
|
|
8
|
+
*/
|
|
9
|
+
export declare const modelDataModelVerifyTool: import("@deepseek-ai/dsh-tools").ToolDefinition;
|
|
10
|
+
export { DATA_ENGINE_SCHEMA_VERSION };
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The design-review engine: shape, and the declared design principles.
|
|
3
|
+
*
|
|
4
|
+
* This is not the verification engine. That one decides whether a machine can misbehave;
|
|
5
|
+
* this one decides whether a design keeps the contracts it declares about *itself* - which
|
|
6
|
+
* layers may depend on which, which responsibility owns which dependency, which subtype may
|
|
7
|
+
* refuse which event. The two answer different questions and their verdicts mean different
|
|
8
|
+
* things, which is why they are two engines and two tools.
|
|
9
|
+
*
|
|
10
|
+
* Three levels, kept apart in the report because they are not the same claim:
|
|
11
|
+
*
|
|
12
|
+
* - **violations** - the design contradicts something it declared. These decide `verdict`.
|
|
13
|
+
* - **signals** - a structural fact worth a reader's attention, reported without claiming it
|
|
14
|
+
* is wrong. A hub with high fan-in is a fact about a graph; whether it is a design defect
|
|
15
|
+
* depends on the design, and nothing here knows that.
|
|
16
|
+
* - **facts** - counts and computed structure, for a reader or another tool.
|
|
17
|
+
*
|
|
18
|
+
* What this engine does *not* do is judge a design. It never says a decomposition is good,
|
|
19
|
+
* a responsibility name is meaningful, or an abstraction is the right one. It compares a
|
|
20
|
+
* design against its own declarations and computes features of its graph, and the wording of
|
|
21
|
+
* every finding says which of those it is.
|
|
22
|
+
*
|
|
23
|
+
* @module model-everything-design-engine
|
|
24
|
+
*/
|
|
25
|
+
import { REPORT_SCHEMAS, type Finding, type Verdict } from './engine.js';
|
|
26
|
+
export { REPORT_SCHEMAS };
|
|
27
|
+
/** How a type is drawn and what it promises. */
|
|
28
|
+
export type DesignStereotype = 'class' | 'interface' | 'abstract-class' | 'enum' | 'component' | 'abstract-component';
|
|
29
|
+
export interface DesignOperation {
|
|
30
|
+
name: string;
|
|
31
|
+
/** Parameter type names, positional: `['Money']`. */
|
|
32
|
+
parameters?: string[];
|
|
33
|
+
returns?: string;
|
|
34
|
+
}
|
|
35
|
+
/** A declared state change, used only where behaviour is part of the contract. */
|
|
36
|
+
export interface DesignEvent {
|
|
37
|
+
event: string;
|
|
38
|
+
/** A guard the model states in words; compared as text, never parsed. */
|
|
39
|
+
guard?: string;
|
|
40
|
+
}
|
|
41
|
+
export interface DesignResponsibility {
|
|
42
|
+
name: string;
|
|
43
|
+
/** The dependencies this responsibility owns. */
|
|
44
|
+
uses: string[];
|
|
45
|
+
}
|
|
46
|
+
/** A type, with whatever the model chose to say about it. */
|
|
47
|
+
export interface DesignType {
|
|
48
|
+
name: string;
|
|
49
|
+
stereotype: DesignStereotype;
|
|
50
|
+
/** The type this one specialises, when the model declares one. */
|
|
51
|
+
extends?: string;
|
|
52
|
+
implements?: string[];
|
|
53
|
+
/** Operations the type exposes, for signature and interface checks. */
|
|
54
|
+
operations?: DesignOperation[];
|
|
55
|
+
/** Events the type handles. Its presence is what makes behaviour checkable. */
|
|
56
|
+
transitions?: DesignEvent[];
|
|
57
|
+
/** Events the type refuses. A refusal its supertype does not also declare is a violation. */
|
|
58
|
+
forbids?: DesignEvent[];
|
|
59
|
+
/** Declared responsibilities, each owning a subset of the dependencies. */
|
|
60
|
+
responsibilities?: DesignResponsibility[];
|
|
61
|
+
/** Types this one depends on, by name. */
|
|
62
|
+
dependsOn?: string[];
|
|
63
|
+
/** What this type calls on another, operation by operation: the interface-segregation input. */
|
|
64
|
+
uses?: Array<{
|
|
65
|
+
type: string;
|
|
66
|
+
operations: string[];
|
|
67
|
+
}>;
|
|
68
|
+
/** The layer the model places this type in, when it places it. */
|
|
69
|
+
layer?: string;
|
|
70
|
+
}
|
|
71
|
+
/** A declared layer: an ordered band of the design. */
|
|
72
|
+
export interface DesignLayer {
|
|
73
|
+
name: string;
|
|
74
|
+
members: string[];
|
|
75
|
+
}
|
|
76
|
+
/** A declared boundary of responsibility; not ordered, unlike a layer. */
|
|
77
|
+
export interface DesignBoundary {
|
|
78
|
+
name: string;
|
|
79
|
+
members: string[];
|
|
80
|
+
}
|
|
81
|
+
/** One rule about which edges may exist. Deny beats allow. */
|
|
82
|
+
export interface DesignEdgeRule {
|
|
83
|
+
id: string;
|
|
84
|
+
source: string;
|
|
85
|
+
allow?: string[];
|
|
86
|
+
deny?: string[];
|
|
87
|
+
}
|
|
88
|
+
/** A dependency the design says must exist. */
|
|
89
|
+
export interface DesignRequirement {
|
|
90
|
+
id: string;
|
|
91
|
+
from: string;
|
|
92
|
+
to: string;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* A design as a model.
|
|
96
|
+
*
|
|
97
|
+
* `architecture` is the dependency graph every shape feature is computed from. `types` is the
|
|
98
|
+
* vocabulary that makes the principle checks possible at all: with no `extends` there is no
|
|
99
|
+
* substitutability to check, with no `uses` no interface can be called fat. An empty
|
|
100
|
+
* vocabulary is not a pass - it is a design that declared nothing for those checks to hold
|
|
101
|
+
* it to, and the report says so.
|
|
102
|
+
*/
|
|
103
|
+
export interface DesignModelV1 {
|
|
104
|
+
schemaVersion: 1;
|
|
105
|
+
/** What this design is, for a reader; carried into the report unchanged. */
|
|
106
|
+
artifact?: string;
|
|
107
|
+
architecture: {
|
|
108
|
+
nodes: string[];
|
|
109
|
+
edges: Array<[string, string]>;
|
|
110
|
+
};
|
|
111
|
+
types?: DesignType[];
|
|
112
|
+
declare?: {
|
|
113
|
+
layers?: DesignLayer[];
|
|
114
|
+
boundaries?: DesignBoundary[];
|
|
115
|
+
allowedEdges?: DesignEdgeRule[];
|
|
116
|
+
requiredEdges?: DesignRequirement[];
|
|
117
|
+
};
|
|
118
|
+
narrative?: {
|
|
119
|
+
types?: Record<string, string>;
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
export interface DesignValidation {
|
|
123
|
+
ok: boolean;
|
|
124
|
+
errors: string[];
|
|
125
|
+
model?: DesignModelV1;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Validate a candidate design.
|
|
129
|
+
*
|
|
130
|
+
* The schema is closed, for the reason the verification engine gives: a mistyped field is not
|
|
131
|
+
* a missing feature, it is a different design, and reviewing the wrong one silently is worse
|
|
132
|
+
* than refusing.
|
|
133
|
+
*/
|
|
134
|
+
export declare function validateDesignModel(input: unknown): DesignValidation;
|
|
135
|
+
export interface GraphFeatures {
|
|
136
|
+
nodes: number;
|
|
137
|
+
edges: number;
|
|
138
|
+
components: number;
|
|
139
|
+
/** McCabe's cyclomatic complexity over the dependency graph: E - V + 2P. */
|
|
140
|
+
cyclomatic: number;
|
|
141
|
+
density: number;
|
|
142
|
+
/** The largest strongly connected component, and its members when it has more than one. */
|
|
143
|
+
largestCycle: string[];
|
|
144
|
+
/** One canonical peeling: remove these and the graph is acyclic. */
|
|
145
|
+
/** The edges whose removal makes the graph acyclic, and whether that count is a proven minimum. */
|
|
146
|
+
feedbackArcs: {
|
|
147
|
+
edges: Array<[string, string]>;
|
|
148
|
+
exact: boolean;
|
|
149
|
+
note: string;
|
|
150
|
+
};
|
|
151
|
+
/** Removing one of these disconnects the graph. */
|
|
152
|
+
articulationPoints: string[];
|
|
153
|
+
/** Removing one of these disconnects the graph. */
|
|
154
|
+
bridges: Array<[string, string]>;
|
|
155
|
+
/** Longest-path rank over the condensation; one band per layer the design actually has. */
|
|
156
|
+
ranks: Record<string, number>;
|
|
157
|
+
/** Fan-out per node: how many distinct dependencies it has. */
|
|
158
|
+
fanOut: Record<string, number>;
|
|
159
|
+
/** Fan-in per node: how many distinct dependents it has. */
|
|
160
|
+
fanIn: Record<string, number>;
|
|
161
|
+
/** Pairs that depend on each other. */
|
|
162
|
+
mutualPairs: Array<[string, string]>;
|
|
163
|
+
}
|
|
164
|
+
/** Every shape feature, computed once so every check reads the same numbers. */
|
|
165
|
+
export declare function designFeatures(model: DesignModelV1): GraphFeatures;
|
|
166
|
+
interface CheckResult {
|
|
167
|
+
id: string;
|
|
168
|
+
name: string;
|
|
169
|
+
status: 'pass' | 'fail' | 'skip';
|
|
170
|
+
detail: string;
|
|
171
|
+
findings: Finding[];
|
|
172
|
+
}
|
|
173
|
+
export type DesignPrincipleLevel = 'violation' | 'signal' | 'fact';
|
|
174
|
+
/** Which level each code belongs to, so a reader never has to guess. */
|
|
175
|
+
export declare const DESIGN_CODE_LEVEL: Record<string, DesignPrincipleLevel>;
|
|
176
|
+
export interface DesignReport {
|
|
177
|
+
ok: boolean;
|
|
178
|
+
ran: boolean;
|
|
179
|
+
verdict: Verdict;
|
|
180
|
+
verdictReason: string;
|
|
181
|
+
schema: typeof REPORT_SCHEMAS.design;
|
|
182
|
+
artifact?: string;
|
|
183
|
+
/** The check-by-check results, in a fixed order. */
|
|
184
|
+
checks: CheckResult[];
|
|
185
|
+
/** Violations of something the design declared. These decide `verdict`. */
|
|
186
|
+
violations: Finding[];
|
|
187
|
+
/** Structural facts a reader should see, reported without claiming they are wrong. */
|
|
188
|
+
signals: Finding[];
|
|
189
|
+
/** Counts and computed structure. */
|
|
190
|
+
facts: Record<string, unknown>;
|
|
191
|
+
summary: {
|
|
192
|
+
errors: number;
|
|
193
|
+
warnings: number;
|
|
194
|
+
info: number;
|
|
195
|
+
checksRun: number;
|
|
196
|
+
nodes: number;
|
|
197
|
+
edges: number;
|
|
198
|
+
};
|
|
199
|
+
hashes: {
|
|
200
|
+
modelHash: string;
|
|
201
|
+
};
|
|
202
|
+
modelHash: string;
|
|
203
|
+
nextSteps: string[];
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Review a design: its graph, and the contracts it declares about itself.
|
|
207
|
+
*
|
|
208
|
+
* Read `verdict`, never `ok`. A design that declares nothing gets `pass` with an empty
|
|
209
|
+
* vocabulary only for the checks that had something to hold it to - which is why the report
|
|
210
|
+
* carries each check's `status` and `detail`, and why `facts.declared` says how much the
|
|
211
|
+
* model actually declared.
|
|
212
|
+
*/
|
|
213
|
+
export declare function runDesignReview(input: unknown): DesignReport;
|
|
214
|
+
/** A stable hash of the design, for a baseline diff or an archive record. */
|
|
215
|
+
export declare function designHash(model: DesignModelV1): string;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const DESIGN_TOOL_NAME = "model_design_review";
|
|
2
|
+
/**
|
|
3
|
+
* Model-visible DSH tool that reviews a *design*: the shape of its dependency graph, and the
|
|
4
|
+
* contracts the design states about itself.
|
|
5
|
+
*
|
|
6
|
+
* This is not `model_verify`. That one asks whether a machine can misbehave; this one asks
|
|
7
|
+
* whether a design keeps its own declarations - which layer may depend on which, which
|
|
8
|
+
* responsibility owns which dependency, which subtype may refuse which event. Two engines,
|
|
9
|
+
* two questions, two verdicts that mean different things.
|
|
10
|
+
*/
|
|
11
|
+
export declare const designTool: import("@deepseek-ai/dsh-tools").ToolDefinition;
|