@canon-protocol/sdk 8.0.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/dist/canons/Canon.d.ts +2 -0
- package/dist/canons/Canon.js +2 -0
- package/dist/canons/DefinedCanon.d.ts +5 -0
- package/dist/canons/DefinedCanon.js +4 -0
- package/dist/canons/EmbeddedCanon.d.ts +3 -0
- package/dist/canons/EmbeddedCanon.js +3 -0
- package/dist/canons/ReferenceCanon.d.ts +6 -0
- package/dist/canons/ReferenceCanon.js +10 -0
- package/dist/canons/SubjectCanon.d.ts +6 -0
- package/dist/canons/SubjectCanon.js +6 -0
- package/dist/canons/index.d.ts +5 -0
- package/dist/canons/index.js +5 -0
- package/dist/ctl/CtlCanonUri.d.ts +28 -0
- package/dist/ctl/CtlCanonUri.js +230 -0
- package/dist/ctl/CtlGraphResolver.d.ts +14 -0
- package/dist/ctl/CtlGraphResolver.js +411 -0
- package/dist/ctl/CtlMarkdownRenderer.d.ts +17 -0
- package/dist/ctl/CtlMarkdownRenderer.js +262 -0
- package/dist/ctl/CtlParser.d.ts +10 -0
- package/dist/ctl/CtlParser.js +331 -0
- package/dist/ctl/CtlValidator.d.ts +12 -0
- package/dist/ctl/CtlValidator.js +207 -0
- package/dist/ctl/ResourceTypeClassifier.d.ts +21 -0
- package/dist/ctl/ResourceTypeClassifier.js +126 -0
- package/dist/ctl/index.d.ts +12 -0
- package/dist/ctl/index.js +9 -0
- package/dist/filtering/GitIgnoreFilter.d.ts +7 -0
- package/dist/filtering/GitIgnoreFilter.js +32 -0
- package/dist/filtering/IGitIgnoreFilter.d.ts +3 -0
- package/dist/filtering/IGitIgnoreFilter.js +1 -0
- package/dist/filtering/index.d.ts +2 -0
- package/dist/filtering/index.js +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +8 -0
- package/dist/parsing/CanonObjectParser.d.ts +21 -0
- package/dist/parsing/CanonObjectParser.js +242 -0
- package/dist/parsing/CanonParser.d.ts +21 -0
- package/dist/parsing/CanonParser.js +457 -0
- package/dist/parsing/ICanonObjectParser.d.ts +7 -0
- package/dist/parsing/ICanonObjectParser.js +1 -0
- package/dist/parsing/PropertyMetadata.d.ts +7 -0
- package/dist/parsing/PropertyMetadata.js +7 -0
- package/dist/parsing/index.d.ts +6 -0
- package/dist/parsing/index.js +3 -0
- package/dist/repositories/CompositeCanonDocumentRepository.d.ts +28 -0
- package/dist/repositories/CompositeCanonDocumentRepository.js +125 -0
- package/dist/repositories/FileSystemCanonDocumentRepository.d.ts +28 -0
- package/dist/repositories/FileSystemCanonDocumentRepository.js +295 -0
- package/dist/repositories/HttpCanonDocumentRepository.d.ts +24 -0
- package/dist/repositories/HttpCanonDocumentRepository.js +79 -0
- package/dist/repositories/InMemoryCanonDocumentRepository.d.ts +23 -0
- package/dist/repositories/InMemoryCanonDocumentRepository.js +149 -0
- package/dist/repositories/PublisherConfig.d.ts +12 -0
- package/dist/repositories/PublisherConfig.js +62 -0
- package/dist/repositories/PublisherIndex.d.ts +20 -0
- package/dist/repositories/PublisherIndex.js +127 -0
- package/dist/repositories/RepositoryFactory.d.ts +6 -0
- package/dist/repositories/RepositoryFactory.js +13 -0
- package/dist/repositories/index.d.ts +9 -0
- package/dist/repositories/index.js +7 -0
- package/dist/resolution/CanonUri.d.ts +10 -0
- package/dist/resolution/CanonUri.js +55 -0
- package/dist/resolution/CanonUriBuilder.d.ts +12 -0
- package/dist/resolution/CanonUriBuilder.js +51 -0
- package/dist/resolution/ResourceResolutionResult.d.ts +9 -0
- package/dist/resolution/ResourceResolutionResult.js +1 -0
- package/dist/resolution/ResourceResolver.d.ts +27 -0
- package/dist/resolution/ResourceResolver.js +266 -0
- package/dist/resolution/TypeResolver.d.ts +15 -0
- package/dist/resolution/TypeResolver.js +110 -0
- package/dist/resolution/index.d.ts +6 -0
- package/dist/resolution/index.js +4 -0
- package/dist/statements/BooleanStatement.d.ts +3 -0
- package/dist/statements/BooleanStatement.js +3 -0
- package/dist/statements/EmbeddedStatement.d.ts +4 -0
- package/dist/statements/EmbeddedStatement.js +3 -0
- package/dist/statements/IStatement.d.ts +2 -0
- package/dist/statements/IStatement.js +1 -0
- package/dist/statements/ListStatement.d.ts +4 -0
- package/dist/statements/ListStatement.js +3 -0
- package/dist/statements/NumberStatement.d.ts +4 -0
- package/dist/statements/NumberStatement.js +10 -0
- package/dist/statements/ReferenceStatement.d.ts +5 -0
- package/dist/statements/ReferenceStatement.js +10 -0
- package/dist/statements/ScalarStatement.d.ts +3 -0
- package/dist/statements/ScalarStatement.js +3 -0
- package/dist/statements/Statement.d.ts +6 -0
- package/dist/statements/Statement.js +4 -0
- package/dist/statements/StringStatement.d.ts +4 -0
- package/dist/statements/StringStatement.js +10 -0
- package/dist/statements/index.d.ts +9 -0
- package/dist/statements/index.js +8 -0
- package/dist/validation/CanonObjectValidator.d.ts +21 -0
- package/dist/validation/CanonObjectValidator.js +150 -0
- package/dist/validation/ICanonObjectValidator.d.ts +7 -0
- package/dist/validation/ICanonObjectValidator.js +1 -0
- package/dist/validation/OntologyValidationError.d.ts +15 -0
- package/dist/validation/OntologyValidationError.js +25 -0
- package/dist/validation/OntologyValidationResult.d.ts +7 -0
- package/dist/validation/OntologyValidationResult.js +8 -0
- package/dist/validation/ValidationContext.d.ts +6 -0
- package/dist/validation/ValidationContext.js +10 -0
- package/dist/validation/ValidationSeverity.d.ts +4 -0
- package/dist/validation/ValidationSeverity.js +5 -0
- package/dist/validation/index.d.ts +10 -0
- package/dist/validation/index.js +7 -0
- package/dist/validation/rules/document/EmbeddedCanonNoExplicitTypeRule.d.ts +8 -0
- package/dist/validation/rules/document/EmbeddedCanonNoExplicitTypeRule.js +53 -0
- package/dist/validation/rules/document/IDocumentValidationRule.d.ts +6 -0
- package/dist/validation/rules/document/IDocumentValidationRule.js +1 -0
- package/dist/validation/rules/document/NamespacePrefixRule.d.ts +10 -0
- package/dist/validation/rules/document/NamespacePrefixRule.js +51 -0
- package/dist/validation/rules/document/PropertyTypeSpecificityRule.d.ts +11 -0
- package/dist/validation/rules/document/PropertyTypeSpecificityRule.js +89 -0
- package/dist/validation/rules/document/ResourceNamingRule.d.ts +12 -0
- package/dist/validation/rules/document/ResourceNamingRule.js +79 -0
- package/dist/validation/rules/document/SubjectCanonTypeRequiredRule.d.ts +7 -0
- package/dist/validation/rules/document/SubjectCanonTypeRequiredRule.js +33 -0
- package/dist/validation/rules/document/index.d.ts +6 -0
- package/dist/validation/rules/document/index.js +5 -0
- package/dist/validation/rules/normalizeToStringList.d.ts +1 -0
- package/dist/validation/rules/normalizeToStringList.js +9 -0
- package/dist/validation/rules/repository/AmbiguousReferenceRule.d.ts +14 -0
- package/dist/validation/rules/repository/AmbiguousReferenceRule.js +155 -0
- package/dist/validation/rules/repository/ClassDefinitionRule.d.ts +12 -0
- package/dist/validation/rules/repository/ClassDefinitionRule.js +205 -0
- package/dist/validation/rules/repository/ClassHierarchyCycleRule.d.ts +11 -0
- package/dist/validation/rules/repository/ClassHierarchyCycleRule.js +98 -0
- package/dist/validation/rules/repository/DefinitionPropertyReferenceRule.d.ts +13 -0
- package/dist/validation/rules/repository/DefinitionPropertyReferenceRule.js +162 -0
- package/dist/validation/rules/repository/IRepositoryValidationRule.d.ts +7 -0
- package/dist/validation/rules/repository/IRepositoryValidationRule.js +1 -0
- package/dist/validation/rules/repository/ImportExistenceRule.d.ts +12 -0
- package/dist/validation/rules/repository/ImportExistenceRule.js +124 -0
- package/dist/validation/rules/repository/InstancePropertyReferenceRule.d.ts +13 -0
- package/dist/validation/rules/repository/InstancePropertyReferenceRule.js +161 -0
- package/dist/validation/rules/repository/NamespaceImportCycleRule.d.ts +11 -0
- package/dist/validation/rules/repository/NamespaceImportCycleRule.js +85 -0
- package/dist/validation/rules/repository/ObjectPropertyImportRule.d.ts +9 -0
- package/dist/validation/rules/repository/ObjectPropertyImportRule.js +113 -0
- package/dist/validation/rules/repository/ObjectPropertyValueValidationRule.d.ts +12 -0
- package/dist/validation/rules/repository/ObjectPropertyValueValidationRule.js +281 -0
- package/dist/validation/rules/repository/PropertyDomainRule.d.ts +14 -0
- package/dist/validation/rules/repository/PropertyDomainRule.js +221 -0
- package/dist/validation/rules/repository/PropertyHierarchyCycleRule.d.ts +11 -0
- package/dist/validation/rules/repository/PropertyHierarchyCycleRule.js +104 -0
- package/dist/validation/rules/repository/PropertyRangeReferenceRule.d.ts +13 -0
- package/dist/validation/rules/repository/PropertyRangeReferenceRule.js +184 -0
- package/dist/validation/rules/repository/PropertyRangeRequiredRule.d.ts +8 -0
- package/dist/validation/rules/repository/PropertyRangeRequiredRule.js +40 -0
- package/dist/validation/rules/repository/PropertyValueTypeRule.d.ts +11 -0
- package/dist/validation/rules/repository/PropertyValueTypeRule.js +171 -0
- package/dist/validation/rules/repository/SubClassOfReferenceRule.d.ts +11 -0
- package/dist/validation/rules/repository/SubClassOfReferenceRule.js +133 -0
- package/dist/validation/rules/repository/SubPropertyOfReferenceRule.d.ts +11 -0
- package/dist/validation/rules/repository/SubPropertyOfReferenceRule.js +133 -0
- package/dist/validation/rules/repository/TypeAmbiguityRule.d.ts +10 -0
- package/dist/validation/rules/repository/TypeAmbiguityRule.js +104 -0
- package/dist/validation/rules/repository/UnresolvedReferenceRule.d.ts +11 -0
- package/dist/validation/rules/repository/UnresolvedReferenceRule.js +91 -0
- package/dist/validation/rules/repository/XsdImportRule.d.ts +11 -0
- package/dist/validation/rules/repository/XsdImportRule.js +125 -0
- package/dist/validation/rules/repository/index.d.ts +20 -0
- package/dist/validation/rules/repository/index.js +19 -0
- package/package.json +82 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { CanonDocument } from '@canon-protocol/types/document/models/types';
|
|
2
|
+
import { OntologyValidationError } from '../../OntologyValidationError.js';
|
|
3
|
+
import type { IDocumentValidationRule } from './IDocumentValidationRule.js';
|
|
4
|
+
export declare class NamespacePrefixRule implements IDocumentValidationRule {
|
|
5
|
+
private static readonly NAMESPACE_PREFIX_PATTERN;
|
|
6
|
+
readonly ruleName = "NamespacePrefix";
|
|
7
|
+
validate(document: CanonDocument): OntologyValidationError[];
|
|
8
|
+
private validateEntity;
|
|
9
|
+
private getSuggestionForPrefixedValue;
|
|
10
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { OntologyValidationError } from '../../OntologyValidationError.js';
|
|
2
|
+
import { ValidationSeverity } from '../../ValidationSeverity.js';
|
|
3
|
+
export class NamespacePrefixRule {
|
|
4
|
+
static NAMESPACE_PREFIX_PATTERN = /^(rdfs|xsd|owl|rdf|dc|foaf|skos|dct|dcterms|geo|time|prov|schema|og|dbo):/i;
|
|
5
|
+
ruleName = 'NamespacePrefix';
|
|
6
|
+
validate(document) {
|
|
7
|
+
const errors = [];
|
|
8
|
+
for (const [entityName, entityValue] of Object.entries(document.body)) {
|
|
9
|
+
if (entityValue && typeof entityValue === 'object' && !Array.isArray(entityValue)) {
|
|
10
|
+
this.validateEntity(entityName, entityValue, errors);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return errors;
|
|
14
|
+
}
|
|
15
|
+
validateEntity(entityName, entityDict, errors) {
|
|
16
|
+
for (const [propName, propValue] of Object.entries(entityDict)) {
|
|
17
|
+
const propValueStr = propValue?.toString() ?? '';
|
|
18
|
+
if (propValueStr && NamespacePrefixRule.NAMESPACE_PREFIX_PATTERN.test(propValueStr)) {
|
|
19
|
+
const suggestion = this.getSuggestionForPrefixedValue(propValueStr);
|
|
20
|
+
const rootEntity = entityName.split('.')[0];
|
|
21
|
+
const error = new OntologyValidationError();
|
|
22
|
+
error.ruleType = this.ruleName;
|
|
23
|
+
error.severity = ValidationSeverity.Error;
|
|
24
|
+
error.entityName = rootEntity; // Root entity for lookup
|
|
25
|
+
error.propertyName = entityName; // Full nested path for navigation
|
|
26
|
+
error.actualValue = propName; // Property to find at end of path
|
|
27
|
+
error.expectedValue = suggestion;
|
|
28
|
+
error.message = `Invalid namespace prefix '${propValueStr}'. Use '${suggestion}' without prefix.`;
|
|
29
|
+
error.suggestion = 'Canon YAML uses simple type names. Namespace resolution is handled through imports.';
|
|
30
|
+
errors.push(error);
|
|
31
|
+
}
|
|
32
|
+
if (propValue && typeof propValue === 'object' && !Array.isArray(propValue)) {
|
|
33
|
+
this.validateEntity(`${entityName}.${propName}`, propValue, errors);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
getSuggestionForPrefixedValue(prefixedValue) {
|
|
38
|
+
const lowerValue = prefixedValue.toLowerCase();
|
|
39
|
+
if (lowerValue === 'rdfs:class' || lowerValue === 'owl:class') {
|
|
40
|
+
return 'Class';
|
|
41
|
+
}
|
|
42
|
+
if (lowerValue === 'rdfs:property' || lowerValue === 'rdf:property') {
|
|
43
|
+
return 'DatatypeProperty or ObjectProperty';
|
|
44
|
+
}
|
|
45
|
+
if (lowerValue.startsWith('xsd:')) {
|
|
46
|
+
return prefixedValue.substring(4);
|
|
47
|
+
}
|
|
48
|
+
const colonIndex = prefixedValue.indexOf(':');
|
|
49
|
+
return colonIndex >= 0 ? prefixedValue.substring(colonIndex + 1) : prefixedValue;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { CanonDocument } from '@canon-protocol/types/document/models/types';
|
|
2
|
+
import { OntologyValidationError } from '../../OntologyValidationError.js';
|
|
3
|
+
import type { IDocumentValidationRule } from './IDocumentValidationRule.js';
|
|
4
|
+
export declare class PropertyTypeSpecificityRule implements IDocumentValidationRule {
|
|
5
|
+
private static readonly XSD_TYPES;
|
|
6
|
+
readonly ruleName = "PropertyTypeSpecificity";
|
|
7
|
+
validate(document: CanonDocument): OntologyValidationError[];
|
|
8
|
+
private determinePropertyType;
|
|
9
|
+
private getPropertyValue;
|
|
10
|
+
private isPropertyDefinedAsClass;
|
|
11
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { OntologyValidationError } from '../../OntologyValidationError.js';
|
|
2
|
+
import { ValidationSeverity } from '../../ValidationSeverity.js';
|
|
3
|
+
export class PropertyTypeSpecificityRule {
|
|
4
|
+
static XSD_TYPES = new Set([
|
|
5
|
+
'string', 'integer', 'int', 'long', 'short', 'byte',
|
|
6
|
+
'decimal', 'float', 'double', 'boolean', 'bool',
|
|
7
|
+
'datetime', 'date', 'time', 'duration',
|
|
8
|
+
'anyuri', 'anysimpletype',
|
|
9
|
+
'nonnegativeinteger', 'positiveinteger',
|
|
10
|
+
'negativeinteger', 'nonpositiveinteger',
|
|
11
|
+
'unsignedint', 'unsignedlong', 'unsignedshort', 'unsignedbyte',
|
|
12
|
+
'base64binary', 'hexbinary'
|
|
13
|
+
]);
|
|
14
|
+
ruleName = 'PropertyTypeSpecificity';
|
|
15
|
+
validate(document) {
|
|
16
|
+
const errors = [];
|
|
17
|
+
if (document.metadata?.namespace_?.publisher === 'canon-protocol.org' &&
|
|
18
|
+
document.metadata?.namespace_?.package_?.startsWith('core-')) {
|
|
19
|
+
return errors; // Empty list - no validation errors
|
|
20
|
+
}
|
|
21
|
+
const propertyDefinedAsClass = this.isPropertyDefinedAsClass(document);
|
|
22
|
+
for (const [entityName, entityValue] of Object.entries(document.body)) {
|
|
23
|
+
if (entityValue && typeof entityValue === 'object' && !Array.isArray(entityValue)) {
|
|
24
|
+
const entityDict = entityValue;
|
|
25
|
+
const typeValue = entityDict['type'];
|
|
26
|
+
if (typeValue) {
|
|
27
|
+
const invalidTypes = [];
|
|
28
|
+
if (Array.isArray(typeValue)) {
|
|
29
|
+
for (const type of typeValue) {
|
|
30
|
+
const typeStr = type?.toString();
|
|
31
|
+
if (typeStr && typeStr === 'Property' && !propertyDefinedAsClass) {
|
|
32
|
+
invalidTypes.push(typeStr);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
const typeStr = typeValue?.toString();
|
|
38
|
+
if (typeStr && typeStr === 'Property' && !propertyDefinedAsClass) {
|
|
39
|
+
invalidTypes.push(typeStr);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
for (const invalidType of invalidTypes) {
|
|
43
|
+
const rangeValue = this.getPropertyValue(entityDict, 'range');
|
|
44
|
+
const suggestedType = this.determinePropertyType(rangeValue);
|
|
45
|
+
const error = new OntologyValidationError();
|
|
46
|
+
error.ruleType = this.ruleName;
|
|
47
|
+
error.severity = ValidationSeverity.Error;
|
|
48
|
+
error.entityName = entityName;
|
|
49
|
+
error.propertyName = 'type';
|
|
50
|
+
error.actualValue = invalidType;
|
|
51
|
+
error.expectedValue = suggestedType;
|
|
52
|
+
error.message = `Invalid property type '${invalidType}'. Use '${suggestedType}' instead.`;
|
|
53
|
+
error.suggestion = 'Properties should specify their type: DatatypeProperty for data values, ObjectProperty for relationships.';
|
|
54
|
+
errors.push(error);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return errors;
|
|
60
|
+
}
|
|
61
|
+
determinePropertyType(rangeValue) {
|
|
62
|
+
if (!rangeValue || rangeValue.trim().length === 0) {
|
|
63
|
+
return 'DatatypeProperty or ObjectProperty';
|
|
64
|
+
}
|
|
65
|
+
if (PropertyTypeSpecificityRule.XSD_TYPES.has(rangeValue.toLowerCase())) {
|
|
66
|
+
return 'DatatypeProperty';
|
|
67
|
+
}
|
|
68
|
+
if (/^[A-Z]/.test(rangeValue)) {
|
|
69
|
+
return 'ObjectProperty';
|
|
70
|
+
}
|
|
71
|
+
return 'DatatypeProperty or ObjectProperty';
|
|
72
|
+
}
|
|
73
|
+
getPropertyValue(dict, propertyName) {
|
|
74
|
+
const value = dict[propertyName];
|
|
75
|
+
return value ? value.toString() : null;
|
|
76
|
+
}
|
|
77
|
+
isPropertyDefinedAsClass(document) {
|
|
78
|
+
const propertyEntity = document.body['Property'];
|
|
79
|
+
if (propertyEntity && typeof propertyEntity === 'object' && !Array.isArray(propertyEntity)) {
|
|
80
|
+
const entityDict = propertyEntity;
|
|
81
|
+
const typeValue = entityDict['type'];
|
|
82
|
+
if (typeValue) {
|
|
83
|
+
const typeStr = typeValue.toString();
|
|
84
|
+
return typeStr === 'Class' || typeStr.endsWith('.Class');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { CanonDocument } from '@canon-protocol/types/document/models/types';
|
|
2
|
+
import { OntologyValidationError } from '../../OntologyValidationError.js';
|
|
3
|
+
import type { IDocumentValidationRule } from './IDocumentValidationRule.js';
|
|
4
|
+
export declare class ResourceNamingRule implements IDocumentValidationRule {
|
|
5
|
+
private static readonly RESERVED_WORDS;
|
|
6
|
+
private static readonly VALID_PROPERTIES;
|
|
7
|
+
private static readonly VALID_NAME_PATTERN;
|
|
8
|
+
private static readonly VALID_PROPERTY_NAME_PATTERN;
|
|
9
|
+
readonly ruleName = "ResourceNaming";
|
|
10
|
+
validate(document: CanonDocument): OntologyValidationError[];
|
|
11
|
+
private validatePropertyNames;
|
|
12
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { OntologyValidationError } from '../../OntologyValidationError.js';
|
|
2
|
+
import { ValidationSeverity } from '../../ValidationSeverity.js';
|
|
3
|
+
export class ResourceNamingRule {
|
|
4
|
+
static RESERVED_WORDS = new Set([
|
|
5
|
+
'canon',
|
|
6
|
+
'imports',
|
|
7
|
+
'namespace',
|
|
8
|
+
'type',
|
|
9
|
+
'label',
|
|
10
|
+
'comment',
|
|
11
|
+
'subclassof',
|
|
12
|
+
'domain',
|
|
13
|
+
'range',
|
|
14
|
+
'required',
|
|
15
|
+
'functional'
|
|
16
|
+
]);
|
|
17
|
+
static VALID_PROPERTIES = new Set([
|
|
18
|
+
'type', 'label', 'comment', 'subClassOf', 'subPropertyOf', 'domain', 'range',
|
|
19
|
+
'required', 'functional', 'inverseOf', 'transitive', 'symmetric', 'inverseFunctional'
|
|
20
|
+
]);
|
|
21
|
+
static VALID_NAME_PATTERN = /^[a-zA-Z][a-zA-Z0-9_-]*$/;
|
|
22
|
+
static VALID_PROPERTY_NAME_PATTERN = /^[a-zA-Z][a-zA-Z0-9_.]*$/;
|
|
23
|
+
ruleName = 'ResourceNaming';
|
|
24
|
+
validate(document) {
|
|
25
|
+
const errors = [];
|
|
26
|
+
for (const [entityName, entityValue] of Object.entries(document.body)) {
|
|
27
|
+
if (ResourceNamingRule.RESERVED_WORDS.has(entityName.toLowerCase())) {
|
|
28
|
+
const error = new OntologyValidationError();
|
|
29
|
+
error.ruleType = this.ruleName;
|
|
30
|
+
error.severity = ValidationSeverity.Error;
|
|
31
|
+
error.entityName = entityName;
|
|
32
|
+
error.message = `Entity name '${entityName}' is a reserved word and cannot be used.`;
|
|
33
|
+
error.suggestion = "Choose a different name that doesn't conflict with Canon reserved words.";
|
|
34
|
+
errors.push(error);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (!ResourceNamingRule.VALID_NAME_PATTERN.test(entityName)) {
|
|
38
|
+
let suggestion;
|
|
39
|
+
if (/^\d/.test(entityName)) {
|
|
40
|
+
suggestion = 'Entity names must start with a letter, not a number.';
|
|
41
|
+
}
|
|
42
|
+
else if (entityName.includes(' ')) {
|
|
43
|
+
suggestion = 'Entity names cannot contain spaces. Use camelCase or underscores instead.';
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
suggestion = 'Entity names must start with a letter and contain only letters, numbers, and underscores.';
|
|
47
|
+
}
|
|
48
|
+
const error = new OntologyValidationError();
|
|
49
|
+
error.ruleType = this.ruleName;
|
|
50
|
+
error.severity = ValidationSeverity.Error;
|
|
51
|
+
error.entityName = entityName;
|
|
52
|
+
error.message = `Invalid entity name '${entityName}'. ${suggestion}`;
|
|
53
|
+
error.suggestion = suggestion;
|
|
54
|
+
errors.push(error);
|
|
55
|
+
}
|
|
56
|
+
if (entityValue && typeof entityValue === 'object' && !Array.isArray(entityValue)) {
|
|
57
|
+
this.validatePropertyNames(entityName, entityValue, errors);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return errors;
|
|
61
|
+
}
|
|
62
|
+
validatePropertyNames(entityName, entityDict, errors) {
|
|
63
|
+
for (const propertyName of Object.keys(entityDict)) {
|
|
64
|
+
if (ResourceNamingRule.VALID_PROPERTIES.has(propertyName)) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (!ResourceNamingRule.VALID_PROPERTY_NAME_PATTERN.test(propertyName)) {
|
|
68
|
+
const error = new OntologyValidationError();
|
|
69
|
+
error.ruleType = this.ruleName;
|
|
70
|
+
error.severity = ValidationSeverity.Warning;
|
|
71
|
+
error.entityName = entityName;
|
|
72
|
+
error.propertyName = propertyName;
|
|
73
|
+
error.message = `Property name '${propertyName}' in entity '${entityName}' doesn't follow naming conventions.`;
|
|
74
|
+
error.suggestion = 'Property names should start with a letter and contain only letters, numbers, and underscores.';
|
|
75
|
+
errors.push(error);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { CanonDocument } from '@canon-protocol/types/document/models/types';
|
|
2
|
+
import type { IDocumentValidationRule } from './IDocumentValidationRule.js';
|
|
3
|
+
import { OntologyValidationError } from '../../OntologyValidationError.js';
|
|
4
|
+
export declare class SubjectCanonTypeRequiredRule implements IDocumentValidationRule {
|
|
5
|
+
get ruleName(): string;
|
|
6
|
+
validate(document: CanonDocument): OntologyValidationError[];
|
|
7
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { OntologyValidationError } from '../../OntologyValidationError.js';
|
|
2
|
+
import { ValidationSeverity } from '../../ValidationSeverity.js';
|
|
3
|
+
export class SubjectCanonTypeRequiredRule {
|
|
4
|
+
get ruleName() {
|
|
5
|
+
return 'SubjectCanonTypeRequired';
|
|
6
|
+
}
|
|
7
|
+
validate(document) {
|
|
8
|
+
const errors = [];
|
|
9
|
+
for (const [entityName, entityDef] of Object.entries(document.body)) {
|
|
10
|
+
if (typeof entityDef !== 'object' || entityDef === null || Array.isArray(entityDef)) {
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
const entity = entityDef;
|
|
14
|
+
const hasType = 'type' in entity;
|
|
15
|
+
const typeValue = entity['type']?.toString().trim();
|
|
16
|
+
if (!hasType || !typeValue) {
|
|
17
|
+
const error = new OntologyValidationError();
|
|
18
|
+
error.ruleType = this.ruleName;
|
|
19
|
+
error.severity = ValidationSeverity.Error;
|
|
20
|
+
error.entityName = entityName;
|
|
21
|
+
error.propertyName = 'type';
|
|
22
|
+
error.message = `Subject Canon '${entityName}' must have a 'type' property.`;
|
|
23
|
+
error.suggestion =
|
|
24
|
+
"Add a type statement like:\n type: Scene\n\n" +
|
|
25
|
+
"Only embedded objects can infer type from their parent property's range. " +
|
|
26
|
+
"Subject Canons (top-level entities) must explicitly declare their type.";
|
|
27
|
+
error.expectedValue = "A valid class name (Scene, Character, Story, etc.)";
|
|
28
|
+
errors.push(error);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return errors;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type { IDocumentValidationRule } from './IDocumentValidationRule.js';
|
|
2
|
+
export { NamespacePrefixRule } from './NamespacePrefixRule.js';
|
|
3
|
+
export { ResourceNamingRule } from './ResourceNamingRule.js';
|
|
4
|
+
export { PropertyTypeSpecificityRule } from './PropertyTypeSpecificityRule.js';
|
|
5
|
+
export { SubjectCanonTypeRequiredRule } from './SubjectCanonTypeRequiredRule.js';
|
|
6
|
+
export { EmbeddedCanonNoExplicitTypeRule } from './EmbeddedCanonNoExplicitTypeRule.js';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { NamespacePrefixRule } from './NamespacePrefixRule.js';
|
|
2
|
+
export { ResourceNamingRule } from './ResourceNamingRule.js';
|
|
3
|
+
export { PropertyTypeSpecificityRule } from './PropertyTypeSpecificityRule.js';
|
|
4
|
+
export { SubjectCanonTypeRequiredRule } from './SubjectCanonTypeRequiredRule.js';
|
|
5
|
+
export { EmbeddedCanonNoExplicitTypeRule } from './EmbeddedCanonNoExplicitTypeRule.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function normalizeToStringList(value: unknown): string[];
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { CanonDocument } from '@canon-protocol/types/document/models/types';
|
|
2
|
+
import type { ICanonDocumentRepository } from '@canon-protocol/types/document/models';
|
|
3
|
+
import type { IRepositoryValidationRule } from './IRepositoryValidationRule.js';
|
|
4
|
+
import { OntologyValidationError } from '../../OntologyValidationError.js';
|
|
5
|
+
export declare class AmbiguousReferenceRule implements IRepositoryValidationRule {
|
|
6
|
+
get ruleName(): string;
|
|
7
|
+
validateAsync(document: CanonDocument, repository: ICanonDocumentRepository): Promise<OntologyValidationError[]>;
|
|
8
|
+
private buildEntitySourceMapAsync;
|
|
9
|
+
private collectEntitiesRecursivelyAsync;
|
|
10
|
+
private validateEntityReferences;
|
|
11
|
+
private validatePropertyValue;
|
|
12
|
+
private createAmbiguityError;
|
|
13
|
+
private findAliasForNamespace;
|
|
14
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { OntologyValidationError } from '../../OntologyValidationError.js';
|
|
2
|
+
import { ValidationSeverity } from '../../ValidationSeverity.js';
|
|
3
|
+
export class AmbiguousReferenceRule {
|
|
4
|
+
get ruleName() {
|
|
5
|
+
return 'AmbiguousReference';
|
|
6
|
+
}
|
|
7
|
+
async validateAsync(document, repository) {
|
|
8
|
+
const errors = [];
|
|
9
|
+
const entitySourceMap = await this.buildEntitySourceMapAsync(document, repository);
|
|
10
|
+
for (const [entityName, entityValue] of Object.entries(document.body)) {
|
|
11
|
+
if (typeof entityValue === 'object' && entityValue !== null && !Array.isArray(entityValue)) {
|
|
12
|
+
this.validateEntityReferences(entityName, entityValue, entitySourceMap, document, errors);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return errors;
|
|
16
|
+
}
|
|
17
|
+
async buildEntitySourceMapAsync(document, repository) {
|
|
18
|
+
const entitySources = new Map();
|
|
19
|
+
const visitedNamespaces = new Set();
|
|
20
|
+
for (const entityName of Object.keys(document.body)) {
|
|
21
|
+
const docNamespace = document.metadata.namespace_?.toString() ?? '';
|
|
22
|
+
if (!entitySources.has(entityName)) {
|
|
23
|
+
entitySources.set(entityName, []);
|
|
24
|
+
}
|
|
25
|
+
entitySources.get(entityName).push(`(local:${docNamespace})`);
|
|
26
|
+
}
|
|
27
|
+
if (document.metadata.imports) {
|
|
28
|
+
for (const [publisher, imports] of Object.entries(document.metadata.imports)) {
|
|
29
|
+
for (const import_ of imports) {
|
|
30
|
+
const importedDoc = await repository.getHighestCompatibleVersionAsync(publisher, import_);
|
|
31
|
+
if (importedDoc) {
|
|
32
|
+
await this.collectEntitiesRecursivelyAsync(importedDoc, entitySources, visitedNamespaces, repository);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return entitySources;
|
|
38
|
+
}
|
|
39
|
+
async collectEntitiesRecursivelyAsync(document, entitySources, visitedNamespaces, repository) {
|
|
40
|
+
const docNamespace = document.metadata.namespace_?.toString() ?? '';
|
|
41
|
+
if (visitedNamespaces.has(docNamespace)) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
visitedNamespaces.add(docNamespace);
|
|
45
|
+
for (const entityName of Object.keys(document.body)) {
|
|
46
|
+
if (!entitySources.has(entityName)) {
|
|
47
|
+
entitySources.set(entityName, []);
|
|
48
|
+
}
|
|
49
|
+
const sources = entitySources.get(entityName);
|
|
50
|
+
if (!sources.includes(docNamespace)) {
|
|
51
|
+
sources.push(docNamespace);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (document.metadata.imports) {
|
|
55
|
+
for (const [publisher, imports] of Object.entries(document.metadata.imports)) {
|
|
56
|
+
for (const import_ of imports) {
|
|
57
|
+
const importedDoc = await repository.getHighestCompatibleVersionAsync(publisher, import_);
|
|
58
|
+
if (importedDoc) {
|
|
59
|
+
await this.collectEntitiesRecursivelyAsync(importedDoc, entitySources, visitedNamespaces, repository);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
validateEntityReferences(entityPath, entity, entitySourceMap, document, errors) {
|
|
66
|
+
for (const [propKey, propValue] of Object.entries(entity)) {
|
|
67
|
+
const propertyName = propKey;
|
|
68
|
+
if (propertyName && !propertyName.includes('.')) {
|
|
69
|
+
const sources = entitySourceMap.get(propertyName);
|
|
70
|
+
if (sources && sources.length > 1) {
|
|
71
|
+
const hasLocal = sources.some(s => s.startsWith('(local:'));
|
|
72
|
+
if (!hasLocal) {
|
|
73
|
+
const externalSources = sources.filter(s => !s.startsWith('(local:'));
|
|
74
|
+
if (externalSources.length > 1) {
|
|
75
|
+
errors.push(this.createAmbiguityError(entityPath, propertyName, `Property '${propertyName}'`, externalSources, document, true, propertyName));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
this.validatePropertyValue(entityPath, propertyName, propValue, entitySourceMap, document, errors);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
validatePropertyValue(entityPath, propertyName, value, entitySourceMap, document, errors) {
|
|
84
|
+
if (value == null) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (typeof value === 'string' && !value.includes('.')) {
|
|
88
|
+
const sources = entitySourceMap.get(value);
|
|
89
|
+
if (sources && sources.length > 1) {
|
|
90
|
+
const hasLocal = sources.some(s => s.startsWith('(local:'));
|
|
91
|
+
if (!hasLocal) {
|
|
92
|
+
const externalSources = sources.filter(s => !s.startsWith('(local:'));
|
|
93
|
+
if (externalSources.length > 1) {
|
|
94
|
+
errors.push(this.createAmbiguityError(entityPath, propertyName, `Reference '${value}'`, externalSources, document, false, value));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
else if (Array.isArray(value)) {
|
|
100
|
+
for (let i = 0; i < value.length; i++) {
|
|
101
|
+
const itemPath = `${entityPath}.${propertyName}[${i}]`;
|
|
102
|
+
this.validatePropertyValue(itemPath, null, value[i], entitySourceMap, document, errors);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
else if (typeof value === 'object' && !Array.isArray(value)) {
|
|
106
|
+
const nestedPath = propertyName ? `${entityPath}.${propertyName}` : entityPath;
|
|
107
|
+
this.validateEntityReferences(nestedPath, value, entitySourceMap, document, errors);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
createAmbiguityError(entityPath, propertyName, description, sources, document, _isProperty, ambiguousValue) {
|
|
111
|
+
const suggestions = [];
|
|
112
|
+
for (const source of sources) {
|
|
113
|
+
const matchingAlias = this.findAliasForNamespace(document, source);
|
|
114
|
+
if (matchingAlias) {
|
|
115
|
+
const entityName = propertyName?.split('.').pop() ?? propertyName;
|
|
116
|
+
suggestions.push(`${matchingAlias}.${entityName}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const suggestionText = suggestions.length > 0
|
|
120
|
+
? `Use one of: ${suggestions.map(s => `'${s}'`).join(', ')}`
|
|
121
|
+
: `Add aliases to imports and use qualified names (e.g., 'alias.${propertyName}')`;
|
|
122
|
+
const sourcesText = sources.map(s => ` • ${s}`).join('\n');
|
|
123
|
+
const rootEntity = entityPath.split('.')[0];
|
|
124
|
+
const propertyPath = entityPath.includes('.') ? entityPath : propertyName;
|
|
125
|
+
const error = new OntologyValidationError();
|
|
126
|
+
error.ruleType = this.ruleName;
|
|
127
|
+
error.severity = ValidationSeverity.Error;
|
|
128
|
+
error.entityName = rootEntity; // Root entity for lookup
|
|
129
|
+
if (propertyPath) {
|
|
130
|
+
error.propertyName = propertyPath; // Property name for simple, full path for nested
|
|
131
|
+
}
|
|
132
|
+
const valueToUse = ambiguousValue ?? propertyName;
|
|
133
|
+
if (valueToUse) {
|
|
134
|
+
error.actualValue = valueToUse; // Use actual ambiguous value if provided, otherwise property name
|
|
135
|
+
}
|
|
136
|
+
error.message = `${description} is ambiguous - defined in multiple imported namespaces`;
|
|
137
|
+
error.suggestion = `${suggestionText}\n\nDefined in:\n${sourcesText}`;
|
|
138
|
+
error.expectedValue = 'Unambiguous reference (use namespace alias to disambiguate)';
|
|
139
|
+
return error;
|
|
140
|
+
}
|
|
141
|
+
findAliasForNamespace(document, namespaceString) {
|
|
142
|
+
if (!document.metadata.imports) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
for (const [publisher, imports] of Object.entries(document.metadata.imports)) {
|
|
146
|
+
for (const import_ of imports) {
|
|
147
|
+
const importNamespace = `${publisher}/${import_.packageName}@${import_.version}`;
|
|
148
|
+
if (importNamespace === namespaceString && import_.alias) {
|
|
149
|
+
return import_.alias;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { CanonDocument } from '@canon-protocol/types/document/models/types';
|
|
2
|
+
import type { ICanonDocumentRepository } from '@canon-protocol/types/document/models';
|
|
3
|
+
import type { IRepositoryValidationRule } from './IRepositoryValidationRule.js';
|
|
4
|
+
import { OntologyValidationError } from '../../OntologyValidationError.js';
|
|
5
|
+
export declare class ClassDefinitionRule implements IRepositoryValidationRule {
|
|
6
|
+
private readonly builtInClasses;
|
|
7
|
+
get ruleName(): string;
|
|
8
|
+
validateAsync(document: CanonDocument, repository: ICanonDocumentRepository): Promise<OntologyValidationError[]>;
|
|
9
|
+
private checkNestedTypes;
|
|
10
|
+
private isClassEntity;
|
|
11
|
+
private isDefinitionType;
|
|
12
|
+
}
|