image:https://img.shields.io/badge/License-MPL--2.0-blue.svg[License: PMPL-1.0,link="https://github.com/hyperpolymath/palimpsest-license"]
image:https://img.shields.io/badge/Philosophy-Palimpsest-indigo.svg[Palimpsest,link="https://github.com/hyperpolymath/palimpsest-license"]


// SPDX-License-Identifier: CC-BY-SA-4.0
= Spindle




:toc: macro
:toclevels: 3
:icons: font
:source-highlighter: rouge

[.lead]
*A Haskell library for parsing Nickel configuration files into type-safe data structures.*

[cols="1,3"]
|===
|*Project Name* |Spindle
|*What It Does* |Parses https://nickel-lang.org/[Nickel] configs → Haskell types via Aeson/JSON
|*Language* |Haskell (GHC 9.6+)
|*License* |PMPL-1.0
|*Status* |Early Development (~15% complete)
|===

[WARNING]
====
*Why is this repo called `ada-loom-registry`?*

This repository was created under a placeholder name before the project scope was defined. The actual project is *Spindle*, a Haskell-Nickel configuration parser.

* *NOT* an Ada programming language project
* *NOT* a textile/loom/weaving project
* *NOT* a package registry (it has a _config_ registry, not a _package_ registry)

The repository will be renamed to `spindle` or `spindle-nickel` in a future release. See <<repository-rename>> for details.
====

toc::[]

== Overview

Spindle is a Haskell-based tool for parsing, evaluating, and converting https://nickel-lang.org/[Nickel] configuration files into type-safe Haskell data structures. It bridges the gap between Nickel's powerful contract-based configuration language and Haskell's type system.

=== What Spindle Does

[source]
----
 .ncl file    -->   Nickel Parser   -->   Evaluate   -->   JSON   -->   Haskell Types
 (config.ncl)      (hnickel lib)        (contracts)      (Aeson)      (Config record)
----

=== Key Features

* *Type-Safe Configs* -- Nickel contracts + Haskell types = double validation
* *Native Nickel* -- Uses `hnickel` for direct Nickel language integration
* *JSON Bridge* -- Aeson-based conversion for interoperability
* *Config Registry* -- Store and manage validated configurations
* *WASM-Ready* -- GHC 9.6.3 supports `wasm32-wasi` compilation target

== Quick Start

=== Prerequisites

* GHC 9.6.3+ (or 9.12.2 for latest features)
* Cabal 2.2+
* Nickel runtime (for config validation)

=== Build and Run

[source,bash]
----
# Build
cabal build

# Run with a Nickel config
cabal run spindle -- config.ncl

# Example output
# Successfully parsed config: Config {name = "spindle-from-nickel", version = "2.0.0"}
----

=== Example Configuration

.config.ncl
[source,nickel]
----
{
  name = "spindle-from-nickel",
  version = "2.0.0",
}
----

== Registry

Spindle includes a minimal registry for storing and managing validated Nickel configurations.

=== Registry Commands

[source,bash]
----
# Register a validated config
spindle register config.ncl
# Output: Registered: spindle-from-nickel v2.0.0

# List all registered configs
spindle list
# Output:
# Registered configs:
# --------------------------------------------------
# spindle-from-nickel v2.0.0 (config.ncl)

# Get details of a specific config
spindle get spindle-from-nickel
# Output:
# Name:       spindle-from-nickel
# Version:    2.0.0
# Source:     config.ncl
# Registered: 2025-01-15 10:30:00 UTC

# Remove a config from the registry
spindle remove spindle-from-nickel
# Output: Removed: spindle-from-nickel
----

=== Registry Storage

The registry is stored locally in `.spindle/registry.json`. This file is JSON-formatted and can be version-controlled if desired.

== Architecture

=== Core Pipeline

.app/Main.hs
[source,haskell]
----
-- 1. Parse the file into a Nickel expression
expr <- parseNickelFile filename content

-- 2. Evaluate the expression to get a Nickel value
evaluatedValue <- eval expr

-- 3. Convert the Nickel value to JSON
let jsonValue = nvalueToJSON evaluatedValue

-- 4. Decode JSON into Haskell types via Aeson
eitherDecodeStrict' jsonValue
----

=== Type Definition

[source,haskell]
----
data Config = Config
  { name    :: Text
  , version :: Text
  }
  deriving (Show, Generic, FromJSON)
----

=== Project Structure

[source]
----
spindle/                        # (current repo: ada-loom-registry)
+-- app/
|   +-- Main.hs                 # CLI entry point
+-- src/
|   +-- Spindle.hs              # Public library API
|   +-- Spindle/
|       +-- Registry.hs         # Config registry module
+-- config/
|   +-- build.ncl               # Build configuration
|   +-- ci.ncl                  # CI/CD pipeline config
|   +-- dev.ncl                 # Development settings
|   +-- deploy.ncl              # Deployment configuration
+-- config.ncl                  # Example Nickel config
+-- spindle.cabal               # Cabal package definition
+-- docs/
|   +-- adr/                    # Architecture Decision Records
+-- .spindle/                   # Local registry storage (gitignored)
    +-- registry.json           # Registered configs
----

== Configuration System

Spindle uses Nickel for its own configuration, demonstrating "eating your own dog food":

=== Build Configuration

.config/build.ncl (excerpt)
[source,nickel]
----
{
  project = {
    name = "spindle",
    version = "0.1.0",
    license = "PMPL-1.0",
  },

  haskell = {
    version = "9.4.8",
    compiler = "ghc",
    build_tool = 'cabal,

    settings = {
      ghc_options = ["-Wall", "-Wcompat", "-Widentities", ...],
      language_extensions = ["OverloadedStrings", "DeriveGeneric"],
    },
  },

  nickel = {
    version = "1.4.0",
    use_for = ["parsing", "validation", "testing"],
  },
}
----

=== CI Configuration

.config/ci.ncl (excerpt)
[source,nickel]
----
{
  pipeline = { name = "spindle-ci", version = "1.0.0" },

  gitlab = {
    stages = ["build", "test", "quality", "security", "docs", "deploy"],
  },

  test = {
    unit = { enabled = true, command = "cabal test --enable-coverage" },
    nickel_integration = { enabled = true, command = "cabal test nickel-integration" },
  },
}
----

== Dependencies

=== Haskell Libraries

[cols="1,3"]
|===
|Package |Purpose

|`base` (>=4.7 && <5)
|Standard library

|`hnickel`
|Nickel language integration (parser, evaluator)

|`aeson`
|JSON encoding/decoding

|`text`
|Efficient text handling

|`bytestring`
|Binary data / file reading
|===

=== Planned Additions

* `optparse-applicative` -- CLI argument parsing
* `filepath` / `directory` -- File system operations
* `hspec` / `QuickCheck` -- Testing framework

== WASM Compilation

Spindle targets WASM deployment via GHC's native `wasm32-wasi` backend:

[source,bash]
----
# Compile to WASM
cabal build --with-ghc wasm32-wasi-ghc

# Run in WASM runtime (wasmtime, wasmer, etc.)
wasmtime spindle.wasm config.ncl
----

*Status:* Compile-tested; deployment deferred pending WASM runtime evaluation and Nickel FFI boundary testing.

See: link:WASM_STATUS.adoc[WASM_STATUS.adoc]

== Compliance

This project follows *Rhodium Standard 0.5* targeting *Silver* level.

=== Current Status

[cols="1,1,2"]
|===
|Pillar |Level |Notes

|Audit Grade
|Bronze
|SLSA attestation planned

|Narratable
|Silver
|Documentation complete

|Interoperable
|Bronze
|WASM + Nickel FFI testing pending

|Inclusive
|Bronze
|PMPL-1.0 + Palimpsest dual license
|===

=== Nickel Advantage

[NOTE]
====
This project *exceeds* Rhodium's typed configuration requirement by using Nickel natively as its configuration language, not just as a target format.
====

== Related Projects

[cols="1,2,1"]
|===
|Project |Relationship |Priority

|*scaffoldia*
|Potential merger -- both Haskell + Nickel
|HIGH

|*valence*
|Could use Spindle for Nickel config parsing
|MEDIUM
|===

=== Merger Consideration

There is a strong case for merging Spindle into `scaffoldia` as a Nickel parsing module:

* Both are Haskell projects
* Both integrate with Nickel
* Reduces maintenance overhead
* Spindle becomes a reusable library

== Development

=== Building

[source,bash]
----
cabal update
cabal build --enable-tests
----

=== Testing

[source,bash]
----
cabal test --test-show-details=direct all
----

=== Linting

[source,bash]
----
hlint app/
----

=== Using the Justfile

[source,bash]
----
just build          # Build project
just test           # Run tests
just lint           # Run hlint
----

== Documentation

|===
|Document |Purpose

|link:IDENTITY.adoc[IDENTITY.adoc]
|What this project is (and isn't) - explains the repo name

|link:ROADMAP.adoc[ROADMAP.adoc]
|Development phases and milestones

|link:PROJECT_STATUS.adoc[PROJECT_STATUS.adoc]
|Detailed status, blockers, implementation sequence

|link:docs/adr/[docs/adr/]
|Architecture Decision Records

|link:CONTRIBUTING.adoc[CONTRIBUTING.adoc]
|Contribution guidelines

|link:SECURITY.adoc[SECURITY.adoc]
|Security policy and considerations

|link:REVERSIBILITY.adoc[REVERSIBILITY.adoc]
|Rollback procedures
|===

[[repository-rename]]
== Repository Rename

This repository is currently named `ada-loom-registry` due to its origin as a placeholder project. A rename is planned:

[cols="1,2"]
|===
|*Current Name* |`ada-loom-registry`
|*Target Name* |`spindle` or `spindle-nickel`
|*Timeline* |Phase 1 (before first stable release)
|===

=== Migration Path

When the rename occurs:

1. GitHub will automatically redirect the old URL
2. All `git remote` URLs will need updating
3. The `spindle.cabal` homepage/bug-reports URLs will be updated
4. Hackage publication will use the new name

=== Why Not Rename Now?

* Existing CI/CD pipelines reference current name
* Pending decision on merge with `scaffoldia` project
* Want to ensure new name is permanent

== License

PMPL-1.0 (see link:LICENSE.txt[LICENSE.txt])

Additional terms: link:LICENSE-PALIMPSEST.txt[Palimpsest 0.8.0]

== Status

*Version:* 0.1.0.0 +
*Implementation:* Early development (~15% complete) +
*Documentation:* Complete (Rhodium Silver) +

See link:ROADMAP.adoc[ROADMAP.adoc] for development plans.

---

_Spindle -- Weaving Nickel configurations into Haskell types_


== Architecture

See link:TOPOLOGY.md[TOPOLOGY.md] for a visual architecture map and completion dashboard.
