release: v7.6.0 — OCL pre/post end-to-end (metamodel + WME) and Full Project templates#531
Merged
Conversation
Bundles three independently-cohesive units that benefit any future OCL
consumer (Lean encoder, SQL CHECK generators, JSON-Schema, runtime
evaluators, IDE tooling). Each unit can be split into its own PR via
interactive rebase if desired.
1. Method.pre / Method.post first-class fields
- besser/BUML/metamodel/structural/structural.py: extend Method with
pre and post list fields, name-uniqueness validated via property
setters, plus add_pre / add_post helpers.
- tests/BUML/metamodel/structural/test_structural.py: 5 new tests.
Constraints attached to operations are now anchored on the Method;
no naming-convention scraping required.
2. Constraint.expression typing tightened, OCLConstraint.ast added
- structural.py: Constraint.expression is now strict str (TypeError
on non-str); the loose Any annotation is gone.
- besser/BUML/metamodel/ocl/ocl.py: OCLConstraint validates its
expression argument is an OCLExpression, pretty-prints it for the
base's source-text expression, and exposes the AST via a new ast
property (with a setter that refreshes the source text).
- normalization/normalize.py:61: clone(constraint.expression) ->
clone(constraint.ast) since the AST now lives on .ast.
- pretty_printer.py:59: same .expression -> .ast migration.
- tests/BUML/metamodel/structural/test_structural.py: 2 new tests
for Constraint TypeError.
- tests/BUML/notations/ocl/test_parse_ocl.py: 5 new tests for the
ast / expression separation.
- tests/BUML/notations/ocl/test_normalization.py,
test_pretty_printer.py, test_wrapping_visitor.py: 9 in-place
migrations of constraint.expression -> constraint.ast.
3. SourceLocation tracking on OCLExpression
- ocl.py: optional line, col, source_text fields with type-validated
setters and a copy_location_from helper for synthetic nodes.
- notations/ocl/visitor.py: BOCLVisitorImpl.visit() override
populates the three fields from each ANTLR ctx's start token and
getText().
- metamodel/ocl/clone.py: clone() preserves location through deep
cloning via the new helper.
- normalization/normalize.py: _rewrite_pass calls
copy_location_from after each rule fires so a rewritten root
inherits its origin location.
- tests/BUML/notations/ocl/test_parse_ocl.py: 4 new tests.
- tests/BUML/notations/ocl/test_normalization.py: 1 new test for
normalization preservation.
Verification: full BESSER non-generator suite at 707 passed / 5 skipped
(up from 695 before this change).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DjangoGenerator.generate() shells out to `django-admin startproject` with no cwd, and several internal paths are derived from os.getcwd(). The pre- refactor backend.py wrapped the call in chdir(temp_dir)/try-finally; the extracted _generate_django helper in generation_router.py dropped that guard, so the project got scaffolded in the FastAPI process's cwd while the harvester walked an empty temp_dir/<project_name>, producing "Django project generation failed: Output directory is empty". Re-introduce the chdir guard, scoped to the worker thread that runs generate(), and restore the original cwd in finally.
deploy_webapp_to_github lets httpx.HTTPStatusError fall through to the
catch-all handler, which buries the actual cause ("name already exists
on this account", expired token, rate limit) under "An internal error
occurred during GitHub deployment." and a 500. The frontend then has
nothing useful to show in the popup.
Add a dedicated httpx.HTTPStatusError branch that pulls the message out
of GitHub's response body — preferring the nested errors[*].message
that names the offending field — and re-raises with a meaningful
status: 401 / 403 pass through, 422 maps to 409 Conflict (so the UI
can offer rename/overwrite), other 4xx pass through, and 5xx becomes
502 Bad Gateway.
OCLConstraint.__init__ and the ast setter wrapped pretty_print() in a broad `except Exception`, falling back to `str(expression)`. The fallback is itself broken (OCLExpression.__str__ returns None) and the broad catch hides real pretty-printer bugs by silently writing "None" into Constraint.expression — a defect that would surface much later as a mysterious "None" in downstream diagnostics. Narrow to `except ImportError` (the only documented reason for the deferred import), use try/else to keep the import scope local, and fall back to repr() — at least visibly typed — only when the import itself fails. Any other exception now propagates so a real bug surfaces at the construction site.
…on copy _rewrite_pass guarded the copy_location_from() call with hasattr(new_node, "copy_location_from"). Every rule.rewrite() returns an OCLExpression (verified across rules/_helpers.py and each rule module), so the hasattr is duck-typing where the real contract is "new_node is an OCLExpression." Replace with isinstance(new_node, OCLExpression). This asserts the actual contract, matches the same check used in clone.py:30, and crashes loudly if a future rule ever returns something unexpected (e.g. None) instead of silently skipping the location copy.
The visitor's source-location population wrapped ctx.getText() in `try: ... except Exception: pass`. ANTLR's ParserRuleContext.getText() concatenates token text from the subtree and does not raise under normal parses; the bare except was defensive against a problem that doesn't exist and would silently hide any real failure. Drop the try/except. If getText() ever raises, the error surfaces at the parse site rather than being swallowed into a None source_text.
Two doc updates triggered by the iteration-1 OCL infrastructure: - ocl_grammar.rst: the evaluation example printed `str(constraint.expression)` from a time when `expression` could be either a string or an AST. Drop the redundant `str()` (it's always `str` now) and add a short note clarifying that `.expression` holds the source text and `.ast` exposes the parsed tree on OCLConstraint. - ocl.rst: the page declares BOCL supports preconditions and postconditions but never showed how they're anchored on a model. Add a section with a worked example using `Method.add_pre` / `add_post` — the new canonical wiring point that replaces the previous naming-convention scraping.
Regression hygiene flagged by a docs audit alongside the iteration-1 infrastructure work. Three independent issues: - examples/ocl.rst: the constraint example `context Book: self.pages > 0` is not valid BOCL — the grammar requires `inv:`, `pre:`, `post:`, or `init:` after the context name. Add `inv:`. - buml_language/model_building/ocl_grammar.rst: the page referenced `tests/ocl/test_ocl_parser.py` for grammar test cases; that file does not exist at that path. Point at the actual location, `tests/BUML/notations/ocl/test_parse_ocl.py`. - api/BUML/metamodel/api_ocl.rst: the autodoc page only exposed `besser.BUML.metamodel.ocl.ocl`. Expand to also cover `clone`, `notations.ocl.api` (parse_ocl), `notations.ocl.error_handling` (BOCLSyntaxError), `notations.ocl.pretty_printer` (pretty_print), and `notations.ocl.normalization.normalize` (normalize) — the rest of the user-facing OCL surface that was unreachable through API docs.
Substantive docs pass for the iteration-1 OCL infrastructure. The overview page (`buml_language/model_types/ocl.rst`) gains a sub-toctree that produces a navigable tree in the sidebar; three new sub-pages under `model_types/ocl/` cover the public surface a tool walking the OCL AST needs: - parsing.rst — `parse_ocl`, the .expression / .ast split, `BOCLSyntaxError`, source-location fields, anchoring constraints on `Method.pre` / `Method.post`. - ast.rst — `OCLExpression` inheritance and the major node types (`OperationCallExpression`, `PropertyCallExpression`, `IfExp`, `LoopExp`, `IteratorExp`, literals); the `[lhs, InfixOperator, rhs]` shape of binary calls; the role of the wrapping visitor. - normalization.rst — `normalize`, `pretty_print`, `clone`, including source-location preservation across rewrites and the caveat that `source_text` after rewrite describes the origin node, not the current shape. `examples/ocl.rst` is rewritten end-to-end into a runnable example: build a domain model, parse pre/post into ASTs, attach via add_pre / add_post, inspect source location, normalize, pretty-print.
Iteration 1 included line / col / source_text fields on every
OCLExpression, populated by BOCLVisitorImpl.visit() and preserved
through clone() and normalize(). On reflection the value was thin for
BESSER specifically: OCL constraints in BESSER are embedded as string
fields inside BUML models (not edited as standalone .ocl files), so
"line N column M" rarely points anywhere a tool can usefully open. The
sub-expression highlighting use case remains real, but is better
addressed when there is a concrete consumer asking for it (so the
shape of the location data — whitespace handling, post-rewrite
semantics — can be designed against a real requirement).
Removes:
* line / col / source_text properties + setters on OCLExpression
* copy_location_from() helper
* visit() override in BOCLVisitorImpl that populated the fields
* location preservation in clone() — clone collapses back to a
single function instead of the wrapper + _clone_inner split
* location preservation in normalize._rewrite_pass
* 4 tests in test_parse_ocl.py (root location, inner-node distinct
locations, setter type validation, copy_location_from helper)
* 1 test in test_normalization.py (location preservation across
implies -> not-or rewrite)
* Source-location sections in docs (parsing.rst, ast.rst,
normalization.rst, examples/ocl.rst)
The other two iteration-1 units stay: Method.pre / Method.post
first-class fields, and the OCLConstraint .expression / .ast
separation.
feat(ocl): Method.pre/post and OCLConstraint AST/source split
…he converter Make the WME class diagram converter route OCL constraints by their JSON ``kind`` field — invariants land on ``domain_model.constraints``; preconditions and postconditions land on ``Method.pre`` / ``Method.post`` via ``targetMethodId``. Builds on the metamodel work landed on development (PR #528): ``Method.pre`` / ``Method.post`` first-class fields, AST-backed ``OCLConstraint``, and ``parse_ocl(...)``. Backend changes: * ``parsers/ocl_parser.py`` - new ``parse_ocl_body(body, kind, name, class_, model, method=None)`` helper that synthesizes the BOCL grammar's required header (``context C inv name:`` for invariants, ``context C::m(p:T) pre|post:`` for pre/post — different shapes mandated by the BOCL grammar) before calling ``parse_ocl``. - rewritten ``process_ocl_constraints`` returns AST-backed ``OCLConstraint`` (was bare ``Constraint``) and skips malformed blocks with a warning instead of producing junk text. Kept on the legacy path for projects whose textareas still contain the full ``context X inv name: expr`` form. * ``json_to_buml/class_diagram_processor.py`` - ``_process_classes`` now returns ``(class_id_to_class, method_id_to_method)`` element-id maps; the maps are stashed on ``domain_model._class_element_ids`` and ``_method_element_ids`` so ``buml_to_json`` can re-emit the original UUIDs (without this, every BUML→JSON cycle silently broke ``targetMethodId`` references). - rewritten ``_process_constraints`` routes by ``kind``: invariants are parsed via ``parse_ocl_body`` and added to ``domain_model.constraints``; pre/post resolve their target method via ``targetMethodId`` and call ``method.add_pre`` / ``add_post``. Auto-generates names when ``constraintName`` is absent. Skip-with-warning on parse failure, missing link, or orphan target method. - the existing legacy textarea path (no ``kind`` field) continues to work. * ``buml_to_json/class_diagram_converter.py`` - reuses stashed element ids when emitting classes and methods. - existing invariant emitter tags ``kind: "invariant"`` plus ``constraintName`` so the next ingest takes the kind-aware path. - new emission pass walks every class's methods' ``pre`` / ``post`` and emits one ``ClassOCLConstraint`` element + ``ClassOCLLink`` per constraint (``kind``, ``targetMethodId``, body-only ``constraint``). * ``validators/ocl_checker.py`` - ``check_ocl_constraint`` now collects invariants AND every method's pre/post (was iterating only ``domain_model.constraints``). Labels include ``[ClassName::method kind name]`` for disambiguation. - ``OCLConstraint`` instances are accepted as valid by virtue of carrying an AST; bare ``Constraint`` still gets the lex/parse round-trip fallback. Tests: 16 new in ``test_ocl_pre_post.py`` covering parse_ocl_body header synthesis (all three kinds + grammar shapes), kind-routing, skip-with-warning on orphan targets / unlinked boxes / invalid OCL, legacy textarea compat, auto-generated naming, full JSON→BUML→JSON round-trip stability, and method-element-id stability across cycles. Full backend suite stays green (1047 passed, 0 regressions). Submodule bump: ``besser/utilities/web_modeling_editor/frontend`` -> ``feature/wme-ocl-pre-post`` (kind dropdown + method picker on the OCL constraint box).
…raints
Replace the body-only intermediate shape introduced one commit ago.
The textbox on the canvas now holds the complete OCL block — header
plus body — exactly as it appears in the BOCL grammar:
context Book inv book_pages_positive: self.pages > 0
context Book::decrease_stock(qty: int) pre: qty > 0
context Book::decrease_stock(qty: int) post: self.stock >= 0
Driver: Jordi's downstream metamodel consumer reads the full OCL source
text to extract the constraint name and detect duplicates. The body-only
shape hid the name inside metadata that his parser doesn't see, so the
canonical wire format goes back to full text.
Backend changes:
* ``parsers/ocl_parser.py``
- Drop ``parse_ocl_body``; replace with ``parse_constraint_text(text,
model)`` that inspects the BOCL header to extract kind / class /
optional method / optional name, resolves the class manually, and
delegates lex/parse to ``parse_ocl(...)`` with an explicit
``context_class`` (the upstream auto-detect regex doesn't handle
the ``Class::method(params)`` segment in pre/post headers, so we
bypass it).
- ``process_ocl_constraints`` now returns a list of
``(kind, OCLConstraint, class_name, method_name)`` routing tuples,
one per ``context X ...`` block in the textarea blob. Malformed
blocks still skip-with-warning.
- Add ``legacy_body_only_to_text(...)`` — a one-direction shim used
only on ingest, to lift JSON files saved during the previous
iteration (body-only ``constraint`` plus ``kind`` /
``targetMethodId`` / ``constraintName`` siblings) to canonical
full text before parsing. Self-deprecates per file: any project
saved through the new emission path drops the legacy fields and
never hits this shim again.
* ``json_to_buml/class_diagram_processor.py``
- Replace the kind/targetMethodId/ClassOCLLink-walk routing in
``_process_constraints`` with a single loop that:
1. coerces each box's textarea to canonical full text via
``_ocl_box_to_full_text`` (fast-path when already canonical;
legacy shim otherwise),
2. calls ``process_ocl_constraints`` to parse + classify,
3. routes invariants into ``domain_model.constraints`` and
pre/post into ``Method.pre`` / ``Method.post`` via a
``(class_name, method_name) -> Method`` index built from the
metamodel (Class.methods names are unique per class).
- Drop the ``_class_element_ids`` / ``_method_element_ids`` stashes
that existed solely to keep ``targetMethodId`` references stable
across save/load. With full-text, references are name-based and
stability is automatic.
* ``buml_to_json/class_diagram_converter.py``
- Drop ``saved_class_ids`` / ``saved_method_ids`` element-id reuse;
fresh UUIDs are fine again.
- Replace the kind/targetMethodId/constraintName fields on emitted
``ClassOCLConstraint`` boxes with a single ``constraint`` field
holding the rebuilt full text.
- Add ``_emit_full_ocl_text(constraint, kind, method=None)`` to
reconstruct the BOCL header from the constraint's context class,
name, and (for pre/post) the owning method's signature.
- Pre/post emission walks every class's methods' ``.pre`` and
``.post`` lists exactly as before — the text shape is what
changed, not the structural layout of emitted elements.
* ``parsers/__init__.py`` — re-exports updated.
Tests (test_ocl_pre_post.py): rewritten end-to-end. Drops the now-gone
``parse_ocl_body_*`` and method-element-id-stability tests; adds tests
for ``parse_constraint_text`` header extraction, multi-block parsing,
unknown-method skip-with-warning, body-only legacy ingest via the shim,
metadata stripping on emit, and full-text round-trip stability.
17 tests, all green; full backend suite (1048 passed) stays green.
Submodule bump: ``besser/utilities/web_modeling_editor/frontend`` ->
``feature/wme-ocl-pre-post`` (popup reverted to plain textarea, badge
removed).
Frontend now renders a small derived «inv»/«pre»/«post» badge on the OCL constraint box so users get a visual cue of the constraint kind without opening the popup. Pure visual; no backend impact.
Frontend ships a new structural-pattern template (``Library_OCL.json``) pre-populated with 11 canonical full-text OCL constraints across invariants, preconditions, and postconditions. Useful as a learning example and as smoke-test fodder.
Ruff F401 was failing CI for two leftovers from an earlier refactor: ``parse_constraint_text`` and ``BOCLSyntaxError`` were imported but never referenced in the module body.
Adds an optional `description` field to the `Constraint` metamodel so end-users see a plain-language explanation when an OCL constraint is violated during model validation, instead of the raw expression. Threaded end-to-end: - Metamodel: new `description` field on Constraint - JSON->BUML: accepts a `description` JSON field on ClassOCLConstraint elements, plus inline OCL `--` comments per constraint (inline wins) - BUML->JSON: emits `description` so editor round-trips preserve it - Validator: surfaces the description as the violation reason in /validate-diagram output (legacy format unchanged when no description) - Code builder: generated Constraint() Python code includes the field Tests: 13 new tests covering all four touch points. Note: this commit was generated by Claude Code on behalf of the user. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Consolidates the OCL constraint pipeline around a single contract: ``constraint.expression`` carries the full canonical OCL text (``context X (inv|pre|post) [name]: body``) from the moment a constraint is parsed, through every storage, transport, validation, and evaluation step. No more body-only-vs-full-text divergence. Eliminates three reconstruction layers and one mutation hack that all existed to paper over OCLConstraint storing pretty-printed body-only on its expression field: - ocl_parser.parse_constraint_text: one-line override ``constraint.expression = text`` after parse_ocl(). - ocl_checker: drops _method_signature / _already_full_text / _canonical_invariant_text / _canonical_method_contract_text and the swap-and-restore around parser.evaluate(). Just uses constraint.expression directly. Adds _ensure_canonical_expression as a narrow legacy fallback for old BUML files (deprecated: removal target 2026-Q4). - buml_to_json: drops _emit_full_ocl_text. Emits constraint.expression verbatim for both class invariants and method contracts. While here: - domain_model_builder now walks each method's pre/post lists and emits ``Method.add_pre(...)`` / ``add_post(...)`` calls. Without this loop, BUML export silently dropped every method contract on round-trip — confirmed against library__full_stack__project (1).py. - json_to_buml.class_diagram_processor warns on duplicate (Class, method_name) pairs so silent overload collisions in pre/post routing are surfaced. - legacy_body_only_to_text gets a deprecation note pointing at 2026-Q4 removal. - Validator skips evaluation of pre/post against an object model (the BOCL evaluator can't bind method parameters without a runtime invocation — would raise NameError silently). Pre/post remain syntax-checked. Tests: 1061 backend tests pass (test_ocl_pre_post.py adjusted to the new ``expression`` contract). Validated against: - Library_OCL.json (7 inv + 3 pre + 1 post): 11/11 ✅ - team_player_ocl.json (2 inv): 2/2 ✅ - Library_Complete.json (1 inv): 1/1 ✅ - Legacy body-only BUML: 7/7 ✅ (via fallback) - Library_OCL → BUML → re-import → validate: 11/11 ✅, pre/post routing preserved (Book.decrease_stock.pre = 2, .post = 1).
Two ``ClassOCLConstraint`` boxes that parse to the same constraint name (whether typed by the user or auto-generated by ``process_ocl_constraints``) used to crash ``_process_constraints``: ``DomainModel.constraints`` rejects duplicate names with a ``ValueError``, and the assignment ``domain_model.constraints = set(...) | extra_invariants`` had no guard around it, so the error escaped the whole conversion. De-dup by name before assigning, keep the first occurrence, and emit a ``Warning: duplicate constraint name 'X' across OCL boxes; keeping the first occurrence`` so users can spot the collision in the validator output. Discovered by a 2000-constraint stress test (5 parallel agents exercising invariants, pre/post, multi-block, evaluator, and adversarial inputs); this was the only case that produced an uncaught exception across ~2023 inputs. Tests: new ``test_duplicate_constraint_name_across_boxes_does_not_crash`` in ``test_ocl_pre_post.py``. Full suite remains green (1062 passed).
The de-dup loop iterated over a set, whose iteration order is not guaranteed across Python versions. Locally Python 3.11 happened to yield insertion order so 'first wins' worked; CI Python 3.11 gave a different order and the second occurrence won, breaking test_duplicate_constraint_name_across_boxes_does_not_crash. Switch extra_invariants from set to list so iteration order matches the source elements.items() order (insertion order on modern Python dicts), making 'first wins' deterministic across environments.
Three independent issues called out by the multi-agent review.
1. Identifier injection via constraint/method names in BUML codegen.
``constraint.name`` and ``method.name`` flowed straight into
Python identifier positions on the LHS of assignments — a name
like ``x=1;import os;os.system('id')#`` (no spaces, no hyphens,
passes ``NamedElement.name``'s setter) would land verbatim in
code that gets ``exec()``'d. Route every identifier through
``safe_var_name`` (existing helper in ``common.py``); add a
``_method_var_name(method)`` shim and apply it at all five
emit sites (was 5 different copies of ``method.name.split('(')[0]``).
2. Pre/post boxes lost their ``description`` on JSON↔BUML round-trip.
``buml_to_json/class_diagram_converter.py`` emitted ``description``
only on the invariant path; method-contract boxes silently dropped
the field. Mirror the invariant emit. Also drops the hard-coded
``"name": "OCL"`` field that was unique to the pre/post path
(invariants don't set it; reviewer flagged the asymmetry).
3. Inline ``--`` comments lost on round-trip. ``_extract_inline_description``
used to strip ``--`` from the canonical text; the BUML→JSON
emitter never re-injected it, so a user's
``context X inv: self.x > 0 -- must be positive`` collapsed to
``context X inv: self.x > 0`` after the first emit. Now the
helper returns ``(stripped, description)`` — the parser still
sees a stripped form (``parse_ocl``'s visitor rejects trailing
comment tokens, even though the lexer skips them) — but
``process_ocl_constraints`` overrides ``constraint.expression``
with the comment-preserving canonical line so emit is bit-stable.
Tests: 1062/1062 pass. ``test_inline_comment_takes_precedence_over_default``
now asserts ``"-- Inline wins" in constraint.expression`` (the new
contract), where it previously asserted the comment was stripped.
* Embed auto-generated invariant names back into expression so the next BUML→JSON→BUML cycle parses out the same name instead of generating a fresh ``counter_blockidx`` suffix. * Tighten the OCL header regex consumer to ignore the optional name segment for pre/post — only invariants carry a name in BOCL. * Walk the inheritance chain when building the ``(class, method) → Method`` index so a precondition written as ``context Sub::base_method() pre: ...`` resolves to a method declared on a parent class instead of warn-skipping. * Narrow the residual ``except Exception`` in OCL element processing to ``(BOCLSyntaxError, ValueError)`` so programmer errors propagate instead of being swallowed. * Sort method_contracts in domain_model_builder so the emitted Python is byte-stable (matches how invariants and other emissions sort). * Drop defensive ``getattr`` / ``hasattr`` guards on metamodel attributes that the metamodel now guarantees (``Class.methods``, ``Method.pre``, ``Method.post``). * Walk method pre/post in DomainModel._validate_constraints so a precondition with a stale or external context is flagged the same way an invariant would be. * Tighten the round-trip stress test to also assert link wiring and description survive each cycle, not just constraint text. Bumps frontend submodule pointer to e906308 which adds the currentColor fallback on the OCL kind badge.
feat(wme): wire OCL invariants, preconditions, and postconditions
…Project templates - Bump setup.cfg 7.5.1 -> 7.6.0 and add v7.6.0 release notes. - Frontend submodule bump (e906308 -> ee5c9b4): also pulls in PR #126 (perspective on project create), PR #127 (Full Project tab with library_full_stack and personalized_gym_agent multi-diagram bundles), fix(workspace) diagram-bridge wire-up on project load, refactor project-import to shared/services, fix(perspectives) "Full Application" -> "Full Web Application", feat(object-diagram) generate objects+links from referenced class, chore(i18n) drop German locale, package version bump 2.5.0. - Backend: PR #528 (Method.pre/post + OCLConstraint AST/source split), PR #529 (WME wires inv/pre/post by BOCL header into Method.pre/post, validator walks every method's pre/post), PR #499 (natural-language description on Constraint), de-dup constraint names with stable insertion order, identifier-injection hardening on BUML codegen via safe_var_name, inheritance-chain method lookup for pre/post on parent classes, single canonical full-text on Constraint.expression, fix(django generator) chdir into temp_dir, fix(github deploy) surface httpx.HTTPStatusError instead of generic 500.
9 tasks
Pre/post examples in ocl.rst, parsing.rst, and examples/ocl.rst all used ``context Account inv: ...`` headers and overrode .name afterwards. That parses (the inner expression is what add_pre/add_post stores) but it's pedagogically wrong — readers copy the inv: header and then trip on the WME's parse_constraint_text, which routes by the kind keyword in the header. Switch every pre/post example to the canonical ``context Class::method(p: Type) pre|post:`` form. Also: - Replace the single misleading invariant example (``context library inv inv1: self.books>0``) with a header-shape reference table covering all four BOCL kinds (inv, pre, post, init) and one canonical example for each. - Add an "Authoring constraints in the Web Modeling Editor" section to ocl.rst — the page used to be entirely Python-API-flavoured even though most users edit OCL through the editor's constraint box. New section names the «inv»/«pre»/«post» badge and the description field. - parsing.rst: note that the auto-detect regex only handles the invariant header shape; pre/post/init must pass context_class explicitly. Same callout in ocl.rst beside the pre/post Python example. - normalization.rst: tighten the termination claim — each rule strictly decreases the lexicographic measure, max_iterations exists to surface a developer bug (a rule that violates the measure) rather than to bound a "guaranteed" termination.
Closes BESSER-PEARL/B-OCL-Interpreter#5. Lets the modeling assistant author OCL constraints from a plain-language description -- e.g. "add a constraint that a Library always has at least one Book" -- by emitting a new add_ocl_constraint modification that the frontend turns into a ClassOCLConstraint element on the canvas, linked to the target class via a ClassOCLLink. - Frontend submodule bump (ee5c9b4 -> 54a0741): new ClassDiagramModifier case 'add_ocl_constraint' that builds the constraint element + link, positions the box to the right of the anchor class, and writes the BOCL block into the constraint field plus the plain-language gloss into description. Action union and ModificationChanges shape extended in modifiers/base.ts. - Modeling-agent (BESSER-PEARL/modeling-agent@c0e47e8 on main, deploy alongside via scripts/deploy.sh agent): ClassModification.action gains 'add_ocl_constraint'; class_diagram_handler system prompt gains an OCL section with 5 NL->BOCL few-shot examples and an explicit gate forbidding unprompted OCL emission. - Release notes: new "Modeling Assistant -- Natural Language to OCL" section in v7.6.0.rst covering the cross-repo wiring and the deploy callout. Pre-flight validation (calling /validate-diagram before writing) is deferred -- malformed BOCL surfaces post-hoc through the validator path landed in PR #529, which is sufficient for first iteration.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This release lands OCL preconditions and postconditions end-to-end, from BUML metamodel through the Web Modeling Editor's class-diagram converter, validator, and canvas.
Method.pre/Method.postare now first-class list fields onMethod; the OCL textbox in the editor speaks the canonical BOCL form (context X (inv|pre|post) [name]: body); and the validator walks every method's pre/post lists alongsidedomain_model.constraints, labelling errors with[Class::method kind name].OCLConstraintexposes the parsedastseparately from the source-textexpression.Alongside, the editor's template library gains a Full Project tab with multi-diagram bundles (Library Full Stack, Personalized Gym Agent), project creation gains modeling-perspective selection, the modeling assistant can now author OCL constraints from natural language, and a number of OCL hardening / codegen safety fixes land.
Headline changes
OCL — Metamodel and WME wiring
Method.pre/Method.postfirst-class fields withadd_pre/add_posthelpers;Constraint.expressiontypedstr;OCLConstraint.astexposes the parsed AST.parse_constraint_text(text, model)inspects the BOCL header to extract kind / class / method / name and routes invariants ontodomain_model.constraints, pre/post ontoMethod.pre/Method.post. Method lookup walks the inheritance chain. Validator walks every method's pre/post lists. Back-compat shim transparently lifts body-only legacy JSON to canonical full text on first ingest. 17 new tests.descriptionfield onConstraint, threaded through metamodel / converter / validator / code builder.Constraint.expression— three reconstruction layers and theparser.evaluate()swap-and-restore are gone.OCL — Editor UX (frontend)
«inv»,«pre» <method-name>,«post» <method-name>— derived from the BOCL header.Method.pre/Method.posthappens entirely backend-side.Modeling Assistant — Natural Language to OCL (closes BESSER-PEARL/B-OCL-Interpreter#5)
add_ocl_constraintmodification when the user explicitly asks for an invariant, precondition, or postcondition ("add a constraint that a Library always has at least one Book","the precondition of Account::deposit is amount > 0", …).ClassDiagramModifierconsumes the action and spawns aClassOCLConstraintbox on the canvas linked to the target class via aClassOCLLink.c0e47e8deployed alongside this release (./scripts/deploy.sh agent)./validate-diagramvalidation is deferred to a follow-up; malformed BOCL surfaces post-hoc through the validator path landed in PR feat(wme): wire OCL invariants, preconditions, and postconditions #529.Hardening and code quality
safe_var_name. Closes a code-execution vector via theexec()'d generated file.descriptionon JSON↔BUML round-trip; auto-generated invariant names embed back intoconstraint.expressionfor stable round-trips.method_contractsemission so generated Python is byte-stable.Web Modeling Editor — Templates and Project Hub
Web Modeling Editor — Backend Fixes
fix(django generator)re-introduces thechdir(temp_dir)guard arounddjango-admin startproject(regression from the router refactor).fix(github deploy)surfaces realhttpx.HTTPStatusErrordetails with appropriate status mapping (401/403 pass-through, 422 → 409, other 4xx pass-through, 5xx → 502).Cross-repo
Frontend submodule pointer moves
e906308 -> 54a0741(covers the v7.6.0 OCL UX + Full Project templates + the newadd_ocl_constraintmodifier). Companion frontend release PR (develop→main) at BESSER-PEARL/BESSER-Web-Modeling-Editor#129.Modeling-agent commit
c0e47e8onmainadds the assistant-side NL→OCL plumbing — deploy alongside via./scripts/deploy.sh agent.Test plan
python -m pytest tests/ --ignore=tests/generators/nn --ignore=tests/utilities/web_modeling_editor/backend/test_spreadsheet_import.pyclean locally.«inv»/«pre»/«post»badges render./validate-diagramreports the unknown-method error and the constraint is dropped while the rest of the diagram still validates.context X (inv|pre|post) name: bodyform (legacy body-only files normalize on first round-trip).Librarywith the«inv»badge and the BOCL text.cd docs && make html— new v7.6.0 page renders, OCL parsing/AST/normalization sub-pages link.