Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ jobs:
# token). Migrate to project-scoped tokens once the projects exist on PyPI.
#
# Sequenced via `needs:` because darnit-baseline, darnit-gittuf,
# darnit-reproducibility, and darnit-mcp declare `darnit-core>=...` runtime
# darnit-amber, and darnit-mcp declare `darnit-core>=...` runtime
# deps; darnit-mcp additionally depends on the other four. Publishing them
# before the deps are on the index briefly produces unresolvable wheels.

Expand Down Expand Up @@ -289,8 +289,8 @@ jobs:
password: ${{ secrets.PYPI_API_TOKEN }}
skip-existing: true

publish-darnit-reproducibility:
name: Publish darnit-reproducibility to PyPI
publish-darnit-amber:
name: Publish darnit-amber to PyPI
needs: [preflight, build, publish-darnit-core]
runs-on: ubuntu-latest
timeout-minutes: 10
Expand All @@ -303,10 +303,10 @@ jobs:
name: dist
path: dist/

- name: Publish darnit-reproducibility
- name: Publish darnit-amber
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
with:
packages-dir: dist/darnit-reproducibility/
packages-dir: dist/darnit-amber/
password: ${{ secrets.PYPI_API_TOKEN }}
skip-existing: true

Expand All @@ -317,7 +317,7 @@ jobs:
- build
- publish-darnit-baseline
- publish-darnit-gittuf
- publish-darnit-reproducibility
- publish-darnit-amber
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
Expand Down Expand Up @@ -482,7 +482,7 @@ jobs:
## Install

- **MCP (Claude Code / Claude Desktop):** \`darnit install\` writes an \`uvx --from darnit-mcp\` config for you. See [docs/install/](https://github.com/${GITHUB_REPOSITORY}/tree/${TAG}/docs/install).
- **PyPI:** \`pip install darnit-mcp==${VERSION}\` (installs the meta-package plus \`darnit-core\`, \`darnit-baseline\`, \`darnit-gittuf\`, \`darnit-reproducibility\`).
- **PyPI:** \`pip install darnit-mcp==${VERSION}\` (installs the meta-package plus \`darnit-core\`, \`darnit-baseline\`, \`darnit-gittuf\`, \`darnit-amber\`).
- **Container:** \`docker pull ghcr.io/${owner_lc}/darnit:${TAG}\`.
- **Standalone binary / Homebrew:** not yet in this release. Tracked for follow-ups.

Expand Down Expand Up @@ -512,7 +512,7 @@ jobs:
- publish-darnit-core
- publish-darnit-baseline
- publish-darnit-gittuf
- publish-darnit-reproducibility
- publish-darnit-amber
- publish-darnit-mcp
- container_build_push
- release
Expand All @@ -527,7 +527,7 @@ jobs:
PYPI_CORE: ${{ needs.publish-darnit-core.result }}
PYPI_BASELINE: ${{ needs.publish-darnit-baseline.result }}
PYPI_GITTUF: ${{ needs.publish-darnit-gittuf.result }}
PYPI_REPRO: ${{ needs.publish-darnit-reproducibility.result }}
PYPI_REPRO: ${{ needs.publish-darnit-amber.result }}
PYPI_MCP: ${{ needs.publish-darnit-mcp.result }}
CONTAINER: ${{ needs.container_build_push.result }}
RELEASE: ${{ needs.release.result }}
Expand All @@ -540,7 +540,7 @@ jobs:
echo "| pypi-darnit-core | $PYPI_CORE |"
echo "| pypi-darnit-baseline | $PYPI_BASELINE |"
echo "| pypi-darnit-gittuf | $PYPI_GITTUF |"
echo "| pypi-darnit-reproducibility | $PYPI_REPRO |"
echo "| pypi-darnit-amber | $PYPI_REPRO |"
echo "| pypi-darnit-mcp | $PYPI_MCP |"
echo "| container | $CONTAINER |"
echo "| github-release | $RELEASE |"
Expand Down
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ my-framework = "my_framework:register"

### Framework config resolution (feature 021)

`get_framework_config_path()` MUST use `importlib.resources.files(__package__) / "<framework>.toml"` -- never `Path(__file__).parent.parent...`. The framework TOML MUST live INSIDE `src/<module>/` (alongside `implementation.py`), not sibling to `src/`, so hatchling's default `packages = ["src/<module>"]` includes it in the wheel and `importlib.resources` finds it under both editable and wheel installs. See `packages/darnit-baseline`, `packages/darnit-gittuf`, `packages/darnit-reproducibility` for reference layouts; `packages/darnit-hello` is the minimal template.
`get_framework_config_path()` MUST use `importlib.resources.files(__package__) / "<framework>.toml"` -- never `Path(__file__).parent.parent...`. The framework TOML MUST live INSIDE `src/<module>/` (alongside `implementation.py`), not sibling to `src/`, so hatchling's default `packages = ["src/<module>"]` includes it in the wheel and `importlib.resources` finds it under both editable and wheel installs. See `packages/darnit-baseline`, `packages/darnit-gittuf`, `packages/darnit-amber` for reference layouts; `packages/darnit-hello` is the minimal template.

## Sieve Pattern

Expand Down Expand Up @@ -365,7 +365,7 @@ else:
## Active Technologies
- Python 3.11/3.12 (workspace targets) plus bash for release scripts and GitHub Actions YAML + `shiv` (binary builder), `cosign` (image + binary signing), `syft` (SBOM generation), `docker buildx` (multi-arch images), `gh` CLI (release creation), Sigstore-action (PyPI wheel signing via `pypa/gh-action-pypi-publish`). No new runtime dependencies in any darnit Python package. (012-packaging-distribution)
- External release surfaces only — PyPI, TestPyPI, GHCR, GitHub Releases (binary assets + attestations), `kusari-oss/homebrew-tap` repo (formula). Repo itself stores only build configs and workflow definitions. (012-packaging-distribution)
- Python 3.11/3.12 (workspace targets — same as the rest of darnit) + `pydantic >= 2.0` (already used for `FrameworkConfig`); `packaging` for PEP 440 `SpecifierSet` (declared by `darnit-reproducibility` as of feature 037; darnit-core imports it at runtime without declaring it, which is tracked separately). `tomllib` from stdlib for TOML parsing. No new runtime dependencies. (013-plugin-composition)
- Python 3.11/3.12 (workspace targets — same as the rest of darnit) + `pydantic >= 2.0` (already used for `FrameworkConfig`); `packaging` for PEP 440 `SpecifierSet` (declared by `darnit-amber` as of feature 037; darnit-core imports it at runtime without declaring it, which is tracked separately). `tomllib` from stdlib for TOML parsing. No new runtime dependencies. (013-plugin-composition)
- Filesystem only. Composition is resolved in-memory at framework-config load time; no new persistent state. (013-plugin-composition)

## Recent Changes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Executive Summary

This document describes a system design for **darnit-reproducibility**, an extension to the darnit compliance framework that enables automated reproducibility verification and attestation for computational research software. The system addresses a critical gap in scientific computing: the inability to reliably rebuild and re-execute research software (e.g., protein folding simulations, molecular dynamics, genomics pipelines) in deterministic ways.
This document describes a system design for **darnit-amber**, an extension to the darnit compliance framework that enables automated reproducibility verification and attestation for computational research software. The system addresses a critical gap in scientific computing: the inability to reliably rebuild and re-execute research software (e.g., protein folding simulations, molecular dynamics, genomics pipelines) in deterministic ways.

The system operates in two primary flows:

Expand Down Expand Up @@ -256,7 +256,7 @@ mutation {
hasMetadata: {
key: "https://darnit.dev/attestations/reproducibility/v1",
value: "{...attestation JSON...}",
origin: "darnit-reproducibility",
origin: "darnit-amber",
collector: "darnit"
}
)
Expand Down Expand Up @@ -514,7 +514,7 @@ witness_attestation_bundle:
#### Ingestor Implementation

```python
# packages/darnit-reproducibility/src/darnit_reproducibility/ingest/witness.py
# packages/darnit-amber/src/darnit_amber/ingest/witness.py

from dataclasses import dataclass
from typing import Optional, List, Dict, Any
Expand Down Expand Up @@ -580,7 +580,7 @@ The Provenance Analyzer processes Witness data to understand the complete depend
#### Analysis Pipeline

```python
# packages/darnit-reproducibility/src/darnit_reproducibility/analysis/provenance.py
# packages/darnit-amber/src/darnit_amber/analysis/provenance.py

from dataclasses import dataclass, field
from typing import List, Dict, Set, Optional
Expand Down Expand Up @@ -745,7 +745,7 @@ Generates environment definitions based on the provenance analysis.
#### Generator Interface

```python
# packages/darnit-reproducibility/src/darnit_reproducibility/generate/base.py
# packages/darnit-amber/src/darnit_amber/generate/base.py

from abc import ABC, abstractmethod
from dataclasses import dataclass
Expand Down Expand Up @@ -784,7 +784,7 @@ class EnvironmentGenerator(ABC):
#### Container Generator (Docker/Singularity)

```python
# packages/darnit-reproducibility/src/darnit_reproducibility/generate/container.py
# packages/darnit-amber/src/darnit_amber/generate/container.py

class ContainerGenerator(EnvironmentGenerator):
"""Generates Dockerfile or Singularity definition files."""
Expand Down Expand Up @@ -842,7 +842,7 @@ class ContainerGenerator(EnvironmentGenerator):
hardware: HardwareRequirements
) -> str:
lines = [
f"# Auto-generated by darnit-reproducibility",
f"# Auto-generated by darnit-amber",
f"# Provenance: {self._provenance_comment()}",
f"FROM {base_image}",
"",
Expand Down Expand Up @@ -904,7 +904,7 @@ class ContainerGenerator(EnvironmentGenerator):
#### HPC Generator (Slurm/PBS)

```python
# packages/darnit-reproducibility/src/darnit_reproducibility/generate/hpc.py
# packages/darnit-amber/src/darnit_amber/generate/hpc.py

class HPCGenerator(EnvironmentGenerator):
"""Generates HPC job scripts and environment modules."""
Expand Down Expand Up @@ -993,7 +993,7 @@ class HPCGenerator(EnvironmentGenerator):
#### Nix Generator

```python
# packages/darnit-reproducibility/src/darnit_reproducibility/generate/nix.py
# packages/darnit-amber/src/darnit_amber/generate/nix.py

class NixGenerator(EnvironmentGenerator):
"""Generates Nix derivations for maximum reproducibility."""
Expand Down Expand Up @@ -1031,7 +1031,7 @@ class NixGenerator(EnvironmentGenerator):
nix_deps: List["NixPackage"]
) -> str:
return f'''{{
description = "Reproducible environment generated by darnit-reproducibility";
description = "Reproducible environment generated by darnit-amber";

inputs = {{
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
Expand Down Expand Up @@ -1086,7 +1086,7 @@ class NixGenerator(EnvironmentGenerator):
Executes builds in generated environments with Witness tracing.

```python
# packages/darnit-reproducibility/src/darnit_reproducibility/execute/engine.py
# packages/darnit-amber/src/darnit_amber/execute/engine.py

from dataclasses import dataclass
from typing import Optional, List
Expand Down Expand Up @@ -1224,7 +1224,7 @@ class ExecutionEngine:
Generates reproducibility attestations in in-toto format.

```python
# packages/darnit-reproducibility/src/darnit_reproducibility/attestation/reproducibility.py
# packages/darnit-amber/src/darnit_amber/attestation/reproducibility.py

from dataclasses import dataclass, field
from typing import List, Dict, Any, Optional
Expand Down Expand Up @@ -1296,7 +1296,7 @@ class ReproducibilityAttestationGenerator:
"predicateType": predicate.predicate_type,
"predicate": {
"assessor": {
"name": "darnit-reproducibility",
"name": "darnit-amber",
"version": self.config.version,
"timestamp": datetime.utcnow().isoformat() + "Z"
},
Expand Down Expand Up @@ -1403,7 +1403,7 @@ class ReproducibilityAttestationGenerator:
Publishes attestations to OpenSSF GUAC.

```python
# packages/darnit-reproducibility/src/darnit_reproducibility/publish/guac.py
# packages/darnit-amber/src/darnit_amber/publish/guac.py

from dataclasses import dataclass
from typing import Optional, List, Dict, Any
Expand Down Expand Up @@ -1554,7 +1554,7 @@ class GUACPublisher:
"value": json.dumps(attestation["predicate"]),
"timestamp": attestation["predicate"]["assessor"]["timestamp"],
"justification": "Reproducibility attestation from darnit",
"origin": "darnit-reproducibility",
"origin": "darnit-amber",
"collector": "darnit"
}
}
Expand Down Expand Up @@ -1632,7 +1632,7 @@ class GUACPublisher:
### Tool Definitions

```python
# packages/darnit-reproducibility/src/darnit_reproducibility/server/tools.py
# packages/darnit-amber/src/darnit_amber/server/tools.py

REPRODUCIBILITY_TOOLS = [
{
Expand Down Expand Up @@ -1874,12 +1874,12 @@ reproducibility:
### Plugin Registration

```python
# packages/darnit-reproducibility/src/darnit_reproducibility/__init__.py
# packages/darnit-amber/src/darnit_amber/__init__.py

from darnit.core.plugin import ComplianceImplementation

def register() -> ComplianceImplementation:
"""Register darnit-reproducibility as a compliance implementation."""
"""Register darnit-amber as a compliance implementation."""
return ReproducibilityImplementation()

class ReproducibilityImplementation:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# darnit-reproducibility
# darnit-amber

Scientific reproducibility checks plugin for darnit.

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[project]
name = "darnit-reproducibility"
name = "darnit-amber"
version = "0.1.0"
description = "Scientific reproducibility checks plugin for darnit"
readme = "README.md"
Expand All @@ -15,16 +15,16 @@ dependencies = [
]

[project.entry-points."darnit.implementations"]
reproducibility = "darnit_reproducibility:register"
amber = "darnit_amber:register"

[project.entry-points."darnit.frameworks"]
reproducibility = "darnit_reproducibility:get_framework_path"
amber = "darnit_amber:get_framework_path"

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.build.targets.wheel]
packages = ["src/darnit_reproducibility"]
# reproducibility.toml lives at src/darnit_reproducibility/reproducibility.toml
packages = ["src/darnit_amber"]
# amber.toml lives at src/darnit_amber/amber.toml
# (feature 021) and is packaged automatically by the entry above.
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

from pathlib import Path

from .implementation import ReproducibilityImplementation
from .implementation import AmberImplementation


def register() -> ReproducibilityImplementation:
def register() -> AmberImplementation:
"""Entry point called by darnit plugin discovery."""
impl = ReproducibilityImplementation()
impl = AmberImplementation()
impl.register_controls()
impl.register_sieve_handlers()
return impl
Expand All @@ -19,7 +19,7 @@ def get_framework_path() -> Path:
# and name lookup. The 'darnit.implementations' get_framework_config_path()
# feeds the audit path instead; both entry points are required. Delegating
# here ensures both paths use the same importlib.resources resolver.
return ReproducibilityImplementation().get_framework_config_path()
return AmberImplementation().get_framework_config_path()


__all__ = ["ReproducibilityImplementation", "register", "get_framework_path"]
__all__ = ["AmberImplementation", "register", "get_framework_path"]
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
[metadata]
schema_version = "0.1.0-alpha"
name = "reproducibility"
display_name = "Scientific Reproducibility Checks"
name = "amber"
display_name = "Amber — Scientific Reproducibility Checks"
version = "0.1.0"
spec_version = "0.1.0"
description = "Checks that builds are reproducible and verifiable"

[mcp]
name = "reproducibility"
name = "amber"
description = "Run scientific reproducibility checks on a repository"

[mcp.tools.audit_reproducibility]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

from .witness_attestation import WitnessCheckResult, check_witness_attestation

logger = get_logger("darnit_reproducibility.handlers")
logger = get_logger("darnit_amber.handlers")


_MAX_EVIDENCE_EXAMPLES = 10
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
from typing import Any

from darnit.core.plugin import ControlSpec
from darnit_reproducibility import handlers
from darnit_amber import handlers


class ReproducibilityImplementation:
class AmberImplementation:
"""Scientific reproducibility checks plugin.

Provides checks for dependency pinning, build environment
Expand All @@ -17,11 +17,11 @@ class ReproducibilityImplementation:

@property
def name(self) -> str:
return "reproducibility"
return "amber"

@property
def display_name(self) -> str:
return "Scientific Reproducibility Checks"
return "Amber — Scientific Reproducibility Checks"

@property
def version(self) -> str:
Expand Down Expand Up @@ -93,11 +93,11 @@ def get_remediation_registry(self) -> dict[str, Any]:
def get_framework_config_path(self) -> Path | None:
from importlib.resources import files

resource = files(__package__) / "reproducibility.toml"
resource = files(__package__) / "amber.toml"
path = Path(str(resource))
if not path.is_file():
raise FileNotFoundError(
f"reproducibility.toml not found in installed darnit_reproducibility "
f"amber.toml not found in installed darnit_amber "
f"package at {path}. This indicates a broken build; check the "
f"wheel's force-include configuration."
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
from darnit.core.logging import get_logger
from darnit.sieve.handler_registry import HandlerContext

logger = get_logger("darnit_reproducibility.witness_attestation")
logger = get_logger("darnit_amber.witness_attestation")

try:
from sigstore.models import Bundle
Expand Down
Loading
Loading