CI Theatre and a NuGet Feed Made of Folders
Four pipelines run dotnet test against projects with no tests and pass every time, the shared model ships as committed .nupkg files in a folder, one executable has no pipeline at all, and a DLL with no source hides an algorithm pasted inline elsewhere.
A green pipeline is a promise: this built, this passed its tests, this is safe to deploy. Four of this estate's pipelines make that promise on every commit, and every one of them is lying about the middle clause. They run dotnet test, the stage goes green, and no test executes — because no test exists, in any repository, anywhere in the estate. This part is about the delivery machinery behind the message-bus estate: the tests that aren't, the private package feed made of a folder, the executable with no pipeline at all, and the compiled DLL whose source vanished — shipping an algorithm that also lives, pasted by hand, somewhere you can read. It is where the whole series' theme — coordination without contracts — reaches the build system.
The test stage that tests nothing
Each function repository's pipeline follows the same three beats: build, test, deploy. The middle beat:
test:
stage: test
script:
- dotnet test Attendance.EmployeeSync.csproj # the app project itself
dotnet test against the application csproj — not a test project, because there is no test project. When dotnet test is pointed at an assembly containing no tests, it does not fail; it finds zero tests, runs zero tests, reports zero failures, and exits successfully. The stage goes green. It has gone green on every commit, in all four repositories, for the life of the estate — a perfect record of a test suite that does not exist.
This is worse than having no test stage at all, and the difference is the whole point. A pipeline with no test stage is honest: it tells everyone, by its absence, that quality is unverified. A pipeline whose test stage always passes is theatre — it manufactures the green badge, the reassuring word “passed”, the checkmark next to the commit, and delivers none of the substance behind them. The badge trains the team to trust it, and the trust is unearned. I would rather inherit a repo that admits it has no tests than one that performs having them, because the second one has to be discovered — and it is usually discovered in production, the first time the green checkmark shipped something broken. The error logger that cannot log shipped past four green pipelines.
The fix is not “write tests” — that is a different, larger project. The fix is stop lying: point the stage at a real test project or delete it, so the pipeline's signal matches reality. An empty honest pipeline is a to-do list; a green empty pipeline is a false alibi.
A package feed made of a folder
The estate's executables share one EF Core model — the DbContext that is the integration contract — plus a Key Vault helper, distributed as NuGet packages. But not from a feed. From a folder of .nupkg files committed into each repository:
SharedDLL/
Attendance.DataModel.1.4.2.nupkg
Attendance.Azure.1.1.0.nupkg
Attendance.MonthlyReportGenerator.1.0.0.nupkg
The pipeline adds the folder as a package source — dotnet nuget add source ./SharedDLL — and restores from it. It is a private feed reimplemented with the filesystem, and for a small team without feed infrastructure it is not an unreasonable starting point. The trouble is what a folder-feed cannot give you that a real feed does for free: a single authoritative copy at each version. Here every repo carries its own committed copy of the model package, so the estate's most important shared contract exists as N binaries in N repositories, at whatever version each repo last vendored. Upgrading the model means rebuilding the package, copying the new .nupkg into every consuming repo, and committing it N times — a manual fan-out with no mechanism to detect when one repo falls behind. The contract that binds five executables to one database is distributed by copy-paste of a binary, which is exactly the version-drift risk you adopt a package manager to escape. (This is the same drift the web repo carries at the framework level — targeting one runtime while building on another's SDK — noted in the web platform's opening.)
The executable with no pipeline
Three of the five executables have pipelines that at least deploy, gated on branch: a service-principal login, then a zip-deploy to the environment matching the branch. The fourth — the shift calculator, the six-hundred-line reconciliation engine that writes the fact table every dashboard reads — has no CI file at all. No build stage, no deploy stage, no trace in the repository of how it reaches the machine that runs it. A config value hints it runs as a scheduled job; the scheduler itself is another of the estate's invisible components, like the badge ingestion and the phantom Hangfire.
Think about the asymmetry. The most consequential executable in the estate — the one that infers shifts, computes allowances, and can rewrite two years of attendance — is the one with no automated path to production. Its deployment is manual, undocumented, and unrepeatable; the knowledge of how to ship it lives in a person, not a file. The zip-deploys on the other three are crude, but they are at least recorded — a new engineer can read the YAML and know how the notifier reaches Azure. For the calculator there is nothing to read. When the person who deploys it by hand leaves, the estate's most important job becomes undeployable until someone reverse-engineers the ritual. The riskiest code had the least process, which is the exact inversion of where process belongs.
A DLL with no source, shipping a pasted algorithm
The last artefact ties the whole delivery story to part 4. One of the shared packages — the monthly report generator — ships as a compiled DLL only. Its source exists in none of the repositories I could read; there is a version 1.0.0 binary in the SharedDLL folders and nothing behind it. The leave sync depends on it to recompute reports when a cancellation forces a full re-derivation.
And here is the twist: the algorithm inside that opaque DLL — the shift calculation — is also present as inline source, hand-pasted into the shift calculator's six hundred lines. The same logic ships two ways: as a black box the leave sync links against, and as readable-but-duplicated code the console job carries itself. So the estate has the worst of both worlds at once. The DLL is unmaintainable because its source is gone — you cannot fix a bug in it, only work around it. The inline copy is unmaintainable because it is a duplicate — fix the bug there and the DLL still has it, and vice versa. There is no single place the shift algorithm lives; there are two, one of which you cannot even open. A bug in shift inference — and part 5 noted the algorithm has no confidence threshold and no tie-break — would have to be fixed in a readable copy and somehow in a binary with no source, and the two paths through the estate would silently diverge the moment you touched either.
A binary with no source is a liability the day you ship it; a binary with no source whose logic is also copy-pasted elsewhere is two liabilities pretending to be reuse. The honest version was always available: one source-controlled package, built by CI, consumed by both the leave sync and the calculator, from a real feed. Every ingredient existed. They were assembled into the shape most likely to drift.
That completes the tour of the machinery. What remains is the accounting — eighteen parts of patterns, defects, absences and genuine cleverness, added up honestly. What did coordinating five executables through one database actually cost, what earned its keep, and what would I build instead today? Next, the reckoning: five executables, one database.