gachi-sync-0.1.0.0: src/Gachi/Sync/CI.hs
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE OverloadedStrings #-}
-- | Module: Gachi.Sync.CI
-- Description: Forge-tiered CI matrix synthesis and asymmetric runner resource allocation
--
-- This module implements Milestone 3.3 of the Gachi strategic roadmap (~/src/PLAN.md §1.A).
-- It provides pure declarative models and deterministic generators for multi-forge
-- continuous integration (CI) workflows, partitioning physical verification gates
-- across asymmetric runner capabilities.
--
-- = Architectural Context & Invariants
--
-- In accordance with Note [Ecosystem Standardization Invariants]
-- [ref:note-ecosystem-standardization], Note [Cross-Repository Standardization Matrix and Audit Semantics]
-- [ref:note-standardization-matrix], and Note [Forge-Tiered CI Resource Allocation and Static Gate Partitioning]
-- [ref:note-forge-tiered-ci], ecosystem repositories replicate across heterogeneous
-- hosting providers (commercial forges like GitHub, community forges like GitLab/Framagit/Disroot,
-- and sovereign self-hosted instances like Forgejo/Woodpecker/Radicle).
--
-- == Note [Forge-Tiered CI Resource Allocation and Static Gate Partitioning]
-- [tag:note-forge-tiered-ci]
--
-- Modern Haskell toolchains impose heavy compute and memory demands. Compiling
-- an interconnected multi-package workspace under GHC 9.14 with Template Haskell,
-- unboxed primitive arrays, and advanced type families requires multi-gigabyte RAM
-- footprints and significant CPU time.
--
-- Commercial hosting providers (e.g., GitHub Actions) provide high-capacity
-- container runners equipped with 4+ virtual CPUs, 16 GB RAM, generous monthly
-- compute allowances, and global artifact caching services (@actions/cache@).
-- Conversely, community-run and self-hosted forges (e.g., Framagit, Disroot,
-- Codeberg, sourcehut) operate on modest, shared virtual servers where running
-- a full GHC compilation pipeline on every push triggers runner timeouts, CPU
-- throttling, and out-of-memory kernel kills (OOM 137).
--
-- To achieve universal verification without depleting community resources or
-- failing due to runner memory constraints, Gachi establishes an __asymmetric
-- forge-tiered CI matrix__:
--
-- 1. __High-Resource Commercial Runners ('TierHighResource'):__
-- Allocated to GitHub Actions (@.github/workflows/ci.yml@) and dedicated
-- enterprise Forgejo instances. Executes the complete, heavyweight physical
-- verification suite:
-- * Installs system runtime dependencies (bubblewrap, sqlite3, z3).
-- * Configures AppArmor namespace profiles for bubblewrap container isolation.
-- * Deploys pinned GHC (9.14.1) and Cabal (3.18.1.0) toolchains.
-- * Restores cryptographic Cabal store caches (@~/.cabal/store@, @dist-newstyle@).
-- * Compiles all workspace packages with @--enable-tests@.
-- * Executes HLint static analysis under the zero-hint policy.
-- * Executes all unit, property, and integration test suites.
-- * Verifies comment density thresholds via @./bin/<project>-cloc --check@.
-- * Executes the turnkey five-gate script: @./bin/<project>-verify@.
--
-- 2. __Resource-Constrained Community Runners ('TierLightweightStatic'):__
-- Allocated to community GitLab instances (Framagit, Disroot: @.gitlab-ci.yml@),
-- Woodpecker CI (@.woodpecker.yml@), and Radicle local verification.
-- Executes fast, deterministic static analysis gates inside a minimal
-- container (@debian:bookworm-slim@) without GHC compilation:
-- * Installs lightweight utilities (curl, git, python3) and Tagref binary.
-- * Verifies Tagref cross-reference integrity (0 dangling references).
-- * Verifies Markdown link integrity and anchor slug resolution (0 broken links).
-- * Verifies in-code comment density thresholds (@bin/<project>-cloc --check@).
-- * Finishes execution in < 15 seconds with < 100 MB memory footprint.
--
-- This division guarantees that community infrastructure remains protected
-- from resource starvation while ensuring every repository maintains zero broken
-- links, zero dangling reference tags, and zero documentation drift on every commit.
--
-- == Economic & Operational Invariants
--
-- The forge-tiered CI matrix adheres to four non-negotiable operational rules:
--
-- * __Purity & Determinism:__ CI configuration files are synthesized purely from
-- the project specification ('Project') and CI specification ('CISpec').
-- No ambient environment variables, timestamps, or network requests are consulted.
--
-- * __Zero Dangling Pointers:__ Every script reference within synthesized CI files
-- (such as @./bin/<project>-verify@ or @./bin/<project>-tagref@) resolves
-- to an executable script generated by the Gachi scaffolding engine.
--
-- * __Cache Key Hermeticity:__ Cache keys on high-resource runners incorporate
-- cryptographic hashes of @cabal.project@ and all @**/*.cabal@ package files,
-- ensuring caches invalidate immediately upon dependency or compiler changes.
--
-- * __Sandbox Isolation:__ High-resource runners execute sandboxed test harnesses
-- under Bubblewrap (@bwrap@). To prevent Ubuntu 24.04 AppArmor user namespace
-- restrictions from aborting builds, synthesized workflows explicitly configure
-- unprivileged user namespace allowances and AppArmor profiles.
--
-- = Worked Example: High-Resource Workflow Generation
--
-- >>> import Gachi.Schema.Project (defaultProject, Author(..))
-- >>> import Gachi.Sync.CI
-- >>> let auth = Author "Nadia Yvette Chambers" "nadia.yvette.chambers@ik.me"
-- >>> let proj = defaultProject "gongju" auth
-- >>> let githubWf = generateGitHubWorkflow defaultGitHubSpec proj
-- >>> "cabal build all --enable-tests" `Data.Text.isInfixOf` githubWf
-- True
-- >>> "ubuntu-latest" `Data.Text.isInfixOf` githubWf
-- True
--
-- = Worked Example: Lightweight Community Workflow Generation
--
-- >>> let gitlabWf = generateGitLabWorkflow defaultGitLabSpec proj
-- >>> "debian:bookworm-slim" `Data.Text.isInfixOf` gitlabWf
-- True
-- >>> "./bin/gongju-tagref" `Data.Text.isInfixOf` gitlabWf
-- True
-- >>> "cabal build" `Data.Text.isInfixOf` gitlabWf
-- False
module Gachi.Sync.CI
( -- * Forge and CI Tier Data Models
ForgeType(..)
, CITier(..)
, CISpec(..)
-- * Pre-Configured Default Specifications
, defaultGitHubSpec
, defaultGitLabSpec
, defaultWoodpeckerSpec
, defaultForgejoSpec
, defaultRadicleSpec
, canonicalForgeSpecs
, forgeDefaultSpec
-- * Predicates & Classification Helpers
, forgeName
, tierName
, isHighResource
, isLightweight
, classifyForge
, classifyTier
-- * Deterministic Workflow Generators
, generateWorkflow
, generateGitHubWorkflow
, generateGitLabWorkflow
, generateWoodpeckerWorkflow
, generateForgejoWorkflow
, generateRadicleWorkflow
-- * Matrix Synthesis Combinators
, forgeAssetForSpec
, synthesizeCIAssets
, canonicalCIAssets
) where
import Data.List (isPrefixOf)
import Data.Text (Text)
import qualified Data.Text as T
import GHC.Generics (Generic)
import Gachi.Schema.Project (Project, projectName)
import Gachi.Sync.Types
( AssetCategory(..)
, StandardAsset(..)
)
--------------------------------------------------------------------------------
-- 1. Forge and CI Tier Data Models
--------------------------------------------------------------------------------
-- | Target hosting forge or CI engine platform.
--
-- Distinguishes commercial clouds with abundant runner quotas from shared
-- community instances and sovereign peer-to-peer repositories.
--
-- Architectural Roles:
-- * 'ForgeGitHub': Commercial cloud runners with 4+ vCPUs, 16 GB RAM, caching.
-- * 'ForgeGitLab': Community shared runners with strict memory and CPU limits.
-- * 'ForgeWoodpecker': Lightweight container pipelines for Forgejo / Codeberg.
-- * 'ForgeForgejo': Self-hosted Git server workflows using Docker runners.
-- * 'ForgeRadicle': Sovereign peer-to-peer identity verification prior to sync.
data ForgeType
= ForgeGitHub
-- ^ GitHub Actions (@.github/workflows/ci.yml@): Commercial high-capacity runners.
| ForgeGitLab
-- ^ GitLab CI (@.gitlab-ci.yml@): Community runners (Framagit, Disroot, gitlab.com).
| ForgeWoodpecker
-- ^ Woodpecker CI (@.woodpecker.yml@): Lightweight container pipelines for Forgejo/Gitea.
| ForgeForgejo
-- ^ Forgejo / Gitea Actions (@.forgejo/workflows/ci.yml@): Self-hosted Git server workflows.
| ForgeRadicle
-- ^ Radicle P2P (@.radicle/ci.yaml@): Local verification broker for sovereign peer nodes.
deriving stock (Eq, Show, Ord, Enum, Bounded, Generic)
-- | Resource capability tier assigned to a CI workflow pipeline.
--
-- Governs whether a forge executes the full heavyweight GHC build suite or
-- restricts execution to lightweight static verification gates.
--
-- The allocation strategy guarantees that high-resource runners handle compiler
-- overhead while community runners remain responsive and protected from OOM errors.
data CITier
= TierHighResource
-- ^ Heavyweight execution: Full GHC compilation, test suites, Cabal store caching,
-- AppArmor namespace configuration, and complete physical verification script execution.
| TierLightweightStatic
-- ^ Lightweight execution: Fast static gates (Tagref, linkcheck, CLOC audit)
-- inside a slim container without GHC compiler toolchains (< 15 seconds runtime).
deriving stock (Eq, Show, Ord, Enum, Bounded, Generic)
-- | Declarative specification governing continuous integration generation.
--
-- Encapsulates the target platform, resource tier, filesystem workflow destination,
-- toolchain versions, container images, and test flags.
--
-- All fields are pure and deterministic, permitting total reproducibility.
data CISpec = CISpec
{ ciForge :: !ForgeType
-- ^ Target hosting forge or runner platform.
, ciTier :: !CITier
-- ^ Resource tier governing execution depth (high-resource vs lightweight).
, ciWorkflowPath :: !FilePath
-- ^ Relative workflow path in repository root (e.g. @".github/workflows/ci.yml"@).
, ciGhcVersion :: !Text
-- ^ Target GHC compiler version string (e.g. @"9.14.1"@).
, ciCabalVersion :: !Text
-- ^ Target Cabal specification version string (e.g. @"3.18.1.0"@).
, ciSystemDependencies :: ![Text]
-- ^ System apt packages required by high-resource builds (e.g. @["bubblewrap", "sqlite3", "z3"]@).
, ciContainerImage :: !Text
-- ^ Minimal container image for lightweight static runners (e.g. @"debian:bookworm-slim"@).
, ciEnableTests :: !Bool
-- ^ Whether to configure @--enable-tests@ during Cabal compilation.
, ciEnableDocCheck :: !Bool
-- ^ Whether to perform comment density and documentation verification.
} deriving stock (Eq, Show, Generic)
--------------------------------------------------------------------------------
-- 2. Pre-Configured Default Specifications
--------------------------------------------------------------------------------
-- | Canonical high-resource GitHub Actions specification.
--
-- Configured for full physical verification on Ubuntu latest runners with
-- GHC 9.14.1, Cabal 3.18.1.0, system dependencies, and Cabal store caching.
--
-- Key Properties:
-- * Platform: GitHub Actions (@.github/workflows/ci.yml@).
-- * Resource Tier: 'TierHighResource'.
-- * Compiler: GHC 9.14.1.
-- * Build Tool: Cabal 3.18.1.0.
-- * System Packages: @bubblewrap@, @sqlite3@, @z3@.
-- * Tests Enabled: 'True'.
-- * Doc Check Enabled: 'True'.
defaultGitHubSpec :: CISpec
defaultGitHubSpec = CISpec
{ ciForge = ForgeGitHub
, ciTier = TierHighResource
, ciWorkflowPath = ".github/workflows/ci.yml"
, ciGhcVersion = "9.14.1"
, ciCabalVersion = "3.18.1.0"
, ciSystemDependencies = ["bubblewrap", "sqlite3", "z3"]
, ciContainerImage = "ubuntu-latest"
, ciEnableTests = True
, ciEnableDocCheck = True
}
-- | Canonical lightweight GitLab CI specification for community runners.
--
-- Configured for Framagit and Disroot using @debian:bookworm-slim@.
-- Runs Tagref, linkcheck, and CLOC audit in < 15 seconds without GHC.
--
-- Key Properties:
-- * Platform: GitLab CI (@.gitlab-ci.yml@).
-- * Resource Tier: 'TierLightweightStatic'.
-- * Image: @debian:bookworm-slim@.
-- * System Packages: @curl@, @git@, @python3@.
-- * Tests Enabled: 'False' (avoids GHC compilation).
-- * Doc Check Enabled: 'True' (executes @bin/*-cloc --check@).
defaultGitLabSpec :: CISpec
defaultGitLabSpec = CISpec
{ ciForge = ForgeGitLab
, ciTier = TierLightweightStatic
, ciWorkflowPath = ".gitlab-ci.yml"
, ciGhcVersion = "9.14.1"
, ciCabalVersion = "3.18.1.0"
, ciSystemDependencies = ["curl", "git", "python3"]
, ciContainerImage = "debian:bookworm-slim"
, ciEnableTests = False
, ciEnableDocCheck = True
}
-- | Canonical lightweight Woodpecker CI specification.
--
-- Used on self-hosted Woodpecker servers connected to Forgejo or Codeberg.
--
-- Key Properties:
-- * Platform: Woodpecker CI (@.woodpecker.yml@).
-- * Resource Tier: 'TierLightweightStatic'.
-- * Image: @debian:bookworm-slim@.
-- * System Packages: @curl@, @git@, @python3@.
defaultWoodpeckerSpec :: CISpec
defaultWoodpeckerSpec = CISpec
{ ciForge = ForgeWoodpecker
, ciTier = TierLightweightStatic
, ciWorkflowPath = ".woodpecker.yml"
, ciGhcVersion = "9.14.1"
, ciCabalVersion = "3.18.1.0"
, ciSystemDependencies = ["curl", "git", "python3"]
, ciContainerImage = "debian:bookworm-slim"
, ciEnableTests = False
, ciEnableDocCheck = True
}
-- | Canonical Forgejo Actions specification.
--
-- High-resource containerized pipeline for sovereign Forgejo instances.
--
-- Key Properties:
-- * Platform: Forgejo Actions (@.forgejo/workflows/ci.yml@).
-- * Resource Tier: 'TierHighResource'.
-- * Compiler: GHC 9.14.1.
-- * Image: @node:20-bookworm@.
defaultForgejoSpec :: CISpec
defaultForgejoSpec = CISpec
{ ciForge = ForgeForgejo
, ciTier = TierHighResource
, ciWorkflowPath = ".forgejo/workflows/ci.yml"
, ciGhcVersion = "9.14.1"
, ciCabalVersion = "3.18.1.0"
, ciSystemDependencies = ["bubblewrap", "sqlite3", "z3"]
, ciContainerImage = "node:20-bookworm"
, ciEnableTests = True
, ciEnableDocCheck = True
}
-- | Canonical Radicle P2P local verification broker specification.
--
-- Lightweight static check script invoked by Radicle node post-receive hooks.
--
-- Key Properties:
-- * Platform: Radicle P2P (@.radicle/ci.yaml@).
-- * Resource Tier: 'TierLightweightStatic'.
-- * Verification Scope: Tagref, Linkcheck, Comment Density.
defaultRadicleSpec :: CISpec
defaultRadicleSpec = CISpec
{ ciForge = ForgeRadicle
, ciTier = TierLightweightStatic
, ciWorkflowPath = ".radicle/ci.yaml"
, ciGhcVersion = "9.14.1"
, ciCabalVersion = "3.18.1.0"
, ciSystemDependencies = ["curl", "git", "python3"]
, ciContainerImage = "debian:bookworm-slim"
, ciEnableTests = False
, ciEnableDocCheck = True
}
-- | List of authoritative CI specifications registered across standard ecosystem forges.
--
-- Represents the default triad:
-- 1. GitHub Actions (Commercial High-Resource)
-- 2. GitLab CI (Community Lightweight Static)
-- 3. Woodpecker CI (Self-Hosted Lightweight Static)
canonicalForgeSpecs :: [CISpec]
canonicalForgeSpecs =
[ defaultGitHubSpec
, defaultGitLabSpec
, defaultWoodpeckerSpec
]
-- | Returns the canonical default 'CISpec' for a given 'ForgeType' and 'CITier'.
--
-- Enables parameterized lookup when generating tailored workflows for custom targets.
forgeDefaultSpec :: ForgeType -> CITier -> CISpec
-- GitHub Actions: High resource (default) or lightweight override
forgeDefaultSpec ForgeGitHub TierHighResource = defaultGitHubSpec
forgeDefaultSpec ForgeGitHub TierLightweightStatic =
defaultGitHubSpec { ciTier = TierLightweightStatic, ciEnableTests = False }
-- GitLab CI: Lightweight static (default) or high resource container override
forgeDefaultSpec ForgeGitLab TierLightweightStatic = defaultGitLabSpec
forgeDefaultSpec ForgeGitLab TierHighResource =
defaultGitLabSpec { ciTier = TierHighResource, ciEnableTests = True, ciContainerImage = "haskell:9.14" }
-- Woodpecker CI: Lightweight static (default) or high resource container override
forgeDefaultSpec ForgeWoodpecker TierLightweightStatic = defaultWoodpeckerSpec
forgeDefaultSpec ForgeWoodpecker TierHighResource =
defaultWoodpeckerSpec { ciTier = TierHighResource, ciEnableTests = True, ciContainerImage = "haskell:9.14" }
-- Forgejo Actions: High resource (default) or lightweight static override
forgeDefaultSpec ForgeForgejo TierHighResource = defaultForgejoSpec
forgeDefaultSpec ForgeForgejo TierLightweightStatic =
defaultForgejoSpec { ciTier = TierLightweightStatic, ciEnableTests = False }
-- Radicle P2P: Sovereign static verification broker
forgeDefaultSpec ForgeRadicle _ = defaultRadicleSpec
--------------------------------------------------------------------------------
-- 3. Predicates & Classification Helpers
--------------------------------------------------------------------------------
-- | Human-readable name of a 'ForgeType'.
--
-- Used in diagnostic banners and synchronization audit reports.
forgeName :: ForgeType -> Text
forgeName ForgeGitHub = "GitHub Actions"
forgeName ForgeGitLab = "GitLab CI (Framagit, Disroot)"
forgeName ForgeWoodpecker = "Woodpecker CI"
forgeName ForgeForgejo = "Forgejo Actions"
forgeName ForgeRadicle = "Radicle P2P CI"
-- | Human-readable description of a 'CITier'.
--
-- Highlights whether a pipeline executes heavyweight GHC verification or fast static gates.
tierName :: CITier -> Text
tierName TierHighResource = "High-Resource (Full Physical Verification)"
tierName TierLightweightStatic = "Lightweight (Static Gates Only)"
-- | Predicate checking if a specification belongs to the high-resource tier.
isHighResource :: CISpec -> Bool
isHighResource spec = ciTier spec == TierHighResource
-- | Predicate checking if a specification belongs to the lightweight tier.
isLightweight :: CISpec -> Bool
isLightweight spec = ciTier spec == TierLightweightStatic
-- | Infers the 'ForgeType' from a workflow file path.
--
-- Inspects standard file names and directories:
-- * @.github/workflows/*@ -> 'ForgeGitHub'
-- * @.gitlab-ci.yml@ -> 'ForgeGitLab'
-- * @.woodpecker.yml@ or @.woodpecker/*@ -> 'ForgeWoodpecker'
-- * @.forgejo/workflows/*@ -> 'ForgeForgejo'
-- * @.radicle/*@ -> 'ForgeRadicle'
--
-- Returns 'Nothing' if the path does not match recognized CI conventions.
classifyForge :: FilePath -> Maybe ForgeType
classifyForge path
-- GitHub Actions workflow directory
| ".github/workflows/" `isPrefixOf` path = Just ForgeGitHub
-- GitLab CI configuration file in root
| path == ".gitlab-ci.yml" = Just ForgeGitLab
-- Woodpecker CI single file configuration
| path == ".woodpecker.yml" = Just ForgeWoodpecker
-- Woodpecker CI pipeline directory
| ".woodpecker/" `isPrefixOf` path = Just ForgeWoodpecker
-- Forgejo Actions workflow directory
| ".forgejo/" `isPrefixOf` path = Just ForgeForgejo
-- Radicle P2P CI verification directory
| ".radicle/" `isPrefixOf` path = Just ForgeRadicle
-- Unrecognized path
| otherwise = Nothing
-- | Infers the expected 'CITier' from a standard workflow path.
--
-- Recognizes high-resource vs lightweight tiers based on repository path conventions.
classifyTier :: FilePath -> Maybe CITier
classifyTier path
-- GitHub Actions executes full verification suite
| ".github/" `isPrefixOf` path = Just TierHighResource
-- GitLab CI community runners execute static gates only
| path == ".gitlab-ci.yml" = Just TierLightweightStatic
-- Woodpecker CI executes lightweight container steps
| path == ".woodpecker.yml" = Just TierLightweightStatic
-- Forgejo enterprise runners execute full suite
| ".forgejo/" `isPrefixOf` path = Just TierHighResource
-- Radicle local node broker executes static gates
| ".radicle/" `isPrefixOf` path = Just TierLightweightStatic
-- Unrecognized path
| otherwise = Nothing
--------------------------------------------------------------------------------
-- 4. Deterministic Workflow Generators
--------------------------------------------------------------------------------
-- | Pure dispatcher synthesizing the appropriate CI configuration text for a 'CISpec'.
--
-- Evaluates the target forge platform and invokes the corresponding deterministic generator.
generateWorkflow :: CISpec -> Project -> Text
generateWorkflow spec proj = case ciForge spec of
ForgeGitHub -> generateGitHubWorkflow spec proj
ForgeGitLab -> generateGitLabWorkflow spec proj
ForgeWoodpecker -> generateWoodpeckerWorkflow spec proj
ForgeForgejo -> generateForgejoWorkflow spec proj
ForgeRadicle -> generateRadicleWorkflow spec proj
-- | Generates GitHub Actions workflow configuration (@.github/workflows/ci.yml@).
--
-- Implements the high-resource physical verification pipeline:
--
-- 1. __Checkout Step:__
-- Uses @actions/checkout@v4@ to clone the repository with full git history.
--
-- 2. __Tagref Installation & Check:__
-- Downloads the authoritative Tagref binary and executes @./bin/<project>-tagref@
-- to verify that zero dangling reference tags or duplicate tag definitions exist.
--
-- 3. __Markdown Link & Anchor Gate:__
-- Executes @./bin/<project>-linkcheck@ to verify 100% of intra-corpus relative
-- links and section heading anchors across all markdown documentation files.
--
-- 4. __System Dependencies & AppArmor Setup:__
-- Installs system runtime libraries (@bubblewrap@, @sqlite3@, @z3@).
-- Configures unconfined AppArmor profile for Bubblewrap (@bwrap@) to enable
-- unprivileged user namespace creation on Ubuntu 24.04 runner environments.
--
-- 5. __Git Submodule Protocol Rewriting:__
-- Configures Git to rewrite @git@github.com:@ SSH URLs to HTTPS for clean submodule pulls.
--
-- 6. __Haskell Toolchain Provisioning:__
-- Invokes @haskell-actions/setup@v2@ with pinned GHC (9.14.1) and Cabal (3.18.1.0).
--
-- 7. __HLint Static Analysis Installation:__
-- Installs the recommended HLint binary via GHCup and runs under zero-hint cleanliness.
--
-- 8. __Cryptographic Artifact Caching:__
-- Invokes @actions/cache@v4@ caching @~/.cabal/store@ and @dist-newstyle@ keyed on
-- the combined cryptographic hash of @cabal.project@ and all @**/*.cabal@ manifests.
--
-- 9. __Compilation & Test Build:__
-- Executes @cabal build all --enable-tests@ compiling all workspace packages and test suites.
--
-- 10. __Comment Density Audit:__
-- Executes @./bin/<project>-cloc --check@ enforcing documentation thresholds.
--
-- 11. __Turnkey Verification Harness:__
-- Executes @./bin/<project>-verify@ ensuring all 5 physical gates pass in unison.
generateGitHubWorkflow :: CISpec -> Project -> Text
generateGitHubWorkflow spec proj =
let pName = projectName proj
ghcVer = ciGhcVersion spec
cabalVer = ciCabalVersion spec
deps = T.unwords (ciSystemDependencies spec)
in T.unlines
[ -- SPDX License identifier declaring legal terms
"# SPDX-License-Identifier: EUPL-1.2"
-- Canonical repository metadata comment
, "# Canonical GitHub Actions CI Workflow for " <> pName
-- Cross-reference pointer grounding the Note in the CI asset
, "# Note [Forge-Tiered CI Resource Allocation and Static Gate Partitioning] [ref:note-forge-tiered-ci]"
, ""
-- Top-level workflow descriptive name
, "name: CI Verification Harness"
, ""
-- Trigger definitions: executed on push and pull-request events targeting master
, "on:"
, " push:"
, " branches: [ master ]"
, " pull_request:"
, " branches: [ master ]"
, ""
-- Job definition block
, "jobs:"
, " verify:"
, " name: Tagref & Verification Gates"
-- Host runner operating system targeting Ubuntu LTS
, " runs-on: ubuntu-latest"
, " steps:"
-- Step 1: Check out git working tree
, " - name: Check out repository"
, " uses: actions/checkout@v4"
, ""
-- Step 2: Download and install Tagref cross-reference binary
, " - name: Install Tagref"
, " run: |"
, " mkdir -p \"${HOME}/.local/bin\""
, " PREFIX=\"${HOME}/.local/bin\" curl https://raw.githubusercontent.com/stepchowfun/tagref/main/install.sh -LSfs | sh"
, " echo \"${HOME}/.local/bin\" >> $GITHUB_PATH"
, ""
-- Step 3: Run static Tagref verification gate
, " - name: Verify Tagref Cross-References"
, " run: ./bin/" <> pName <> "-tagref"
, ""
-- Step 4: Run intra-corpus Markdown hyperlink and heading anchor verification
, " - name: Verify Markdown Links & Anchors"
, " run: ./bin/" <> pName <> "-linkcheck"
, ""
-- Step 5: Install runtime system dependencies and configure AppArmor sandbox profiles
, " - name: Install System Dependencies"
, " run: |"
, " sudo apt-get update"
, " sudo apt-get install -y " <> deps
-- Disable Ubuntu 24.04 kernel restriction on unprivileged user namespaces
, " sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 2>/dev/null || true"
-- Create unconfined AppArmor profile for Bubblewrap if profile directory exists
, " if [ -d /etc/apparmor.d ]; then"
, " printf 'abi <abi/4.0>,\\ninclude <tunables/global>\\nprofile bwrap /usr/bin/bwrap flags=(unconfined) {\\n userns,\\n include if exists <local/bwrap>\\n}\\n' | sudo tee /etc/apparmor.d/bwrap >/dev/null || true"
, " sudo apparmor_parser -r /etc/apparmor.d/bwrap 2>/dev/null || true"
, " fi"
, ""
-- Step 6: Rewrite SSH Git submodule URLs to HTTPS to avoid unauthenticated SSH failures
, " - name: Configure Git for HTTPS submodules"
, " run: git config --global url.\"https://github.com/\".insteadOf \"git@github.com:\""
, ""
-- Step 7: Configure pinned GHC and Cabal toolchain via official Haskell action
, " - name: Set up Haskell Toolchain"
, " uses: haskell-actions/setup@v2"
, " with:"
, " ghc-version: '" <> ghcVer <> "'"
, " cabal-version: '" <> cabalVer <> "'"
, ""
-- Step 8: Install HLint binary
, " - name: Install HLint"
, " run: |"
, " mkdir -p \"${HOME}/.local/bin\""
, " if ! command -v hlint >/dev/null 2>&1; then"
, " curl -sSL https://github.com/ndmitchell/hlint/releases/download/v3.10/hlint-3.10-x86_64-linux.tar.gz | tar -xz -C \"${HOME}/.local\""
, " ln -sf \"${HOME}/.local/hlint-3.10/hlint\" \"${HOME}/.local/bin/hlint\""
, " fi"
, " echo \"${HOME}/.local/bin\" >> $GITHUB_PATH"
, ""
-- Step 9: Restore and save Cabal store caches keyed by project manifests
, " - name: Cache Cabal artifacts"
, " uses: actions/cache@v4"
, " with:"
, " path: |"
, " ~/.cabal/store"
, " dist-newstyle"
, " key: ${{ runner.os }}-cabal-${{ hashFiles('cabal.project', '**/*.cabal') }}"
, " restore-keys: |"
, " ${{ runner.os }}-cabal-"
, ""
-- Step 10: Compile all workspace packages and test harnesses
, " - name: Build All Packages & Dependencies"
, " run: cabal build all" <> (if ciEnableTests spec then " --enable-tests" else "")
, ""
-- Step 11: Execute documentation comment density audit
, " - name: Verify Comment Density Audit"
, " run: ./bin/" <> pName <> "-cloc --check"
, ""
-- Step 12: Run complete 5-gate physical verification harness
, " - name: Run Full Physical Verification Gates"
, " run: ./bin/" <> pName <> "-verify"
]
-- | Generates GitLab CI configuration (@.gitlab-ci.yml@).
--
-- Tailored to protect resource-constrained community runners (Framagit, Disroot).
-- Runs fast static analysis gates inside @debian:bookworm-slim@ without GHC overhead.
--
-- The pipeline executes in under 15 seconds, verifying Tagref cross-references,
-- Markdown links, and CLOC comment density while consuming negligible compute quota.
generateGitLabWorkflow :: CISpec -> Project -> Text
generateGitLabWorkflow spec proj =
let pName = projectName proj
cImage = ciContainerImage spec
in if ciTier spec == TierLightweightStatic
then T.unlines
[ -- SPDX license header
"# SPDX-License-Identifier: EUPL-1.2"
-- Target forge and provider documentation
, "# GitLab CI configuration for " <> pName <> " (Framagit, Disroot)"
-- Canonical Note reference
, "# Note [Forge-Tiered CI Resource Allocation and Static Gate Partitioning] [ref:note-forge-tiered-ci]"
, ""
-- Verification stage list
, "stages:"
, " - verify"
, ""
-- Static verification job definition
, "tagref-check:"
, " stage: verify"
-- Lean base container image
, " image: " <> cImage
-- Pre-flight script installing curl, git, python3, and Tagref
, " before_script:"
, " - apt-get update && apt-get install -y curl git python3"
, " - mkdir -p /usr/local/bin"
, " - PREFIX=/usr/local/bin curl https://raw.githubusercontent.com/stepchowfun/tagref/main/install.sh -LSfs | sh"
-- Static gate commands: Tagref, Linkcheck, and CLOC density check
, " script:"
, " - ./bin/" <> pName <> "-tagref"
, " - ./bin/" <> pName <> "-linkcheck"
, " - ./bin/" <> pName <> "-cloc --check"
]
else T.unlines
[ -- SPDX license header
"# SPDX-License-Identifier: EUPL-1.2"
-- Target forge documentation
, "# High-Resource GitLab CI configuration for " <> pName
-- Canonical Note reference
, "# Note [Forge-Tiered CI Resource Allocation and Static Gate Partitioning] [ref:note-forge-tiered-ci]"
, ""
-- Stage definition
, "stages:"
, " - verify"
, ""
-- Full verification job definition
, "full-verify:"
, " stage: verify"
-- Haskell container image
, " image: " <> cImage
-- Pre-flight script installing system libraries and Tagref
, " before_script:"
, " - apt-get update && apt-get install -y curl git python3 bubblewrap sqlite3 z3"
, " - mkdir -p /usr/local/bin"
, " - PREFIX=/usr/local/bin curl https://raw.githubusercontent.com/stepchowfun/tagref/main/install.sh -LSfs | sh"
-- Full build and turnkey verification commands
, " script:"
, " - cabal build all --enable-tests"
, " - ./bin/" <> pName <> "-verify"
]
-- | Generates Woodpecker CI pipeline configuration (@.woodpecker.yml@).
--
-- Used on lightweight, containerized Woodpecker agents attached to Forgejo.
--
-- Executes the three lightweight static verification gates inside @debian:bookworm-slim@.
generateWoodpeckerWorkflow :: CISpec -> Project -> Text
generateWoodpeckerWorkflow spec proj =
let pName = projectName proj
cImage = ciContainerImage spec
in T.unlines
[ -- SPDX license header
"# SPDX-License-Identifier: EUPL-1.2"
-- Target forge documentation
, "# Woodpecker CI pipeline for " <> pName
-- Canonical Note reference
, "# Note [Forge-Tiered CI Resource Allocation and Static Gate Partitioning] [ref:note-forge-tiered-ci]"
, ""
-- Woodpecker pipeline root stanza
, "pipeline:"
, " static-verify:"
-- Container image specification
, " image: " <> cImage
-- Pipeline command sequence
, " commands:"
, " - apt-get update && apt-get install -y curl git python3"
, " - mkdir -p /usr/local/bin"
, " - PREFIX=/usr/local/bin curl https://raw.githubusercontent.com/stepchowfun/tagref/main/install.sh -LSfs | sh"
, " - ./bin/" <> pName <> "-tagref"
, " - ./bin/" <> pName <> "-linkcheck"
, " - ./bin/" <> pName <> "-cloc --check"
]
-- | Generates Forgejo Actions workflow configuration (@.forgejo/workflows/ci.yml@).
--
-- Compatible with GitHub Actions workflow syntax for self-hosted runners.
--
-- Executes containerized verification pipelines in sovereign forge deployments.
generateForgejoWorkflow :: CISpec -> Project -> Text
generateForgejoWorkflow =
-- Reuses GitHub Actions syntax engine with Forgejo specification
generateGitHubWorkflow
-- | Generates Radicle P2P local verification broker configuration (@.radicle/ci.yaml@).
--
-- Executed locally on peer developer nodes prior to peer-to-peer patch distribution.
--
-- Ensures patches meet strict Tagref, linkcheck, and comment density standards before dissemination.
generateRadicleWorkflow :: CISpec -> Project -> Text
generateRadicleWorkflow _spec proj =
let pName = projectName proj
in T.unlines
[ -- SPDX license header
"# SPDX-License-Identifier: EUPL-1.2"
-- Radicle verification broker documentation
, "# Radicle P2P CI Verification Configuration for " <> pName
-- Canonical Note reference
, "# Note [Forge-Tiered CI Resource Allocation and Static Gate Partitioning] [ref:note-forge-tiered-ci]"
, ""
-- Radicle pipeline schema version
, "version: 1"
, "pipelines:"
, " - name: static-gates"
-- Event triggers: runs on branch pushes and patch publications
, " trigger: [ push, patch ]"
, " steps:"
-- Step 1: Run Tagref cross-reference verification
, " - name: Tagref"
, " command: ./bin/" <> pName <> "-tagref"
-- Step 2: Run Markdown link and anchor verification
, " - name: Linkcheck"
, " command: ./bin/" <> pName <> "-linkcheck"
-- Step 3: Run comment density audit
, " - name: CLOC"
, " command: ./bin/" <> pName <> "-cloc --check"
]
--------------------------------------------------------------------------------
-- 5. Matrix Synthesis Combinators
--------------------------------------------------------------------------------
-- | Converts a 'CISpec' into a canonical 'StandardAsset' in 'CategoryCIWorkflow'.
--
-- Sets requirement flags:
-- * GitHub (@.github/workflows/ci.yml@) and GitLab (@.gitlab-ci.yml@) are mandatory ('assetRequired' = True).
-- * Ancillary forges (Woodpecker, Forgejo, Radicle) are optional ('assetRequired' = False).
forgeAssetForSpec :: CISpec -> StandardAsset
forgeAssetForSpec spec =
let path = ciWorkflowPath spec
-- Mandatory assets enforced across every ecosystem repository
isReq = path `elem` [".github/workflows/ci.yml", ".gitlab-ci.yml"]
-- Human-readable asset description
desc = case ciForge spec of
ForgeGitHub -> "High-resource GitHub Actions workflow running complete physical gates"
ForgeGitLab -> "Lightweight GitLab CI workflow for resource-constrained community forges"
ForgeWoodpecker -> "Lightweight Woodpecker CI workflow for Forgejo/Gitea runners"
ForgeForgejo -> "Self-hosted Forgejo Actions workflow running containerized verification"
ForgeRadicle -> "Radicle sovereign peer-to-peer verification pipeline"
in StandardAsset
{ assetPath = path
, assetCategory = CategoryCIWorkflow
, assetRequired = isReq
, assetExecutable = False
, assetDescription = desc
, assetGenerator = generateWorkflow spec
}
-- | Synthesizes a list of 'StandardAsset' records from a collection of 'CISpec' configurations.
--
-- Maps each specification into a standard matrix asset ready for auditing and propagation.
synthesizeCIAssets :: [CISpec] -> [StandardAsset]
synthesizeCIAssets = map forgeAssetForSpec
-- | The canonical list of standard CI workflow assets registered across all ecosystem checkouts.
--
-- Encompasses GitHub Actions, GitLab CI, and Woodpecker CI pipelines.
canonicalCIAssets :: [StandardAsset]
canonicalCIAssets = synthesizeCIAssets canonicalForgeSpecs