mirror of
https://github.com/nejimakibird/model-weave.git
synced 2026-07-22 06:51:24 +00:00
fix: validate DFD domains with domain sources
This commit is contained in:
parent
4337a543c9
commit
1bf2de9f40
3 changed files with 253 additions and 13 deletions
55
main.js
55
main.js
|
|
@ -13179,7 +13179,7 @@ function validateDiagram(diagram, index, warnings) {
|
|||
if (diagram.schema === "dfd_diagram") {
|
||||
const dfdDiagram = diagram;
|
||||
validateDfdLocalDomains(dfdDiagram, index, warnings);
|
||||
validateDfdObjectDomains(dfdDiagram, warnings);
|
||||
validateDfdObjectDomains(dfdDiagram, index, warnings);
|
||||
const objectEntries = dfdDiagram.objectEntries.length > 0 ? dfdDiagram.objectEntries : dfdDiagram.objectRefs.map((objectRef, rowIndex) => ({
|
||||
ref: objectRef,
|
||||
rowIndex,
|
||||
|
|
@ -13272,16 +13272,16 @@ function validateDiagram(diagram, index, warnings) {
|
|||
}
|
||||
}
|
||||
}
|
||||
function validateDfdObjectDomains(diagram, warnings) {
|
||||
const localDomains = diagram.domains ?? [];
|
||||
const localDomainIds = new Set(localDomains.map((domain) => domain.id));
|
||||
function validateDfdObjectDomains(diagram, index, warnings) {
|
||||
const hasDomainSources = diagram.domainSources.length > 0;
|
||||
const mergedDomainIds = buildDfdMergedDomainIdSet(diagram, index, warnings);
|
||||
for (const entry of diagram.objectEntries) {
|
||||
const domain = entry.domain?.trim();
|
||||
if (!domain) {
|
||||
continue;
|
||||
}
|
||||
const objectId = entry.id?.trim() || entry.ref?.trim() || String(entry.rowIndex + 1);
|
||||
if (localDomainIds.size === 0) {
|
||||
if (mergedDomainIds.size === 0 && !hasDomainSources) {
|
||||
warnings.push({
|
||||
code: "unresolved-reference",
|
||||
message: formatDfdObjectDomainWithoutLocalDomainsMessage(objectId, domain),
|
||||
|
|
@ -13290,10 +13290,10 @@ function validateDfdObjectDomains(diagram, warnings) {
|
|||
field: "Objects.domain",
|
||||
context: { rowIndex: entry.rowIndex + 1 }
|
||||
});
|
||||
} else if (!localDomainIds.has(domain)) {
|
||||
} else if (!mergedDomainIds.has(domain)) {
|
||||
warnings.push({
|
||||
code: "unresolved-reference",
|
||||
message: formatDfdObjectUnknownLocalDomainMessage(objectId, domain),
|
||||
message: hasDomainSources ? formatDfdObjectUnknownDomainMessage(objectId, domain) : formatDfdObjectUnknownLocalDomainMessage(objectId, domain),
|
||||
severity: "warning",
|
||||
path: diagram.path,
|
||||
field: "Objects.domain",
|
||||
|
|
@ -13302,6 +13302,47 @@ function validateDfdObjectDomains(diagram, warnings) {
|
|||
}
|
||||
}
|
||||
}
|
||||
function buildDfdMergedDomainIdSet(diagram, index, warnings) {
|
||||
const domainsById = /* @__PURE__ */ new Map();
|
||||
if (diagram.domainSources.length > 0) {
|
||||
const resolvedSources = resolveDomainSources(
|
||||
diagram.path,
|
||||
diagram.domainSources,
|
||||
index
|
||||
);
|
||||
warnings.push(...resolvedSources.warnings);
|
||||
for (const domain of resolvedSources.domains) {
|
||||
domainsById.set(domain.id, domain);
|
||||
}
|
||||
}
|
||||
for (const localDomain of diagram.domains ?? []) {
|
||||
const externalDomain = domainsById.get(localDomain.id);
|
||||
if (externalDomain) {
|
||||
for (const field of STANDALONE_DOMAIN_CANONICAL_FIELDS) {
|
||||
const localValue = localDomain[field]?.trim() ?? "";
|
||||
const sourceValue = externalDomain[field]?.trim() ?? "";
|
||||
if (!localValue || !sourceValue || localValue === sourceValue) {
|
||||
continue;
|
||||
}
|
||||
warnings.push({
|
||||
code: "invalid-structure",
|
||||
message: formatDfdLocalDomainOverridesSourceMessage(
|
||||
localDomain.id,
|
||||
field,
|
||||
localValue,
|
||||
sourceValue
|
||||
),
|
||||
severity: "warning",
|
||||
path: diagram.path,
|
||||
field: `Domains.${field}`,
|
||||
context: { rowIndex: localDomain.rowIndex + 1 }
|
||||
});
|
||||
}
|
||||
}
|
||||
domainsById.set(localDomain.id, localDomain);
|
||||
}
|
||||
return new Set(domainsById.keys());
|
||||
}
|
||||
function validateDfdLocalDomains(diagram, index, warnings) {
|
||||
const localDomains = diagram.domains ?? [];
|
||||
if (localDomains.length === 0) {
|
||||
|
|
|
|||
|
|
@ -11,13 +11,16 @@ import type {
|
|||
} from "../types/models";
|
||||
import {
|
||||
formatDfdObjectDomainWithoutLocalDomainsMessage,
|
||||
formatDfdObjectUnknownDomainMessage,
|
||||
formatDfdObjectUnknownLocalDomainMessage,
|
||||
formatDfdLocalDomainOverridesSourceMessage,
|
||||
formatDfdLocalDomainFieldMismatchMessage,
|
||||
formatDfdLocalDomainMissingSharedMessage,
|
||||
formatDomainParentUnknownMessage,
|
||||
formatStandaloneDomainDuplicateMessage,
|
||||
formatStandaloneDomainFieldConflictMessage
|
||||
} from "./domain-diagnostics";
|
||||
import { resolveDomainSources } from "./domain-diagram-resolver";
|
||||
import {
|
||||
buildReferenceIdentityKeys,
|
||||
parseReferenceValue,
|
||||
|
|
@ -228,7 +231,7 @@ function validateDiagram(
|
|||
if (diagram.schema === "dfd_diagram") {
|
||||
const dfdDiagram = diagram;
|
||||
validateDfdLocalDomains(dfdDiagram, index, warnings);
|
||||
validateDfdObjectDomains(dfdDiagram, warnings);
|
||||
validateDfdObjectDomains(dfdDiagram, index, warnings);
|
||||
|
||||
const objectEntries: DfdDiagramObjectEntry[] =
|
||||
dfdDiagram.objectEntries.length > 0
|
||||
|
|
@ -351,10 +354,11 @@ function validateDiagram(
|
|||
|
||||
function validateDfdObjectDomains(
|
||||
diagram: DfdDiagramModel,
|
||||
index: ModelingVaultIndex,
|
||||
warnings: ValidationWarning[]
|
||||
): void {
|
||||
const localDomains = diagram.domains ?? [];
|
||||
const localDomainIds = new Set(localDomains.map((domain) => domain.id));
|
||||
const hasDomainSources = diagram.domainSources.length > 0;
|
||||
const mergedDomainIds = buildDfdMergedDomainIdSet(diagram, index, warnings);
|
||||
|
||||
for (const entry of diagram.objectEntries) {
|
||||
const domain = entry.domain?.trim();
|
||||
|
|
@ -363,7 +367,7 @@ function validateDfdObjectDomains(
|
|||
}
|
||||
|
||||
const objectId = entry.id?.trim() || entry.ref?.trim() || String(entry.rowIndex + 1);
|
||||
if (localDomainIds.size === 0) {
|
||||
if (mergedDomainIds.size === 0 && !hasDomainSources) {
|
||||
warnings.push({
|
||||
code: "unresolved-reference",
|
||||
message: formatDfdObjectDomainWithoutLocalDomainsMessage(objectId, domain),
|
||||
|
|
@ -372,10 +376,12 @@ function validateDfdObjectDomains(
|
|||
field: "Objects.domain",
|
||||
context: { rowIndex: entry.rowIndex + 1 }
|
||||
});
|
||||
} else if (!localDomainIds.has(domain)) {
|
||||
} else if (!mergedDomainIds.has(domain)) {
|
||||
warnings.push({
|
||||
code: "unresolved-reference",
|
||||
message: formatDfdObjectUnknownLocalDomainMessage(objectId, domain),
|
||||
message: hasDomainSources
|
||||
? formatDfdObjectUnknownDomainMessage(objectId, domain)
|
||||
: formatDfdObjectUnknownLocalDomainMessage(objectId, domain),
|
||||
severity: "warning",
|
||||
path: diagram.path,
|
||||
field: "Objects.domain",
|
||||
|
|
@ -385,6 +391,58 @@ function validateDfdObjectDomains(
|
|||
}
|
||||
}
|
||||
|
||||
function buildDfdMergedDomainIdSet(
|
||||
diagram: DfdDiagramModel,
|
||||
index: ModelingVaultIndex,
|
||||
warnings: ValidationWarning[]
|
||||
): Set<string> {
|
||||
const domainsById = new Map<string, DomainEntry>();
|
||||
|
||||
if (diagram.domainSources.length > 0) {
|
||||
const resolvedSources = resolveDomainSources(
|
||||
diagram.path,
|
||||
diagram.domainSources,
|
||||
index
|
||||
);
|
||||
warnings.push(...resolvedSources.warnings);
|
||||
|
||||
for (const domain of resolvedSources.domains) {
|
||||
domainsById.set(domain.id, domain);
|
||||
}
|
||||
}
|
||||
|
||||
for (const localDomain of diagram.domains ?? []) {
|
||||
const externalDomain = domainsById.get(localDomain.id);
|
||||
if (externalDomain) {
|
||||
for (const field of STANDALONE_DOMAIN_CANONICAL_FIELDS) {
|
||||
const localValue = localDomain[field]?.trim() ?? "";
|
||||
const sourceValue = externalDomain[field]?.trim() ?? "";
|
||||
if (!localValue || !sourceValue || localValue === sourceValue) {
|
||||
continue;
|
||||
}
|
||||
|
||||
warnings.push({
|
||||
code: "invalid-structure",
|
||||
message: formatDfdLocalDomainOverridesSourceMessage(
|
||||
localDomain.id,
|
||||
field,
|
||||
localValue,
|
||||
sourceValue
|
||||
),
|
||||
severity: "warning",
|
||||
path: diagram.path,
|
||||
field: `Domains.${field}`,
|
||||
context: { rowIndex: localDomain.rowIndex + 1 }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
domainsById.set(localDomain.id, localDomain);
|
||||
}
|
||||
|
||||
return new Set(domainsById.keys());
|
||||
}
|
||||
|
||||
function validateDfdLocalDomains(
|
||||
diagram: DfdDiagramModel,
|
||||
index: ModelingVaultIndex,
|
||||
|
|
|
|||
|
|
@ -482,6 +482,57 @@ function resolveDfdWithFiles(files) {
|
|||
};
|
||||
}
|
||||
|
||||
function buildDfdValidationIndex({
|
||||
domainSources = "",
|
||||
domains = "",
|
||||
objectDomain = "wms",
|
||||
extraFiles = []
|
||||
} = {}) {
|
||||
const domainSourcesSection = domainSources
|
||||
? `## Domain Sources
|
||||
|
||||
| ref | notes |
|
||||
|---|---|
|
||||
${domainSources}
|
||||
|
||||
`
|
||||
: "";
|
||||
const domainsSection = domains
|
||||
? `## Domains
|
||||
|
||||
| id | name | kind | parent | description |
|
||||
|---|---|---|---|---|
|
||||
${domains}
|
||||
|
||||
`
|
||||
: "";
|
||||
|
||||
return buildVaultIndex([
|
||||
...extraFiles,
|
||||
{
|
||||
path: "DFD-SHIPPING.md",
|
||||
content: `${dfdFrontmatter}${domainSourcesSection}${domainsSection}## Objects
|
||||
|
||||
| id | label | kind | ref | domain | notes |
|
||||
|---|---|---|---|---|---|
|
||||
| receive_order | Receive order | process | | ${objectDomain} | |
|
||||
|
||||
## Flows
|
||||
|
||||
| id | from | to | data | notes |
|
||||
|---|---|---|---|---|
|
||||
`
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
function dfdValidationMessages(options) {
|
||||
const index = buildDfdValidationIndex(options);
|
||||
return (index.warningsByFilePath["DFD-SHIPPING.md"] ?? []).map(
|
||||
(warning) => warning.message
|
||||
);
|
||||
}
|
||||
|
||||
test("parses standalone Domains documents", () => {
|
||||
const { file, warnings } = parseDomains(`${baseFrontmatter}
|
||||
## Domains
|
||||
|
|
@ -2903,6 +2954,96 @@ test("localizes DFD object Domain diagnostics", () => {
|
|||
);
|
||||
});
|
||||
|
||||
test("vault validation accepts DFD object domain from Domain Sources", () => {
|
||||
const messages = dfdValidationMessages({
|
||||
domainSources: "| [[DOMAINS-COMPANY]] | Company domains |",
|
||||
extraFiles: [
|
||||
domainsFile("DOMAINS-COMPANY.md", "DOMAINS-COMPANY", [
|
||||
"| logistics | Logistics | department | | Logistics group |",
|
||||
"| wms | WMS | system | logistics | WMS group |"
|
||||
].join("\n"))
|
||||
]
|
||||
});
|
||||
|
||||
assert.equal(
|
||||
messages.includes('DFD object "receive_order" references unknown Domain "wms".'),
|
||||
false
|
||||
);
|
||||
assert.equal(
|
||||
messages.includes('DFD object "receive_order" references Domain "wms", but this DFD has no local Domains.'),
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
test("vault validation keeps unknown DFD object domain warning with Domain Sources", () => {
|
||||
const messages = dfdValidationMessages({
|
||||
domainSources: "| [[DOMAINS-COMPANY]] | Company domains |",
|
||||
objectDomain: "missing",
|
||||
extraFiles: [
|
||||
domainsFile("DOMAINS-COMPANY.md", "DOMAINS-COMPANY", [
|
||||
"| logistics | Logistics | department | | Logistics group |",
|
||||
"| wms | WMS | system | logistics | WMS group |"
|
||||
].join("\n"))
|
||||
]
|
||||
});
|
||||
|
||||
assert.ok(messages.includes('DFD object "receive_order" references unknown Domain "missing".'));
|
||||
assert.equal(
|
||||
messages.includes('DFD object "receive_order" references Domain "missing", but this DFD has no local Domains.'),
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
test("vault validation accepts DFD object domain from local Domains", () => {
|
||||
const messages = dfdValidationMessages({
|
||||
domains: "| wms | WMS | system | | Local WMS |"
|
||||
});
|
||||
|
||||
assert.equal(
|
||||
messages.includes('DFD object "receive_order" references unknown local Domain "wms".'),
|
||||
false
|
||||
);
|
||||
assert.equal(
|
||||
messages.includes('DFD object "receive_order" references Domain "wms", but this DFD has no local Domains.'),
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
test("vault validation keeps unknown local DFD object domain warning", () => {
|
||||
const messages = dfdValidationMessages({
|
||||
domains: "| warehouse | Warehouse | location | | Local warehouse |",
|
||||
objectDomain: "missing"
|
||||
});
|
||||
|
||||
assert.ok(messages.includes('DFD object "receive_order" references unknown local Domain "missing".'));
|
||||
});
|
||||
|
||||
test("vault validation keeps DFD local override warning with Domain Sources", () => {
|
||||
const messages = dfdValidationMessages({
|
||||
domainSources: "| [[DOMAINS-COMPANY]] | Company domains |",
|
||||
domains: "| wms | Local WMS | application | logistics | Local override |",
|
||||
extraFiles: [
|
||||
domainsFile("DOMAINS-COMPANY.md", "DOMAINS-COMPANY", [
|
||||
"| logistics | Logistics | department | | Logistics group |",
|
||||
"| wms | WMS | system | logistics | WMS group |"
|
||||
].join("\n"))
|
||||
]
|
||||
});
|
||||
|
||||
assert.ok(messages.includes('DFD-local Domain "wms" overrides Domain Source name "WMS" with "Local WMS".'));
|
||||
assert.ok(messages.includes('DFD-local Domain "wms" overrides Domain Source kind "system" with "application".'));
|
||||
assert.equal(
|
||||
messages.includes('DFD object "receive_order" references unknown Domain "wms".'),
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
test("vault validation preserves DFD domain compatibility warning without Domains", () => {
|
||||
const messages = dfdValidationMessages();
|
||||
|
||||
assert.ok(messages.includes('DFD object "receive_order" references Domain "wms", but this DFD has no local Domains.'));
|
||||
});
|
||||
|
||||
test("DFD-local Domains reuse in-file Domain diagnostics", () => {
|
||||
const { warnings } = parseDfd(dfdBody([
|
||||
"| warehouse | Warehouse | location | | Warehouse operation |",
|
||||
|
|
|
|||
Loading…
Reference in a new issue