Skip to content

feat(ci): CI host integration as ports & adapters (ADR-0005)#341

Open
ChrisonSimtian wants to merge 10 commits into
mainfrom
feature/ci-ports-and-adapters
Open

feat(ci): CI host integration as ports & adapters (ADR-0005)#341
ChrisonSimtian wants to merge 10 commits into
mainfrom
feature/ci-ports-and-adapters

Conversation

@ChrisonSimtian

@ChrisonSimtian ChrisonSimtian commented May 31, 2026

Copy link
Copy Markdown
Collaborator

Adds the runtime-host ports & adapters seam for CI host integration (ADR-0005), validated by a GitHub-adapter spike and a new Forgejo adapter. Additive / non-breaking. Targets experimental.

What

  • Two ports, split by implementor set: IBuildHost (context — branch/commit/is-PR — CI hosts only) and IBuildReporter (warnings/errors/grouping — every host, including local Terminal).
  • Generalized discovery: Host.Default probes an overridable Host.IsActive instead of the magic-string IsRunning{Name} convention. The old convention stays as fallback, so existing hosts are unchanged.
  • Fitness tests enforce the boundary (ports never reference adapters) and the split (Terminal is a reporter, not a context-host).
  • Forgejo adapter — first provider on the new seam: IsActive override (no static), both ports, and config generation by composing the GitHub config model + a shared WorkflowCommands helper rather than inheriting GitHubActions.

Why

Establishes a stable, enforced CI-adapter seam ahead of the public plugin SDK (milestone #7), without breaking consumers. Full rationale in ADR-0005.

Validation

  • Clean build; Build.Tests 107, Common.Tests 46 green.
  • Generated .github workflows byte-identical (config path untouched).
  • No behaviour change to existing hosts.

Follow-ups (in the spike doc)

  • Forgejo detection is a naive FORGEJO_ACTIONS guess — confirm against a live instance.
  • Full Forgejo config parity needs the GitHub model-builder extracted from file placement.
  • Dedup WorkflowCommands onto GitHubActions (deferred to protect that adapter's byte-identical output).

Docs: docs/adr/0005-ci-host-integration-ports-and-adapters.md, docs/spikes/0001-ci-ports-and-adapters.md.

🤖 Generated with Claude Code

ChrisonSimtian and others added 3 commits May 30, 2026 14:20
Seeds the fast/AI lane (ADR-0004). experimental is a non-public NB.GV ref, so
builds get -alpha.<height>.g<commit>, sorting below main's -preview. Same core as
main (2026.1.0); breaking surface rides [Experimental] until the yearly cut.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…338)

* ci: realign triggers to the 3-tier ladder + hygiene (milestone #18) (#330)

Implements the trigger/hygiene slice of milestone #18 (CI cost & pipeline
structure). #325 (publish-lane realignment) already landed in the ladder PR.

- #318/#326 Cross-platform gated to release intent. windows/macos no longer run
  on main/experimental pushes (or any routine push). They run only on PR-to-
  release/* or support/*, and on v* tag pushes. ("On main we've got our edge":
  the ubuntu-latest PR gate + alpha/preview pipelines.) workflow_dispatch is not
  emitted — the generator only writes it with inputs; GitHub's run re-run covers
  on-demand cross-platform.
- #322 concurrency cancel-in-progress on ubuntu/windows/macos (generator) +
  experimental.yml + preview.yml. NOT on release.yml (never cancel a publish).
- #323/#328 Canonical CI-ignore list (docs/**, .assets/**, **/*.md) on every
  PR/push trigger. (release.yml is tag-triggered, so path-ignore is N/A there.)
- #327 Codified "feature branches run zero CI until PR'd" + the trigger model in
  docs/agents/conventions.md, with what-not-to-do guards.
- #329 Dropped dead 'submodules: recursive' from all checkouts + the generator
  (no .gitmodules; full build passes without it) and the stale vendor comment.

Generated workflows regenerated from build/Build.CI.GitHubActions.cs.

Deferred (own follow-ups): #324 split Build/Test/Pack stages; #328 caching
deep-dive; #327 automated reflective guard-test (docs guard in place now).

Also unblocked publishing separately: the github-packages environment
deployment policy now allows experimental/main/release/*/support/* + v* tags.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* ci: test before publishing on every lane + cache restore-keys (#324, #328) (#331)

#324 — experimental.yml and preview.yml ran `dotnet fallout Pack` only, publishing
alpha/preview packages WITHOUT running tests. Both now run `dotnet fallout Test Pack`
(release.yml + the PR gate already did). One invocation = NUKE's discrete internal
stages (Restore → Compile → Test → Pack), failing at the breaking stage; a test
failure stops the job before the push step, so untested packages never publish.
Separate per-step `dotnet fallout` invocations are avoided on purpose — each re-runs
the dependency graph (double-compile); the single invocation is the staged build.

#328 — added `restore-keys:` prefix fallback to the hand-written workflows' caches
for faster partial restores on key miss. Evaluation: current key (global.json +
*.csproj + Directory.Packages.props) is the right dependency set; no packages.lock.json
exists to add; build-output (bin/obj) caching deliberately not done (stale-artifact
risk). Canonical ignore list (docs/.assets/md) already applied in the trigger PR.

Codified both in docs/agents/conventions.md.

Deferred: #327 automated reflective guard-test (docs guard already in place).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…he lanes (#333) (#339)

* feat(publish): multi-channel, package-ID-aware publishing surface (#333, FALLOUT001)

IPublish gains [Experimental("FALLOUT001")] PublishTargets + a --publish-to selector;
Publish now routes one Pack output across multiple feeds via the pure, unit-tested
PublishPackageRouter (glob include/exclude by package name). Existing single-source
members stay as a back-compat default target.

Build.cs wires the two real channels: github-packages (every package incl Nuke.*,
keyed by the GitHub token) and nuget.org (Fallout.* only, never Nuke.*, keyed by
NUGET_API_KEY) — replacing the legacy single-feed push.

- src/Fallout.Components/PublishTarget.cs — PublishTarget record + PublishPackageRouter
- tests/Fallout.Components.Tests — 10 router tests (added to fallout.slnx)
- docs/experimental-apis.md — FALLOUT001 registered

Framework compiles clean (0 errors); router tests green. Workflow rewire to
`dotnet fallout Publish --publish-to …` follows once experimental is synced with main.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* ci(publish): dogfood `dotnet fallout Publish` on the alpha/preview lanes (#333)

experimental.yml and preview.yml now publish via `dotnet fallout Publish --publish-to
github-packages` instead of a hand-rolled `dotnet nuget push` loop. Publish depends on
Test + Pack, so one invocation runs Restore → Compile → Test → Pack → Publish as NUKE
stages; package routing (Fallout.* + Nuke.* → GitHub Packages) lives in Build.cs
IPublish.PublishTargets. The GitHub token is passed via the GitHubToken env.

Also restore IPublish.PackagePushSettings + PushSettingsBase (kept for back-compat) so the
multi-channel reshape stays additive — the new PublishTargets/PublishTo surface is the only
opt-in change, behind [Experimental("FALLOUT001")].

release.yml's publish jobs still use raw nuget push (they're coupled to the artifact
handoff + nuget-org approval gate) — dogfooding those is folded into #336.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(publish): make PublishTarget a sealed class so the shim generator skips it

CI caught CS0509: the TransitionShimGenerator tried to derive a Nuke.Components
shim from the sealed *record* PublishTarget. Sealed classes are skipped by design
(SHIM001), but the sealed-record shape slipped past that guard. PublishTarget is a
new type with no pre-rename consumers, so skipping its shim is correct; switching
record→sealed class hits the documented skip path. We don't use record equality/`with`.

Verified: Nuke.Components builds 0 errors (SHIM001 warning only).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(generators): accept solution-generator snapshot for new Fallout.Components.Tests

Adding tests/Fallout.Components.Tests to fallout.slnx makes the StronglyTypedSolution
generator emit a Fallout_Components_Tests accessor; update the Verify snapshot to match
(one added line — the new project's strongly-typed Solution property).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ChrisonSimtian ChrisonSimtian added the target/2026 Targets the 2026 calendar-version line (current). See ADR-0004. label May 31, 2026
ChrisonSimtian and others added 2 commits May 31, 2026 13:29
… (#340)

The dogfood `dotnet fallout Publish` run failed with "Publish target 'github-packages'
has no API key". The github-packages target resolves its key via
From<ICreateGitHubRelease>().GitHubToken → GitHubActions.Token, which reads the
GITHUB_TOKEN env var (EnvironmentInfo.GetVariable("GITHUB_TOKEN")). The lane workflows
set `GitHubToken` instead, which didn't match (ICreateGitHubRelease is also
[ParameterPrefix]-ed, so the unprefixed name wouldn't bind anyway). Set GITHUB_TOKEN.

Caught by letting the post-merge experimental publish actually run.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#346)

Feed URL nuget.pkg.github.com/ChrisonSimtian → /Fallout-build (Build.cs IPublish
github-packages target, release.yml, consumer docs) + CODEOWNERS → @Fallout-build/maintainers.
Sibling of the main repoint (#345). Feed stays PAT-gated (#344).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ChrisonSimtian added a commit that referenced this pull request Jun 3, 2026
…ts (onion step 5b)

Move the concrete CI host providers (AppVeyor, AzurePipelines, TeamCity,
GitHubActions, GitLab, TravisCI, Jenkins, Bitrise, Bitbucket, Bamboo,
SpaceAutomation) and their config generators from Fallout.Common.CI.* to
Fallout.Infrastructure.CI.*.

The Application ring uses provider-SPECIFIC capabilities (PublishTestResults,
PushArtifact, SetBuildNumber, UpdateBuildNumber, Token, …), so a generic host
abstraction can't capture them. Instead, per-provider ports in
Fallout.Application.CI — IAppVeyor/IAzurePipelines/ITeamCity/IGitHubActions —
plus a CiHost accessor that casts the detected Host.Instance to the port (null
when not running on that host). No registration needed: Host.Instance is the
existing detection seam, and the providers (subclasses of Host) implement the
ports. Components (ITest/IReportCoverage/ISignPackages/ICreateGitHubRelease) and
version/coverage attributes now call CiHost.X instead of X.Instance, so the
Application ring keeps no dependency on Fallout.Infrastructure.* — the fitness
gate still passes. The two enums the ports expose move to Fallout.Application.CI
as vocabulary. Nuke.Common CI host shims repointed to the new namespace.

(The generic CI host abstraction — ADR-0005 IBuildHost/IBuildReporter, #341 —
stays a separate additive effort.)

Tooling: OnionRewriter rules retargeted for 5b (CI → Infrastructure.CI, enums
overridden to Application.CI). Lesson #13: alias-qualified `global::Ns.Type`
refs aren't rewritten (Left includes `global::`) — hit in the shims, mopped up.

Full suite green; Application-ring fitness gate green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ChrisonSimtian and others added 5 commits June 3, 2026 19:25
Model CI host integration as ports & adapters (hexagonal seam) with an
additive-now / deletions-batched-to-the-year-cut compatibility strategy,
plus the GitHub-adapter-end-to-end spike that validates the runtime-host
port shape.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Additive seam per ADR-0005: IBuildHost supersedes the anemic IBuildServer as
the build's-eye-view runtime-host port (context + reporting). GitHubActions
implements it via explicit interface implementation, leaving the public
surface and generated workflows untouched. Adds an architecture fitness test
asserting the ports layer (Fallout.Build) never references the adapters layer
(Fallout.Common).

Spike work on experimental; LogEventSink rewire deferred (see spike verdict #2).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two ports, justified by differing implementor sets: every Host reports (so
the local Terminal is an IBuildReporter), but only CI hosts carry run context
(so Terminal is not an IBuildHost). IBuildHost rescoped to context (branch/
commit/is-PR); new IBuildReporter (warnings/errors/grouping) implemented by the
Host base via explicit impls over the existing protected-virtual hooks — so
adapters override reporting exactly as before. No behavior change.

Fitness tests pin the split (Terminal: reporter not host; GitHubActions: both).
Corrects spike finding #2 (reporting was already wired in the *.Theming.cs
partials; there was no latent bug). Updates ADR-0005 + spike verdict.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Host.Default now probes an overridable Host.IsActive member instead of
reflecting for the magic-string IsRunning{TypeName} static. The default
IsActive falls back to that legacy convention, so all existing hosts work
unchanged; new adapters override IsActive directly (no static, no name match).
Host ctors are side-effect-free, so construct-then-probe is cheap and the
active host (constructed last) remains Host.Instance.

Covers ADR-0005 #3. Tests exercise both the override and fallback paths.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
First adapter built on the finished ADR-0005 seam, proving it generalizes:
- Runtime host overrides Host.IsActive (no IsRunning{Name} static — first user
  of the #3 detection style) and implements both ports (IBuildHost context via
  GitHub-compatible GITHUB_* vars; IBuildReporter reporting).
- Config generation composes the GitHub config model + a shared WorkflowCommands
  helper for the ::cmd:: dialect, rather than inheriting GitHubActionsAttribute
  (which is blocked anyway by ConfigurationAttributeBase.Build's internal set).
  Common-case scaffold; full parity needs the GH model-builder extracted.
- Detection (FORGEJO_ACTIONS) is a naive guess pending the live instance.

Tests assert both ports, the IsActive detection path, and non-inheritance from
the GitHubActions adapter.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ChrisonSimtian ChrisonSimtian force-pushed the feature/ci-ports-and-adapters branch from 9735c5e to 64b1ea9 Compare June 3, 2026 07:25
@ChrisonSimtian ChrisonSimtian requested a review from a team as a code owner June 3, 2026 07:25
@ChrisonSimtian ChrisonSimtian changed the base branch from experimental to main June 18, 2026 09:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

target/2026 Targets the 2026 calendar-version line (current). See ADR-0004.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant