Skip to content

fix(concerto-core): validate metamodel AST bounds and required structures - #1603

Open
Siddharth-Dangi wants to merge 1 commit into
accordproject:mainfrom
Siddharth-Dangi:fix/metamodel-ast-validation
Open

Siddharth-Dangi wants to merge 1 commit into
accordproject:mainfrom
Siddharth-Dangi:fix/metamodel-ast-validation

Conversation

@Siddharth-Dangi

Copy link
Copy Markdown

Summary

Metamodel AST instances created directly (e.g. deserialized from raw JSON models, external integration pipelines, or programmatic AST generation) could bypass structural validation and propagate invalid state down into printers, generators, and serializers:

  • NumberValidator: Normalize bounds using NullUtil.isNull and reject { lower: undefined, upper: undefined } (which previously passed because hasOwnProperty checks evaluated to true) as well as non-numeric bounds. Handle null / undefined validator AST objects gracefully.
  • StringValidator: Require regex pattern to be a non-empty string, handle flags cleanly, and enforce positive integer bounds on minLength / maxLength.
  • CollectionSizeValidator: Safely handle missing/null validator object and enforce non-negative integer bounds on minSize and maxSize.
  • Property: Require ObjectProperty to specify a valid type and type.name during validate(), and enforce that sizeValidator only applies to array or map properties without triggering internal TypeErrors on unresolved types.
  • ClassDeclaration & ModelFile: Validate that properties, declarations, and imports are valid arrays and not null or undefined, raising IllegalModelException rather than propagating raw TypeError crashes.
  • MapKeyType & MapValueType: Validate type presence for object and relationship map keys/values, and align with MetaModelNamespace.
  • BaseModelManager:
    • In fromAst(), validate that ast and ast.models exist and elements are non-null.
    • In validateAst(), clean up temporary metamodel model files within a finally block to prevent namespace pollution on validation failure.

Maintainer Approval & Feedback Requested (@jamieshorten @mttrbrts)

Before merging, I would appreciate maintainer input and approval on the following design decisions:

  1. Fail-fast with IllegalModelException: Does the team agree that inbound AST structures (such as declarations: [null] or non-array collections) should throw IllegalModelException immediately upon model loading, or are there any existing downstream tools that rely on lenient AST handling?
  2. ObjectProperty Type Strictness: We added validation in Property.validate() requiring ObjectProperty to have a defined type (mirroring RelationshipProperty), while keeping constructor parsing lenient for backward compatibility. Does this align with your architectural expectations?
  3. Regex Pattern Constraint: We enforce that StringRegexValidator pattern must be a non-empty string (as empty regex=// is not permitted by Concerto's CTO PEG grammar). Please confirm if this restriction matches your intended model semantics.

Testing

  • Added comprehensive unit tests in astmodelmanager.js, collectionsizevalidator.js, stringvalidator.js, numbervalidator.js, property.js, and mapdeclaration.js.
  • All 1,410 tests in concerto-core and all 8 monorepo workspaces pass with 0 regressions.
  • Coverage metrics in concerto-core: Statements: 99.05%, Branches: 96.01%, Functions: 99.49%, Lines: 99.11%.
  • Linters, license checks (licchk), and JSDoc (doc) pass cleanly.

Signed-off-by: Siddharth-Dangi mr.siddharthdangi@gmail.com

@mira-accord-project

Copy link
Copy Markdown

Thanks for the PR, @Siddharth-Dangi. Validating the metamodel AST (bounds and required structures) before it reaches the introspection classes means malformed input fails with a clear model error instead of a runtime crash, and the tests cover the new checks.

The Concerto maintainers have been asked to review. We aim to give a first review within a few working days, though I can't promise a date. At 690 changed lines, reviewers may ask whether it can be split into smaller pieces, which usually speeds things up.

Drafted by an Accord Project agent, reviewed by a maintainer.

@Siddharth-Dangi

Copy link
Copy Markdown
Author

Thanks @mira-accord-project! Glad to hear the fail-fast validation strategy resonates. Most of the diff (~480 lines) consists of regression tests across the validator classes. If reviewers prefer, I'm very happy to split it into two focused PRs (e.g. 1: Numeric/String/Collection validator bounds; 2: ModelFile & Property AST checks). Looking forward to the review!

…ures

Metamodel AST instances created directly (e.g. deserialized from JSON or
inbound from external tools) could bypass structural validation and propagate
invalid state down into printers, generators, and serializers:
- NumberValidator accepted ASTs with undefined or empty bounds ({ lower: undefined, upper: undefined }) because hasOwnProperty check bypassed null guards, resulting in an unconfigured validator and returning undefined bounds. Additionally, null or undefined ASTs threw uncaught TypeErrors.
- StringValidator accepted regex validators missing a pattern, with pattern: null, or with an empty pattern, which created RegExp(/null/) or /(?:)/ instead of reporting a model error.
- StringValidator length bounds did not use NullUtil.isNull consistently, causing empty length bounds to be treated differently depending on whether properties were null or undefined; furthermore non-integer bounds were not rejected.
- CollectionSizeValidator accepted null/undefined validators without a clean model error and did not reject non-integer bounds.
- ObjectProperty with null or missing type was silently accepted during validate(), leading to downstream crashes (e.g. Printer.toCTO throwing TypeError: Cannot read properties of null (reading 'name')).
- Null or undefined elements in ModelFile declarations or imports threw raw TypeErrors rather than IllegalModelException; non-array imports and declarations now also throw IllegalModelException.
- Null or undefined properties in ClassDeclaration properties threw raw TypeErrors rather than IllegalModelException.
- ObjectMapKeyType, ObjectMapValueType, and RelationshipMapValueType threw raw TypeErrors when type was null or missing instead of IllegalModelException.
- BaseModelManager.fromAst threw raw TypeErrors when ast or ast.models was null or contained null elements.
- BaseModelManager.validateAst leaked the metamodel namespace into modelFiles if deserialization validation threw an exception before cleanup.

Normalize bounds using NullUtil.isNull, ensure length and size bounds are positive integers, fail fast with IllegalModelException when required structures are null, empty, or non-arrays, ensure regex pattern is a non-empty string, and clean up temporary metamodel files in a finally block.

Signed-off-by: Siddharth-Dangi <mr.siddharthdangi@gmail.com>
@Siddharth-Dangi
Siddharth-Dangi force-pushed the fix/metamodel-ast-validation branch from 3548563 to 6747535 Compare November 13, 2026 16:51

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant