Skip to content

feat: refuse a broken document with the code and pointer both implementations agree on - #114

Merged
ExtraToast merged 1 commit into
mainfrom
feat/85-refusals-and-ocl
Sep 15, 2026
Merged

ExtraToast merged 1 commit into
mainfrom
feat/85-refusals-and-ocl

Conversation

@ExtraToast

Copy link
Copy Markdown
Contributor

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.

  • Eight refusal fixtures carry a <name>.diagnostics.json: the set of (code, path) pairs, where the path is the RFC 6901 JSON Pointer of the object refused. The expect: header they used to carry is gone.
  • Six fixtures are new, one defect each: 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.
  • TypeScript: src/wire/project-intent/rules.ts, the document-level rules, run after the schema and before the mapper.
  • Java: emf/metamodel/model/project-intent.ocl, Complete OCL invariants named by the code they emit, evaluated standalone by emf/cli, with the pointer read off the containment chain. Every failed invariant is reported, never only the first.
  • stateful is gone from chapter 10, chapter 20, the class diagram and the examples: the object kind derives from lifecycle and volumes.
  • test/simplification-contract.test.ts reads 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_UNHONOURABLE points at /applications/0/processes/0 rather than at its cutover. The contract's example pointer into a field still holds for a schema refusal, where the production parser knows the field.

alert-class-unknown carries 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 .ecore by 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.

Engine carries a literal with no spelling. EMF reads an unset enumeration as its first literal, so engine <> null was true for every Process. Engine::absent is 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::self became selfDelivery in the model. self is an OCL keyword, and the invariant would not parse. The authored spelling is unchanged.

observability.scrape is 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 makes E_ALERT_CLASS_WITHOUT_SIGNAL reachable.

Verification

  • ./mvnw clean verify in emf/: 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.
  • Every refusal fixture's (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 stateful still 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.

@ExtraToast ExtraToast added type: feature New user-facing or operator-facing capability. area: deploy homelab-deploy, deploy-v2, manifests, and rollout flow. component: build Gradle, npm, packaging, or local build tooling. component: tests Unit, integration, system, or contract test concern. labels Sep 15, 2026
@ExtraToast ExtraToast self-assigned this Sep 15, 2026
@ExtraToast
ExtraToast merged commit 06257dc into main Sep 15, 2026
19 checks passed
@ExtraToast
ExtraToast deleted the feat/85-refusals-and-ocl branch September 15, 2026 09:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: deploy homelab-deploy, deploy-v2, manifests, and rollout flow. component: build Gradle, npm, packaging, or local build tooling. component: tests Unit, integration, system, or contract test concern. type: feature New user-facing or operator-facing capability.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refuse documents with OCL, addressed by JSON Pointer Declare the Project Intent metamodel once and parse every example with it

1 participant