Repository navigation
refactor(repo)!: move every Layer out of service ports into driver modules - #261
Closed
systemfsoftware-maker wants to merge 26 commits into
Closed
systemfsoftware-maker wants to merge 26 commits into
systemfsoftware-maker wants to merge 26 commits into
Conversation
Brainstorm and plan for Stream A L1 (ports split from layers, ADR-0002 superseding ADR-0001's service row, package-topology pack declared) and L2 (core split into packages derived from the import graph). The L1 code PR carries this plan to main.
Supervisor rulings on the doc review: the service-file Layer check becomes a bundled gritlint pack (U20) with violating fixtures, attw joins check:ci in L1 (U21), publint waits for operator approval (U17), the ADR takes the next free number and leaves ADR-0001 line 60 alone, and the L2 cycle claim waits for a real turbo run. A tsc probe showed composite projects already refuse a relative import that escapes the package (TS6059, TS6307), so no rule is added for that.
All four Stream A packs now resolve for CE skills (cell-architecture, boundary-testing, schema-laws, package-topology).
ADR-0002 supersedes ADR-0001's *.service.ts row: every Layer that satisfies a service moves to src/drivers/, following cell-architecture ports-separate-from-layers over the tier-2 static-layer clause.
Every published package already has an attw --pack script and turbo defines the task; check:ci now runs it so a broken published type shape fails the gate.
…ce ports WorkerOptions and WorkerTelemetry declare only their tags; their layers live in src/drivers/ and the Worker namespace exports them as workerOptionsLayer and workerTelemetryLayer. trace-context-rpc, which declared no tag, moves to src/drivers/. ADR-0002.
…yer to a driver CheckerRuntime.service.ts declares only the tag, so importing the port no longer loads typescript/unstable/async. The layer is checkerRuntimeLayer in ./runtime; the tagless CheckerWorker handlers move to src/drivers/. ADR-0002.
… drivers VitestSession.service.ts declares only the tag; the session layer and the tagless VitestRunner and TestRunnerWorker modules move to src/drivers/. ADR-0002.
The fixture-cache, guest-job and stryker-cli-runner ports declare only their tags; their layers and the tagless harness-telemetry layer live in src/Harness/drivers/. ADR-0002.
…ings L1 units already ran against the 1848 plan, so the corrected contract is a new dated plan that declares supersedes and replaces it. - Config: @systemfsoftware/stryker-js/config stays the one access path and owns defineConfig and mergeConfig; the config schema goes to contracts, loading to the engine, and no config package is created (U11). The loader's flag overlay becomes a parameter so the engine never imports the CLI's mergeConfig. - mutation.yml: the L2 PR enrols the new packages itself under a root ruling that overrides the read-only note for this change (U18). - The package-topology checks attw does not cover stay gritlint rules.
…, and checker streams Supersedes the 1955 plan with KTD12. Four streams (F native instrumenter, G execution engine, H tsgo checker, I subsumption and SMT) now work off main. L2 freezes the Checker and TestRunner port shapes in stryker-js-plugin-interface and adds a driver-agnostic Instrumenter port to stryker-js-contracts (U22): selection crosses it as data, while the JS mutators, ignorers, and format registry become construction inputs of the JS driver's Layer. The native addon package is reserved as a driver beside stryker-js-instrumenter; only the CLI depends on a driver.
Thirteen core *.service.ts ports (U6 platform-facing, U7 run-scoped) now declare only their tags; their layers live in src/drivers/. The run-stage composition moves to drivers/run-stage.ts and is exported as Engine.stage and Engine.forStream; RunEventDrainLive becomes RunEvent.drainLayer. Moved bodies are unchanged. ADR-0002.
The TypeScript grammar pinned in gritlint fails to parse two generic call signatures separated only by a newline, and dprint strips the separators that would fix it. A parse failure stops the gritlint scan, so the cell-architecture pack (ADR-0002) could not grade the repo. The intersection type resolves calls in the same order; the body is unchanged.
…cture gritlint pack lint:conventions now fails when a *.service.ts module makes a Layer.* call, declares a layer or *Layer class field, or exports a *Live name (ADR-0002). The systemfsoftware input is locked to the head of systemfsoftware/systemfsoftware#701, which adds the pack; it moves to the merge commit on main before this lands.
fixture-cache.service.ts declares a port and imported the harness telemetry driver for its span helpers, so the port reached a driver (ADR-0002). The span names and helpers move to seam-span.ts; drivers/harness-telemetry.ts keeps only its layer.
portLayer is Layer.succeed with no requirements, so providing fileDrainLayer to it built a drain nobody read; the same memoized fileDrainLayer is already a member of the probe group.
systemfsoftware#701 merged as 6a7c247b3, so gritlint's cell-architecture pack now comes from upstream main instead of the PR branch commit b2c5078a. The pack source is unchanged (same narHash).
Supersedes the 2025 plan. Stream H adds a TypeQuery port as the ./type-query entry of stryker-js-plugin-interface; when L2 moves ports into stryker-js-contracts, that port moves with them.
Review fixes on the L1 head: - plugin-runtime README names Worker.workerOptionsLayer, not the removed Worker.WorkerOptions.layer. - The stage-scope solution note names Engine.stage and temporaryDirectoryLayer, the names that replaced RunEnvironment.stage and TemporaryDirectory.layer. - VitestSession.service.ts gets back the two JSDoc blocks the move dropped, verbatim from main. - ADR-0002 (not yet on main) binds driver imports only in files that declare a tag, which is what holds today: the one *.service.ts that imports a driver, plugin-loader.service.ts, declares no tag and moves in plan U11/U12. Confirmation names the grep that shows it and the check that takes over in L2 (contracts tsc -b, U10/KTD8).
…kage check:ci runs attw through each package's script, and this published package had none, so turbo skipped it. It gets the same script, the arethetypeswrong fork devDependency, and the ESM-only .attw.json the 14 other ESM packages carry byte for byte. attw --pack . exits 0.
main merged ADR-0002 (schema-file operations, #260) while this record waited under the same number. Plan U2 takes the next free number, so it is now ADR-0003. It cites ADR-0001's rows by name, since #260 moved their lines, and names ADR-0002 as the owner of the one clause it supersedes, so "every other clause stands" is true again.
check:ci now runs attw, and CI's check job failed on stryker-js-plugin-interface (NoResolution under node10, CJSResolvesToESM under node16-cjs). turbo stopped there; a local `turbo run attw --continue` showed stryker-js-cli-contract fails the same way. Both are ESM-only and were the two published packages without the .attw.json the other ESM packages carry. They get it byte for byte, and all 51 attw tasks pass.
…lease change Changeset Check counts .attw.json as a move of the package. The file changes no published byte.
… rule systemfsoftware#704 merged as ba6664c18. It extends cell-architecture/service-exports-no-layer to refuse re-exported, aliased, annotated, asserted and default-exported Layers, and its finding message names the next action. Only the root systemfsoftware input moves (6a7c247b3 -> ba6664c18); the two 45f2deb entries from the stryker-published lock stay.
ryanleecode
previously approved these changes
Oct 10, 2026
ryanleecode
left a comment
Contributor
There was a problem hiding this comment.
Verified: CI green on 0bb9c50, diff read, no open threads.
Brings in #262, #263 and #265 (main 4bf5952). One conflict, in packages/stryker-js/src/mutation-reporting.service.ts. #262 added `rememberedMutantIds` to MutationReportingInput and a stampRemembered step to the incremental-report writers. On this branch the service file declares the port only and the implementation lives in drivers/mutation-reporting.ts, so the input field goes on the port and stampRemembered plus its two call sites go into the driver. #262's check-cost-workspace fixture called Engine.RunEnvironment.stage, which this branch renamed to Engine.stage; the fixture now uses that.
This was referenced Oct 10, 2026
Merged
Contributor
|
Closing without merging, superseded: this moves Layers into src/drivers/.ts, a technical-kind folder, which the cell-architecture placement settled in systemfsoftware#708 and #728 rejects. The rule now: a pure implementation stays as static layer on the service class, a driver-backed implementation sits in a flat sibling module beside its service that only composition roots and tests import, and there are no drivers/ folders. It's also 106 files on a day-old base. The rework comes as a fresh PR off main. The branch stream-a/l1-ports-topology is kept. |
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
A consumer can now import any service contract without pulling in its implementation. No
*.service.tsfile that declares aContext.Servicetag builds or exports aLayerany more. Each of those Layers now lives in a driver module (src/drivers/<what-it-binds>.ts) in the owning package, and the e2e harness follows the same pattern. This is layer L1 of the plan indocs/plans/2026-10-09-2237-refactor-ports-split-capability-packages-plan.md. L2 splitspackages/stryker-jsinto one package per capability, and it depends on this change.docs/adr/0003-service-ports-declare-no-layer.md) supersedes ADR-0001's*.service.tsrow and extends itssrc/drivers/row. It was drafted as 0002, but refactor(stryker-js): retire the Checker plans and protocol modules #260 took that number onmainfirst, so it is now 0003 and cites ADR-0001's rows by name rather than by line.gritlint.jsonenables thecell-architecturepack, andflake.lockpins the rootsystemfsoftwareflake input, which builds gritlint, to upstreammainba6664c18(systemfsoftware#701 then #704). The two45f2debentries that arrive through the published stryker-js release's own lock are unchanged.pnpm lint:conventionsincheck:cinow refuses a service module that builds, re-exports, or aliases a Layer.check:ci(pnpm attw, through each package's existingattw --pack .).@systemfsoftware/oxlint-ignorer-configwas the one published package without the script. It now has one, plus the ESM-only.attw.jsonthat the other ESM packages carry. The first CI run on this PR then showed thatstryker-js-plugin-interfaceandstryker-js-cli-contracthad the script but no.attw.json, so they failNoResolutionandCJSResolvesToESM. They get the same file, and a localturbo run attw --continuenow passes all 51 tasks..compound-engineering/config.yamldeclares all four: cell-architecture, boundary-testing, schema-laws, and package-topology.layermembers and the*Livealiases are gone. The replacements areEngine.stage/Engine.forStream(previouslyRunEnvironment.stage/forStream),RunEvent.drainLayer,Worker.workerOptionsLayer/workerTelemetryLayer, and the./runtimeentrycheckerRuntimeLayer(options). Changesets:core-service-ports-declare-no-layer,plugin-runtime-worker-layers, andtypescript-checker-runtime-layerare major;vitest-runner-session-driver,oxlint-ignorer-config-attw, andplugin-interface-attw-configarenone.How an agent consumes this
gritlint check --format jsonemitsfindings[]withrule(cell-architecture/service-exports-no-layer, a stable reason code),path,line, andmessage, plus asummarywith per-rule file counts. The exit code is 1 on a finding and 0 when clean.*.service.tsfile at line 1, so an agent has to search that directory's service modules for the Layer. The message's trailing(directory ...)names the directory, and the message says so.Next: move the Layer to src/drivers/<what-it-binds>.ts as layer, delete its export from the service module, and import the driver at the composition root.Layer.*call, a class field namedlayeror*Layer, a*Liveexport, a re-export oflayer/*Layer/*Live, and an exported binding (const,let,var,default, including annotated,as, andsatisfiesforms) bound to one. A type-only re-export is allowed.export * fromand a Layer under an unrelated name are not reported; the pack README lists these gaps.check:ci.pnpm attwruns turbo'sattwtask, which runsattw --pack .in each published package.@systemfsoftware/arethetypeswrong-clifork prints one JSON line per package:status,packageName,packageVersion,problems[](each withkind,entrypoint, andresolutionKind, wherekindis attw's stable problem code such asNoResolution), andproblemCounts. It then prints a one-line hint naming the--includeflags that add more detail.check:cirecordss=1and keeps running the remaining steps.NoResolutionon an ESM-only package is the.attw.jsonshown in the attw output section below. The envelope carries no schema-version field; that is the fork's format, and this PR does not change it.*.service.tsrow ("Never holds: aLayervalue, a function returning aLayer, a staticlayeror*Layermember, a*Livename; in a file that declares a tag, driver imports") is enforced by the gritlint rule id above, except for the driver-import clause.git grep -nE "from '(\./|\.\./)+drivers/" -- '*.service.ts', whose expected output is onlyplugin-loader.service.ts, a module with no tag that plan U11/U12 rehomes. In L2,tsc -bon the contracts package takes over.check:cigains thepnpm attwstep inside the existing job, so there is no new$GITHUB_STEP_SUMMARYor annotation surface.Review
ce-code-review, full spine, at5d8a97ee4. Reviewers: correctness, adversarial, project-standards, testing, maintainability, api-contract, and learnings, plus a validator.stryker-js-plugin-runtime/README.mdnamed the removedWorker.WorkerOptions.layerWorker.workerOptionsLayer. A repo-wide sweep for removed names found one more,docs/solutions/runtime-errors/stage-scope-closed-with-exit-void-hides-run-outcome.md, which now namesEngine.stageandtemporaryDirectoryLayer.ba6664c18) and re-locked here in0bb9c503e. #704 also refuses the annotated,as,satisfies, andexport defaultforms. With the re-lock, the clean tree still exits 0, so no*.service.tshere re-exports or aliases a Layer. The probes below show each form refused.attwskipped the published@systemfsoftware/oxlint-ignorer-configattwscript, the fork devDependency (catalog, onepnpm-lock.yamlimporter entry), and.attw.json. Output is below.plugin-loader.service.tsplugin-loader.service.tsis named as moving in plan U11/U12, and Confirmation names the check that takes over.VitestSessionInputandVitestSessionShapewas dropped in the movemain.Driver imports in
*.service.tsfiles (the grep for #4/#5):plugin-loader.service.tsdeclares no tag.Evidence
attw on
@systemfsoftware/oxlint-ignorer-configWithout
.attw.json:With
.attw.json, byte-identical to the 14 sibling ESM packages:pnpm lint:conventionsprobesThese runs use gritlint built from the locked input
ba6664c18(/nix/store/wmsl4wda…-gritlint-0.1.0). Each probe file waspackages/stryker-js/src/zz-probe.service.ts, deleted after its run, and the tree was clean afterwards.cell-architecture/service-exports-no-layer: evaluated 39 file(s);clean: 1119 file(s) selectedexport { layer as gitDiffLayer } from './drivers/git-diff.js'packages/stryker-js/src/Sandbox.service.ts:1: cell-architecture/service-exports-no-layer(the directory's first service module); evaluated 40 filesimport { layer } from './drivers/git-diff.js'plusexport const gitDiff = layerexport { layer as GitDiffLive } from './drivers/git-diff.js'evaluated 39 file(s), cleanWith the
6a7c247b3lock that came before, the first two probes exited 0. Thegritlint check --format json .finding for the re-export probe:{"rule":"cell-architecture/service-exports-no-layer","path":"packages/stryker-js/src/Sandbox.service.ts","line":1,"message":"A `*.service.ts` module in this directory builds or exports a `Layer`: a\n`Layer.*` call, a class field named `layer` or `*Layer`, a `*Live` export, a\nre-export of `layer`, `*Layer` or `*Live`, or an export bound to one. The\nfinding names the directory's first service module; search every `*.service.ts`\nin the directory. Next: move the Layer to `src/drivers/<what-it-binds>.ts` as\n`layer`, delete its export from the service module, and import the driver at\nthe composition root. (directory `packages/stryker-js/src`)"}Local verification
Verification was targeted because the host has little memory.
origin/mainmerged up, ADR renumbered):tsc -binpackages/stryker-jspasses.turbo buildfor@systemfsoftware/stryker-jspasses, as doesapi:check.lint:conventionsexits 0.tsc -band oxlint pass for each changed package.attwpasses for each changed package.kill-first-ordering.integration.test.tsfailed to start because a vitest pool worker timed out ([vitest-pool-runner]: Timeout waiting for worker to respond). That is host memory pressure, not an assertion failure. CI runs the file.main.Merge order
This PR shares 8 files with #258 (
stryker/mutant-quality-l1-reasons, also based onmain):packages/stryker-js/etc/stryker-js.api.md,src/Serve/Serve.cell.ts,src/mutation-reporting.service.ts, and five integration tests and fixtures. Whichever PR lands second will needorigin/mainmerged in, and the API report regenerated.