packages feed

gachi-sync-0.1.0.0: src/Gachi/Sync/Types.hs

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}

-- | Module: Gachi.Sync.Types
-- Description: Core types, models, and data structures for repository standardization
--
-- This module defines the foundational types modeling the ecosystem-wide
-- Standardization Matrix (~/src/PLAN.md §1.A), asset categories across the
-- nine canonical dimensions, asset audit outcomes, and synchronization plans.
--
-- = Architectural Context & Invariants
--
-- In accordance with Note [Ecosystem Standardization Invariants]
-- [ref:note-ecosystem-standardization], all repositories across ~/src/*
-- adhere to shared structural conventions. This module formalizes those
-- conventions as strongly-typed Haskell records suitable for static analysis,
-- diagnostic reporting, and automated configuration propagation.
--
-- == The Nine Standardization Dimensions
--
-- In accordance with ~/src/PLAN.md §1.A, the ecosystem formalizes nine core
-- standardization dimensions to maintain ergonomic parity across all checkouts:
--
-- 1. __Dimension 1 (Workspace Layout):__
--    Multi-package hierarchy (@packages/*/src@, @packages/*/test@, @.cabal@),
--    central documentation corpus under @docs/@ (@docs/INDEX.md@, @docs/notes/@),
--    and the operational planning pair (@PLAN.md@ and @STATE.md@).
--
-- 2. __Dimension 2 (Code Formatters):__
--    Deterministic code formatting profiles across Haskell (@fourmolu.yaml@),
--    Rust (@rustfmt.toml@), and C/C++ (@.clang-format@). Eliminates whitespace
--    churn and enforces AST-preserving layout.
--
-- 3. __Dimension 3 (Linters & Guardrails):__
--    Static analysis rules enforcing zero-hint cleanliness (@.hlint.yaml@)
--    and modern GHC idioms such as @OverloadedRecordDot@.
--
-- 4. __Dimension 4 (Build Conventions):__
--    Hermetic toolchain pinning (@cabal.project@) targeting GHC 9.14.1 and
--    Cabal 3.18, with developer override templates (@cabal.project.local.example@).
--
-- 5. __Dimension 5 (Git Governance):__
--    Linux kernel RFC 2822 commit message trailers (@.gitmessage@), Tagref
--    cross-reference configuration (@tagref.yml@), and pre-commit hooks.
--
-- 6. __Dimension 6 (Remote Topology):__
--    Multi-forge distribution topology separating Radicle peer-to-peer from
--    commercial and community mirrors (@docs/MAINTAINER_GUIDE.md@).
--
-- 7. __Dimension 7 (Tiered CI Workflows):__
--    Asymmetric forge resource allocation: heavyweight physical verification on
--    GitHub Actions versus lightweight static checks on community GitLab runners.
--
-- 8. __Dimension 8 (Polyglot Invariants):__
--    ABI stability, FFI boundary pinning, and cross-language integration test runners.
--
-- 9. __Dimension 9 (Agent Advisory Layer):__
--    Context-budgeted agent instructions (@AGENTS.md@, ~4 KB limit), compatibility
--    symlinks (@CLAUDE.md@), and modular rules in @.agents/rules/@.
--
-- 10. __Physical Verification Harnesses:__
--     Turnkey shell scripts in @bin/@ enforcing the five physical gates across
--     every checkout (@verify@, @tagref@, @linkcheck@, @cloc@, @generate-tags@).
--
-- == Audit and Synchronization Lifecycle
--
-- The types defined here establish a two-phase verification and synchronization cycle:
--
-- * __Inspection Phase:__
--   The target workspace is audited without mutations ('Gachi.Sync.Audit.auditRepository').
--   Each asset is tested for presence, content equality against 'assetGenerator',
--   and executable bit validity, yielding an 'AssetAuditResult'.
--
-- * __Remediation Phase:__
--   Diagnostic reports ('AuditReport') guide interactive or automated synchronization
--   ('gachi sync') to resolve drift and install missing required infrastructure assets.
--
-- = When to Use This Module
--
-- Import this module when working with:
-- * The 9 standardization dimensions ('AssetCategory').
-- * Asset definitions and generators ('StandardAsset').
-- * The complete multi-dimension matrix ('StandardizationMatrix').
-- * Audit results and diagnostic reports ('AssetAuditStatus', 'AuditReport').
--
-- = Worked Example
--
-- >>> import Gachi.Schema.Project (defaultProject, Author(..))
-- >>> import Gachi.Sync.Types
-- >>> let auth = Author "Nadia Yvette Chambers" "nadia.yvette.chambers@ik.me"
-- >>> let proj = defaultProject "gongju" auth
-- >>> let asset = StandardAsset "bin/:name:-verify" CategoryVerification True True "Runner" (const "")
-- >>> resolveAssetPath proj asset
-- "bin/gongju-verify"
-- >>> categoryName CategoryFormatter
-- "Code Formatters"
module Gachi.Sync.Types
  ( -- * Standardization Dimensions & Categories
    AssetCategory(..)
  , categoryName
  , categoryDescription

    -- * Standard Asset Models
  , StandardAsset(..)
  , resolveAssetPath

    -- * Standardization Matrix
  , StandardizationMatrix(..)

    -- * Audit Results & Statuses
  , AssetAuditStatus(..)
  , AssetAuditResult(..)
  , isCompliant
  , isDrift
  , isMissing

    -- * Diagnostic Reporting
  , AuditReport(..)

    -- * Operational Synchronization Modes
  , SyncMode(..)
  ) where

import Data.Text (Text)
import qualified Data.Text as T
import GHC.Generics (Generic)
import Gachi.Schema.Project (Project, projectName)

--------------------------------------------------------------------------------
-- 1. Standardization Dimensions & Categories
--------------------------------------------------------------------------------

-- | The nine foundational standardization dimensions governing ecosystem repositories.
--
-- Defined in ~/src/PLAN.md §1.A to ensure uniform developer ergonomics,
-- reproducible builds, and automated AI pair programming context across all checkouts.
data AssetCategory
  = CategoryLayout
    -- ^ Dimension 1: Workspace and directory hierarchy (packages/, docs/, bin/, PLAN.md, STATE.md).
  | CategoryFormatter
    -- ^ Dimension 2: Code formatting profiles (fourmolu.yaml, rustfmt.toml, .clang-format).
  | CategoryLinter
    -- ^ Dimension 3: Static analysis guardrails (.hlint.yaml).
  | CategoryBuild
    -- ^ Dimension 4: Build system conventions (cabal.project, cabal.project.local.example).
  | CategoryGitGovernance
    -- ^ Dimension 5: Git governance, RFC 2822 commit templates, and hooks (.githooks/).
  | CategoryRemoteTopology
    -- ^ Dimension 6: Multi-forge replication remotes (rad, github, gitcode, framagit, disroot).
  | CategoryCIWorkflow
    -- ^ Dimension 7: Forge-tiered CI resource allocation (.github/workflows/, .gitlab-ci.yml).
  | CategoryPolyglot
    -- ^ Dimension 8: Multi-language FFI boundary invariants and cross-language tests.
  | CategoryAgentAdvisory
    -- ^ Dimension 9: Agent orientation pointers (AGENTS.md), symlinks, rules, and skills.
  | CategoryVerification
    -- ^ Physical verification harnesses and non-negotiable verification gates (bin/*-verify).
  deriving stock (Eq, Show, Ord, Enum, Bounded, Generic)

-- | Human-readable short name for an 'AssetCategory'.
--
-- Used in diagnostic audit tables and status report banners.
categoryName :: AssetCategory -> Text
categoryName CategoryLayout         = "Workspace Layout"
categoryName CategoryFormatter      = "Code Formatters"
categoryName CategoryLinter         = "Linters & Guardrails"
categoryName CategoryBuild          = "Build Conventions"
categoryName CategoryGitGovernance  = "Git Governance"
categoryName CategoryRemoteTopology = "Remote Topology"
categoryName CategoryCIWorkflow     = "Tiered CI Workflows"
categoryName CategoryPolyglot       = "Polyglot Invariants"
categoryName CategoryAgentAdvisory  = "Agent Advisory Layer"
categoryName CategoryVerification   = "Physical Verification"

-- | Detailed synopsis of what an 'AssetCategory' governs.
--
-- Explains the architectural responsibilities and scope of each dimension.
categoryDescription :: AssetCategory -> Text
categoryDescription CategoryLayout =
  "Multi-package directory hierarchy, central docs/ corpus, and two-file planning loop."
categoryDescription CategoryFormatter =
  "Deterministic code formatting profiles across Haskell (Fourmolu), Rust, and C/C++."
categoryDescription CategoryLinter =
  "Static analysis rules enforcing zero-hint clean compiler and linter policies."
categoryDescription CategoryBuild =
  "Hermetic toolchain pinning (GHC 9.14.1 / Cabal 3.18) and local override patterns."
categoryDescription CategoryGitGovernance =
  "RFC 2822 commit message trailers (Assisted-by:) and pre-commit gate hooks."
categoryDescription CategoryRemoteTopology =
  "Multi-forge mirroring separating Radicle p2p from commercial and community forges."
categoryDescription CategoryCIWorkflow =
  "Asymmetric forge resource allocation: heavy GitHub Actions vs lightweight community CI."
categoryDescription CategoryPolyglot =
  "ABI stability, FFI boundary pinning, and cross-language integration test runners."
categoryDescription CategoryAgentAdvisory =
  "Context-budgeted AGENTS.md (~4 KB pointer), compatibility symlinks, and modular rules."
categoryDescription CategoryVerification =
  "Turnkey verification scripts enforcing the 5 physical gates across every repository."

--------------------------------------------------------------------------------
-- 2. Standard Asset Models
--------------------------------------------------------------------------------

-- | Declarative specification for an individual standardized asset in the ecosystem.
--
-- Each asset defines its relative file path, category dimension, permission requirements,
-- and a pure generator synthesizing its canonical content from a 'Project' specification.
data StandardAsset = StandardAsset
  { assetPath        :: !FilePath
    -- ^ Relative file path within the workspace root (e.g. @".hlint.yaml"@ or @"bin/:name:-verify"@).
  , assetCategory    :: !AssetCategory
    -- ^ Standardization dimension governing this asset.
  , assetRequired    :: !Bool
    -- ^ Whether absence of this file constitutes an audit violation.
  , assetExecutable  :: !Bool
    -- ^ Whether POSIX executable permissions (@+x@) must be applied.
  , assetDescription :: !Text
    -- ^ Human-readable explanation of asset purpose and architectural role.
  , assetGenerator   :: !(Project -> Text)
    -- ^ Pure generator synthesizing canonical file content for the given project.
  } deriving stock (Generic)

instance Show StandardAsset where
  showsPrec d a = showParen (d > 10) $
    showString "StandardAsset { assetPath = " . shows (assetPath a)
    . showString ", assetCategory = " . shows (assetCategory a)
    . showString ", assetRequired = " . shows (assetRequired a)
    . showString ", assetExecutable = " . shows (assetExecutable a)
    . showString ", assetDescription = " . shows (assetDescription a)
    . showString " }"

instance Eq StandardAsset where
  a == b = assetPath a == assetPath b
        && assetCategory a == assetCategory b
        && assetRequired a == assetRequired b
        && assetExecutable a == assetExecutable b

-- | Resolves the concrete filesystem relative path of an asset for a target project.
--
-- Replaces template tokens such as @":name:"@ and @":project:"@ with the project's
-- declared 'projectName'. For static paths (e.g. @".hlint.yaml"@), returns the
-- path unmodified.
--
-- === Worked Example
--
-- >>> import Gachi.Schema.Project (defaultProject, Author(..))
-- >>> let proj = defaultProject "gongju" (Author "Nadia" "nadia@example.com")
-- >>> let asset = StandardAsset "bin/:name:-verify" CategoryVerification True True "Verify script" (const "")
-- >>> resolveAssetPath proj asset
-- "bin/gongju-verify"
resolveAssetPath :: Project -> StandardAsset -> FilePath
resolveAssetPath proj asset =
  let pName = T.unpack (projectName proj)
      raw = assetPath asset
      subst [] = []
      subst str@(c:cs)
        | take 6 str == ":name:"    = pName ++ subst (drop 6 str)
        | take 9 str == ":project:" = pName ++ subst (drop 9 str)
        | otherwise                 = c : subst cs
  in subst raw

--------------------------------------------------------------------------------
-- 3. Standardization Matrix
--------------------------------------------------------------------------------

-- | Composite Standardization Matrix aggregating all registered standard assets.
--
-- Encapsulates the entire ecosystem contract: which dimensions are active,
-- which assets belong to each dimension, and how their contents are generated.
--
-- === Structural Invariants & Properties
--
-- 1. __Dimension Congruence:__ Every declared dimension in 'matrixDimensions' is
--    represented by at least one asset in 'matrixAssets'.
-- 2. __Unique Target Paths:__ Within a concrete project instantiation, no two
--    assets in 'matrixAssets' resolve to the same relative file path.
-- 3. __Pure Hermetic Generation:__ Every asset's 'assetGenerator' is a pure, total
--    function evaluated strictly from the immutable 'Project' record.
-- 4. __Static Analysis Friendly:__ The matrix can be audited, filtered by category,
--    queried by path, or serialized without accessing ambient filesystem state.
data StandardizationMatrix = StandardizationMatrix
  { matrixAssets     :: ![StandardAsset]
    -- ^ Complete list of registered standard asset specifications.
  , matrixDimensions :: ![AssetCategory]
    -- ^ Active dimensions enforced by this matrix instance.
  } deriving stock (Generic)

--------------------------------------------------------------------------------
-- 4. Audit Results & Statuses
--------------------------------------------------------------------------------

-- | Outcome of auditing an individual asset against the physical filesystem.
--
-- The audit engine classifies every asset into exactly one of these five statuses:
--
-- 1. 'StatusCompliant':
--    File exists on disk, content matches expected generator output, and executable
--    bits conform to 'assetExecutable'.
--
-- 2. 'StatusMissing':
--    File is completely absent from the target directory on disk.
--
-- 3. 'StatusContentDrift':
--    File exists, but its text diverges from canonical generator output.
--    Contains actual character count and expected character count.
--
-- 4. 'StatusPermissionDrift':
--    File exists and content matches, but its executable bit differs from
--    expected (e.g. executable script lacking @+x@ permissions).
--
-- 5. 'StatusIOError':
--    Filesystem I/O exception occurred while attempting to inspect the file.
data AssetAuditStatus
  = StatusCompliant
    -- ^ File exists, textual content matches canonical generator, and permissions are valid.
  | StatusMissing
    -- ^ File is missing on disk.
  | StatusContentDrift !Int !Int
    -- ^ File exists but content diverges (actual character length vs expected length).
  | StatusPermissionDrift !Bool !Bool
    -- ^ File exists and matches content, but executable bits differ (actual vs expected).
  | StatusIOError !Text
    -- ^ Filesystem I/O exception occurred while attempting to inspect the file.
  deriving stock (Eq, Show, Generic)

-- | Helper checking if an audit status represents full compliance.
--
-- Returns 'True' exclusively for 'StatusCompliant'.
isCompliant :: AssetAuditStatus -> Bool
isCompliant StatusCompliant = True
isCompliant _               = False

-- | Helper checking if an audit status represents content or permission drift.
--
-- Returns 'True' for 'StatusContentDrift' and 'StatusPermissionDrift'.
isDrift :: AssetAuditStatus -> Bool
isDrift (StatusContentDrift _ _)    = True
isDrift (StatusPermissionDrift _ _) = True
isDrift _                           = False

-- | Helper checking if an audit status represents a missing file.
--
-- Returns 'True' exclusively for 'StatusMissing'.
isMissing :: AssetAuditStatus -> Bool
isMissing StatusMissing = True
isMissing _             = False

-- | Paired audit result combining an asset specification with its observed status.
--
-- Represents the atomic finding for a single asset evaluated by the audit engine.
-- Pairs the structural contract ('resultAsset') with the empirical status observed
-- on disk ('resultStatus').
--
-- === Worked Example
--
-- >>> import Gachi.Sync.Types
-- >>> let asset = StandardAsset ".hlint.yaml" CategoryLinter True False "HLint" (const "")
-- >>> let res = AssetAuditResult asset StatusCompliant
-- >>> isCompliant (resultStatus res)
-- === Invariants & Verification Semantics
--
-- * An asset result is immutable once constructed.
-- * Predicates like 'isCompliant', 'isDrift', and 'isMissing' project directly
--   from 'resultStatus'.
-- * Audit reports aggregate lists of 'AssetAuditResult' to render tabular diagnostics.
data AssetAuditResult = AssetAuditResult
  { resultAsset  :: !StandardAsset
    -- ^ The standardized asset specification audited.
  , resultStatus :: !AssetAuditStatus
    -- ^ Observed compliance or discrepancy status on disk.
  } deriving stock (Eq, Show, Generic)

--------------------------------------------------------------------------------
-- 5. Diagnostic Reporting
--------------------------------------------------------------------------------

-- | Comprehensive diagnostic summary of a repository audit across the matrix.
--
-- Summarizes aggregate compliance statistics, lists all individual asset audit
-- outcomes, and computes the overall compliance percentage.
--
-- === Diagnostic Metrics & Mathematical Guarantees
--
-- 1. __Exact Asset Partitioning:__
--    The evaluated outcomes in 'reportResults' partition the universe of standard
--    assets:
--    \[
--      \text{length}(\text{reportResults}) = \text{reportTotalAssets}
--    \]
-- 2. __Compliance Ratio Calculation:__
--    The compliance percentage is bounded in \([0.0, 100.0]\):
--    \[
--      \text{reportCompliancePct} = \frac{\text{reportCompliantCount}}{\text{reportTotalAssets}} \times 100.0
--    \]
-- 3. __Observational Safety:__
--    Audits are read-only and non-destructive. Computing an 'AuditReport' guarantees
--    zero filesystem mutations, zero file deletions, and zero timestamp alterations.
data AuditReport = AuditReport
  { reportTargetDir      :: !FilePath
    -- ^ Target repository filesystem path audited.
  , reportProjectName    :: !Text
    -- ^ Name of the audited project specification.
  , reportResults        :: ![AssetAuditResult]
    -- ^ Detailed per-asset audit outcomes.
  , reportTotalAssets    :: !Int
    -- ^ Total number of matrix assets evaluated.
  , reportCompliantCount :: !Int
    -- ^ Number of assets achieving full compliance.
  , reportDriftCount     :: !Int
    -- ^ Number of existing assets with content or permission drift.
  , reportMissingCount   :: !Int
    -- ^ Number of required assets missing from the filesystem.
  , reportCompliancePct  :: !Double
    -- ^ Overall compliance percentage (0.0% to 100.0%).
  } deriving stock (Eq, Show, Generic)

--------------------------------------------------------------------------------
-- 5. Operational Synchronization Modes
--------------------------------------------------------------------------------

-- | Operational mode controlling filesystem mutations during synchronization.
--
-- Governs whether synchronization plans are simulated as read-only dry runs or
-- executed as atomic mutations on disk.
data SyncMode
  = DryRun
    -- ^ Inspect discrepancies and compute the 'SyncPlan', but perform no disk mutations.
  | ApplySync
    -- ^ Apply all planned changes: create missing files, update content, and fix permissions.
  deriving stock (Eq, Show, Ord, Enum, Bounded, Generic)