Skip to content

refactor: make emf/ navigable and its parity tier independent - #134

Merged
ExtraToast merged 5 commits into
mainfrom
refactor/127-emf-tiers
Sep 16, 2026
Merged

ExtraToast merged 5 commits into
mainfrom
refactor/127-emf-tiers

Conversation

@ExtraToast

Copy link
Copy Markdown
Contributor

Closes #127, closes #128, closes #129, closes #130, closes #131

Replaces #133, which GitHub closed when its base branch was deleted on #126's merge and then refused to reopen over a force-push. Same branch, same four commits, rebased onto main now that #126 has landed as a05b7ad.

The two records are 0121 and 0122, not 0120 and 0121: root ADR 0120 landed on main in #123 while #126 was open, and test/adr-contract.test.ts refuses a number used in both registers. #126 renumbered them, and a fifth commit here follows the citations.

What this branch does

emf/ read as one flat list of six Eclipse bundles, and nothing in the listing said which of them a course examiner opens. The parity suite was the sharpest case: it reached past the pipeline's own interface into Pipeline, Parsed, Diagnostic and Descriptor, which is why parity/pom.xml declared eclipse-plugin packaging while the architecture said a module needing no p2 bundle stays a plain jar. Four commits close that drift, in the order the two decisions take.

  • The pipeline writes what the contract compares (Write the parsed intent, the diagnostics and the descriptor as files the build leaves behind #128). A run leaves intent.json or diagnostics.json behind for every case under spec/v1/examples/, beside the exit code it ended on, under bundles/cli/target/parity in a tree that mirrors the example tree, so a written file and its oracle are obviously a pair. bundles/metamodel/target/parity holds descriptor.json, from the same reflective walk as before. Neither is committed. The suites were left untouched here, so a wrong output shape failed in this commit rather than after they depended on it.
  • The parity suite crosses that interface (Move the parity suite onto the pipeline's file interface and make it a plain jar #129). It decides every case from those files and the exit code beside them, holds no EMF type, and so resolves nothing from the target platform: jar packaging, with its META-INF/MANIFEST.MF and build.properties gone.
  • The tree splits into two tiers (Split emf/ into a bundle tier and a test tier #130). bundles/ is what Tycho builds and Eclipse imports; tests/ is what Maven alone runs. Every document naming a module by its old path moved with it, and emf/README.md gained the table that answers where an artefact lives and which task grades it.
  • The test tier is Kotlin (Port the test tier to Kotlin, with the gates the estate runs on Kotlin #131), with the idiom the estate's other JVM repositories already run: JUnit 5, AssertJ, ArchUnit, and a test function named as a sentence in backticks.

The decisions

The canonical JSON writer moved down to bundles/metamodel. 0121 said the module would keep CanonicalJson beside Ledgers in its src/main. It cannot: the pipeline now writes what the suite used to serialise, so the writer belongs to the lowest module that writes a canonical file. The record is amended to say so rather than left to drift, which is the thing it was written to stop.

Two gates would have stopped enforcing silently. ArchitectureTest saw the other modules only because the manifest required them. With no dependency left it reads their class directories by path, and since withOptionalLayers(true) excuses an empty layer, EMF-010 and EMF-011 would otherwise have passed on an empty import while proving nothing. EMF-017 is the new row that fails an import holding no module the build wrote. All three now fail on a fixture, as 0104 requires.

The mutation threshold stands at 100. 0122 warned that PIT reports equivalent mutants on Kotlin, and it does: nine survivors, every one a kotlinc-generated Intrinsics null check or a use block's closeFinally. Neither is written here and neither is observable from a test, so pitest is told not to mutate calls to them, which is the treatment bundles/cli already gives the one-off OCL language registration. No threshold was lowered, so no decision was owed.

0114's word "Java" changed, not its rule. A witness is a JUnit test in whichever language its module is written. Three of the six are now Kotlin functions and three are Java methods of a bundle, Ledgers.checkWitnesses reads a .kt function and a .java method alike, and a new fixture proves a Java witness in a bundle is still found.

Gates

The rule ledger holds eighteen rules: EMF-017 for the import guard above, and EMF-018 for the ktlint step Spotless now runs over src/*/kotlin. The Kotlin compiler runs with -Werror, which is EMF-006's rule in the compiler this tier uses. The coverage floor is unchanged.

Verification

  • ./mvnw -B -ntp clean verify from emf/, exit 0: seven modules green, 40 tests in tests/parity, mutation 29 of 29 killed, the coverage floor held at 1.00.
  • npm run verify at the root, exit 0: 725 tests, coverage unchanged.

Not yet proven

The Eclipse import path in emf/README.md follows from Eclipse naming an imported project by its artifactId, not from an import run against the moved tree. No examiner has opened it since the split.

…files the build leaves behind

A run of the pipeline now leaves behind everything the parity contract
compares, as files, in the canonical JSON the oracles are committed in. For
every case under spec/v1/examples/, Outputs writes the parsed intent or the
diagnostics that refused it, beside the exit code the run ended on, under
cli/target/parity in a tree that mirrors the example tree, so a written file
and its oracle are obviously a pair. DescriptorFile writes the source
metamodel's descriptor from the same reflective walk Descriptor already
performs, under metamodel/target/parity. Neither file is committed.

The canonical JSON writer moves down to metamodel/, the lowest module that
writes a canonical file, because the pipeline now writes what the parity
suite used to serialise. The parity suites are otherwise untouched: this is
the expand half of the move recorded in 0120, so a wrong output shape is
caught here rather than after the suites depend on it.

Closes #128.
The suite decides every case from the files a run leaves behind and the exit
code beside them, so it holds no EMF type, calls nothing, and needs nothing
from the target platform. The module becomes a plain jar: its
META-INF/MANIFEST.MF and build.properties are gone, and it declares the test
libraries the parent's bundle profile used to give it. Each case asserts its
file exists before comparing it, so a file the run owes and did not write
fails as a missing file rather than as a skipped case (0120).

Two gates had to survive that change rather than quietly stop enforcing.
ArchitectureTest saw the other modules only because the manifest required
them; with no dependency left it reads their class directories by path
instead. Because withOptionalLayers excuses an empty layer, EMF-010 and
EMF-011 would then pass on an empty import while proving nothing, so EMF-017
fails when a module whose classes the build wrote is missing from the import.
All three now fail on a fixture that breaks them, as 0104 requires, and the
layered rule no longer names the parity layer as a permitted accessor.

Also removes an em-dash the decision record carried, which test/emdash.test.ts
refuses.

Closes #129.
The listing now says which modules a course examiner opens. `bundles/` holds
the five Tycho builds Eclipse imports; `tests/` holds the parity tier Maven
alone runs, which by 0120 holds no EMF type and needs nothing from the target
platform. The directory names stay short, since Eclipse names an imported
project from its `artifactId` regardless (0121).

Every document that names a module by its old path moves with it: the rule
ledger, whose EMF-014 check fails the build the moment a row's named file is
not where the row says, the witness list, this tree's architecture, two
decision records, the refusal examples' README, the root architecture, and the
two root tests that name a parity path in a comment and in a fixture string.
`emf/README.md` gains the table that says which artefact lives where and which
task grades it.

Two things the move would have broken quietly. The module POMs now sit two
directories below the parent, so each declares its `relativePath`, and
`scripts/lint-codes.ts` matched `emf/<module>/src/test/` one segment deep,
which would have stopped counting a Java test as exercising a code for the
whole tree rather than only for the tier that moved.

Closes #130.
…s on it

`tests/parity` is written in the estate's JVM language, with the idiom its
other repositories already use: JUnit 5, AssertJ, ArchUnit, and a test
function named as a sentence in backticks. The bundle tier stays Java, because
Ecore generates Java, Xtext's runtime hooks are Java, and a bundle compiles
through JDT against a target platform holding no Kotlin unit (0121).

The gates move with it. Spotless gains a ktlint step over `src/*/kotlin`, and
EMF-018 is its ledger row. The Kotlin compiler runs with `-Werror`, which is
EMF-006's rule in the compiler this tier uses. The coverage floor holds
unchanged.

The mutation threshold also stands at 100, which needed one thing rather than
a decision to lower it: `kotlinc` emits a null check for every platform type
crossing into Kotlin and a `closeFinally` for every `use`, and removing either
call is an equivalent mutation, not an uncovered one, so pitest is told not to
mutate calls to them. That is the same treatment `bundles/cli` already gives
the one-off OCL language registration.

The word that changes in 0114 is "Java", not the rule: a witness is a JUnit
test in whichever language its module is written, so `Ledgers.checkWitnesses`
now reads a `.kt` function and a `.java` method alike, three of the six
witnesses name Kotlin functions and three name Java methods, and a new fixture
proves a Java witness in a bundle is still found. The decision record, the
witness list, EMF-013's row and the gates sentence say so.

Closes #131.
Root ADR 0120 landed on main in #123 while #126 was open, so the parity seam
became 0121 and the two tiers 0122 in that pull request. This moves the
citations that live here: the slugged links, the `docs/adr/emf/NNNN` form a
pom comment and a javadoc line use, and the number in a link's own text.
@ExtraToast ExtraToast added type: refactor Code structure change without intended behavior change. 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 16, 2026
@ExtraToast ExtraToast self-assigned this Sep 16, 2026
@github-actions

Copy link
Copy Markdown

Shape

bucket files additions deletions
production code 37 +160 -144
tests 34 +768 -402
decision records 3 +24 -9
documentation 5 +103 -56
examples 1 +1 -1
tooling 27 +256 -47

Coverage

Baseline: main @ a05b7ad.

metric this branch main diff
statements 99.17% 99.17% 0.00
branches 96.56% 96.56% 0.00
functions 100.00% 100.00% 0.00
lines 99.10% 99.10% 0.00

This pull request changed no covered line.

@ExtraToast
ExtraToast merged commit ea0888f into main Sep 16, 2026
23 checks passed
@ExtraToast
ExtraToast deleted the refactor/127-emf-tiers branch September 16, 2026 10:47
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: refactor Code structure change without intended behavior change.

Projects

None yet

1 participant