feat: refuse a broken document with the code and pointer both implementations agree on - #114
Merged
Merged
Conversation
…ntations agree on
This was referenced Sep 15, 2026
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.
Closes #85, closes #38 (parent #78). Last of three pull requests rewriting #77, after #112 and #113.
What this branch does
A document that breaks a model constraint is now refused by both implementations with the same code at the same place, against a committed oracle.
<name>.diagnostics.json: the set of(code, path)pairs, where the path is the RFC 6901 JSON Pointer of the object refused. Theexpect:header they used to carry is gone.engine-without-durability,durability-without-engine,env-cannot-reload,illegal-delivery-for-access,non-kv-delivery,duplicate-route-match. They exercise the six codes Declare the Project Intent metamodel once and parse every example with it #38 left pending, which drops the pending list from 56 codes to 50.src/wire/project-intent/rules.ts, the document-level rules, run after the schema and before the mapper.emf/metamodel/model/project-intent.ocl, Complete OCL invariants named by the code they emit, evaluated standalone byemf/cli, with the pointer read off the containment chain. Every failed invariant is reported, never only the first.statefulis gone from chapter 10, chapter 20, the class diagram and the examples: the object kind derives fromlifecycleand volumes.test/simplification-contract.test.tsreads the parsed model, not YAML columns. The indentation helpers that sliced Processes and Applications out of the text are gone.Decisions worth naming
A path names the object, not the field. An OCL invariant takes an object as its context, so
E_CUTOVER_UNHONOURABLEpoints at/applications/0/processes/0rather than at itscutover. The contract's example pointer into a field still holds for a schema refusal, where the production parser knows the field.alert-class-unknowncarries no diagnostics oracle. A value outside a closed vocabulary is refused by each implementation's own front end before any rule runs: the Zod schema can point at the field, the Xtext parser refuses the token and has no object to point at. The code agrees, the place cannot, so the case is not a parity oracle; the README says so, and the TypeScript side still asserts its pointer.The constraints import the metamodel by
nsURI. Importing the.ecoreby path builds a second copy of the package, and invariants then bind to classes the parser never instantiates: validation passed while the document was broken.Enginecarries a literal with no spelling. EMF reads an unset enumeration as its first literal, soengine <> nullwas true for every Process.Engine::absentis what an unset engine reads as; a document cannot write it, and the descriptor leaves it out, because a literal the language cannot write is not part of the vocabulary.Delivery::selfbecameselfDeliveryin the model.selfis an OCL keyword, and the invariant would not parse. The authored spelling is unchanged.observability.scrapeis optional in the shape. The block is whole or absent in the model, which is a rule with its own code, not a schema failure: that is what makesE_ALERT_CLASS_WITHOUT_SIGNALreachable.Verification
./mvnw clean verifyinemf/: 116 tests, line coverage 100.0%, mutation score 100.0% (244 of 244).npm run verify: 466 tests,src/at 100% statements, branches, functions and lines; Stryker 505 of 507 killed, 2 ignored with their reasons. Ratchet raised to statements 98.67, branches 94.3, functions 100, lines 98.58, with README.md matching.(code, path)set matches its oracle in both implementations; the accepted counterpart still parses in both.npm run lint:codes: 59 codes defined, 9 exercised, 50 pending.What #38 leaves behind
The word
statefulstill appears in chapter 20 and in the rendered-tree READMEs as the derived health-timeout class (stateless: 5m,stateful: 10m), which is layer 2 vocabulary and not the authored field this ticket deleted. The class diagram's Process box keeps its old height until the drawing is exported from draw.io again; its rows now match the mermaid the chapter carries.