This guide describes how to package a SpecFact module for registry publishing: validate structure, create a tarball and checksum, optionally sign the manifest, and automate publishing in the dedicated modules repository.

Modules docs handoff: this page remains in the core docs set as release-line overview content. Canonical bundle-specific deep guidance now lives in the canonical modules docs site, currently published at https://modules.specfact.io/.

Repository ownership

  • specfact-cli owns the lean runtime, bundled modules under src/specfact_cli/modules/, shared contracts, and CI that re-signs those bundles.
  • specfact-cli-modules owns official workflow bundle payloads, the marketplace registry/index.json, and publish automation for bundles shipped from that repo.

If you are publishing an official marketplace bundle, work from nold-ai/specfact-cli-modules.

Module structure

Your module must have:

  • A directory containing module-package.yaml (the manifest).
  • Manifest fields: name, version, commands (required). For marketplace distribution, use namespace/name (e.g. acme-corp/backlog-pro) and optional publisher, tier.

Recommended layout:

<module-dir>/
  module-package.yaml
  src/
    __init__.py
    commands.py   # or app.py, etc.

Exclude from the package: .git, __pycache__, tests, .pytest_cache, *.pyc, *.pyo.

Script: publish-module.py

Use scripts/publish-module.py to validate, package, and optionally sign a module.

# Basic: create tarball and SHA-256 checksum
python scripts/publish-module.py path/to/module -o dist

# Write a registry index fragment (for merging into your registry index)
python scripts/publish-module.py path/to/module -o dist \
  --index-fragment dist/entry.yaml \
  --download-base-url https://registry.example.com/packages/

# Sign the manifest after packaging (requires key)
python scripts/publish-module.py path/to/module -o dist --sign
python scripts/publish-module.py path/to/module -o dist --sign --key-file /path/to/private.pem

Options:

  • module_path: Path to the module directory or to module-package.yaml.
  • -o / --output-dir: Directory for <name>-<version>.tar.gz and <name>-<version>.tar.gz.sha256.
  • --sign: Run scripts/sign-modules.py on the manifest (uses SPECFACT_MODULE_PRIVATE_SIGN_KEY or --key-file).
  • --key-file: Path to PEM private key when using --sign.
  • --index-fragment: Write a single-module index entry (id, latest_version, download_url, checksum_sha256) to the given path.
  • --download-base-url: Base URL for download_url in the index fragment.

Namespace and marketplace: If the manifest has publisher or tier, the script requires name in namespace/name form and validates format (^[a-z][a-z0-9-]*/[a-z][a-z0-9-]+$).

Signing (optional)

For runtime verification, sign the manifest so the tarball includes integrity metadata:

  • Environment: Set SPECFACT_MODULE_PRIVATE_SIGN_KEY (inline PEM) and SPECFACT_MODULE_PRIVATE_SIGN_KEY_PASSPHRASE if the key is encrypted. Or use --key-file and optionally --passphrase / --passphrase-stdin.
  • Re-sign after changes: Run scripts/sign-modules.py on the manifest (and bump version if content changed). See Module signing and key rotation.

GitHub Actions workflow

nold-ai/specfact-cli ships .github/workflows/publish-modules.yml for bundled modules only (re-sign + package + in-repo snapshot). It does not open PRs against specfact-cli-modules or update the marketplace registry/index.json.

  • Triggers: workflow_run after Module Signature Hardening on dev/main, workflow_dispatch (single module path), and push of tags *-v*.
  • Bundled snapshot: resources/bundled-module-registry/index.json records published versions for CI comparison. When versions advance, the workflow opens a PR in this repository (GITHUB_TOKEN) updating that file and uploads build artifacts.
  • Marketplace registry: Official bundle publishing and registry/index.json in nold-ai/specfact-cli-modules stay in that repository’s own automation.

Optional signing in CI: add repository secrets such as SPECFACT_MODULE_PRIVATE_SIGN_KEY and SPECFACT_MODULE_PRIVATE_SIGN_KEY_PASSPHRASE. Signing must happen before publish so the generated registry artifacts contain current integrity metadata.

Release flow summary (bundled modules in specfact-cli)

  1. Bump the bundled module version in module-package.yaml.
  2. Re-sign the changed manifest(s) (for example via sign-modules.yml on dev/main).
  3. Verify signatures locally and in CI.
  4. Push to dev and main (or merge via PR to those branches) in specfact-cli; when signing completes, publish-modules.yml may open a PR updating resources/bundled-module-registry/index.json.

For marketplace bundles maintained in specfact-cli-modules, follow that repository’s publishing guide and registry PR flow.

Best practices

  • Bump module version in module-package.yaml whenever payload or manifest content changes; keep versions immutable for published artifacts.
  • Use namespace/name for any module you publish to a registry.
  • Before merging to a protected branch such as main, run strict verification (same bundle as main pre-commit and sign-modules.yml push verify), e.g. hatch run verify-modules-signature or python scripts/verify-modules-signature.py with the flags in scripts/module-verify-policy.sh (VERIFY_MODULES_STRICT). On feature or dev branches, local pre-commit and PR CI use the relaxed VERIFY_MODULES_PR bundle (--skip-checksum-verification); see Module signing and key rotation. Follow your registry’s policy if stricter.
  • Prefer --download-base-url and --index-fragment when integrating with a custom registry index.

See also