feat(ci): v0.9 enterprise-ready β GHCR auto-build, SBOM, air-gapped doc, compliance one-pager#129
Conversation
Phase 3 enterprise-ready: every `v*` tag push builds the repo-root Dockerfile and publishes to GHCR. No more "the README documents the ghcr.io URL but you have to build the image yourself for now." - Tags: ghcr.io/dfrostar/neuralmind:vX.Y.Z and :latest - Platforms: linux/amd64 + linux/arm64 via buildx + QEMU - Auth: GITHUB_TOKEN with packages:write (no separate registry creds) - OCI labels: source, version, licenses=MIT, title/description - Cache: type=gha, mode=max so subsequent releases reuse the transitive-wheel pre-download layer in the builder stage - verify-pull job pulls the published image and runs `neuralmind --help` before declaring success Also supports workflow_dispatch with a tag input for backfilling an already-tagged release that didn't fire the on-push trigger (same escape hatch as release.yml's dispatch path). https://claude.ai/code/session_01SH6iHNAqeMJHXdq7ubVcuJ
Phase 3 enterprise-ready: every `v*` tag push generates a CycloneDX JSON SBOM and attaches it to the GitHub Release as neuralmind-vX.Y.Z.sbom.json. - Generator: Anchore syft via the official anchore/sbom-action - Format: CycloneDX 1.x JSON (ingested by Grype, Trivy, Dependency-Track, FOSSology, most enterprise SCA scanners) - Scope: scans the active Python env after `pip install .`, so every transitive runtime dep is captured with version + license info - Asset name: neuralmind-vX.Y.Z.sbom.json on the release page, downloadable via stable URL https://github.com/dfrostar/neuralmind/releases/download/vX.Y.Z/neuralmind-vX.Y.Z.sbom.json - Fallback: if the release upload fails (immutable Release), the SBOM is preserved as a 90-day workflow artifact - workflow_dispatch with tag input for backfilling https://claude.ai/code/session_01SH6iHNAqeMJHXdq7ubVcuJ
Phase 3 enterprise-ready: new docs/use-cases/air-gapped.md covering the strictest deployment posture β no outbound network at install, build, runtime, or query phase. Addresses the two install-time network deps that have always been the "but does it work behind a firewall" question: 1. PyPI bundle β `pip download` on a connected machine with explicit --platform tags matching the target, transfer the wheel set, install with --no-index --find-links on the air-gapped machine. 2. ChromaDB embedding model β pre-cache the all-MiniLM-L6-v2 ONNX model (~85 MB) on the connected machine and transfer alongside the wheel bundle. Sets the standard ~/.cache/chroma path; supports CHROMA_CACHE_DIR override for containerised / NFS-home setups. Includes the Docker variant β `docker save` + sneakernet works the same way because the v0.7.0 multi-stage Dockerfile pre-wheels all transitive deps in the builder stage (no PyPI calls at image runtime). Includes a compliance-posture summary block + troubleshooting for the two failure modes (platform-tag mismatch, ChromaDB cache miss). Cross-links to the v0.9 COMPLIANCE-SUMMARY.md. https://claude.ai/code/session_01SH6iHNAqeMJHXdq7ubVcuJ
#120) Phase 3 enterprise-ready: new docs/COMPLIANCE-SUMMARY.md β single reviewable surface for procurement, security review, and compliance teams. Consolidates claims previously scattered across SECURITY-GUIDE.md and ENTERPRISE.md. Sections: - At-a-glance posture table - NIST AI RMF β full GOVERN / MAP / MEASURE / MANAGE coverage with line-item evidence - SOC 2 Type II β CC6.1, CC7.1, CC7.2, A1.1, C1.2, P3.1/4.1 with evidence pointers - GDPR β data minimisation, storage limitation, right to erasure, no controller/processor split (operator is sole controller) - SBOM + container image provenance β what v0.9 ships, where to get it - Deployment postures (strict β permissive) - Verification table β every claim has a "how to verify yourself" command so the doc isn't asking the reader to take our word Also includes RELEASE_NOTES_v0.9.0.md with the minimum-doc scope (marketing rollout deferred per the project's "ship code + minimum docs, marketing arc later" cadence). Honest scope note up front: "NeuralMind itself is not certified to any compliance framework. The architecture supports certification of your deployment β every required control is either built in or available to switch on." https://claude.ai/code/session_01SH6iHNAqeMJHXdq7ubVcuJ
NeuralMind self-benchmarkStatus: Phase 1 β Reduction on committed fixture
Phase 2 β Learning uplift
Note: uplift numbers on a 500-line fixture are intentionally modest β the point is to Assumptions
Per-model token reduction
Rows marked measured use the provider's real tokenizer. Rows marked Automated by |
There was a problem hiding this comment.
Pull request overview
Phase 3 of the v0.6βv0.7βv0.8 release arc, packaging NeuralMind for enterprise consumption: CI-driven GHCR container publishing, a CycloneDX SBOM attached to each release, and two new compliance-oriented docs. No runtime code changes.
Changes:
- New
docker-publish.ymlworkflow that builds multi-platform (amd64+arm64) images onv*tag push, pushes to GHCR with version +:latest+ OCI labels, and runs a smoke-test pull job. - New
sbom.ymlworkflow that generates a CycloneDX JSON SBOM viaanchore/sbom-action, attaches it to the GitHub Release (falling back to a workflow artifact), and supports manualworkflow_dispatch. - New docs:
air-gapped.mdwalkthrough (wheel bundle + ChromaDB ONNX cache, Docker variant),COMPLIANCE-SUMMARY.mdconsolidating NIST AI RMF / SOC 2 / GDPR claims, andRELEASE_NOTES_v0.9.0.md.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
.github/workflows/docker-publish.yml |
New tag-triggered workflow to build/push multi-arch GHCR images and verify the pull. |
.github/workflows/sbom.yml |
New tag-triggered workflow producing a CycloneDX SBOM and attaching it to the Release. |
docs/use-cases/air-gapped.md |
New walkthrough for installing NeuralMind without outbound network at install or runtime. |
docs/COMPLIANCE-SUMMARY.md |
New one-pager consolidating NIST AI RMF, SOC 2 and GDPR posture for procurement reviewers. |
RELEASE_NOTES_v0.9.0.md |
Release notes describing the GHCR build, SBOM, air-gapped doc and compliance one-pager. |
Comments suppressed due to low confidence (1)
docs/use-cases/air-gapped.md:32
- The TL;DR tar command bundles
~/.cache/chroma/onnx_modelsusing its (absolute) path, so aftertar xzfthe model directory will land athome/<user>/.cache/chroma/onnx_models/(with leading slash stripped), not at./onnx_models/. The follow-upcp -r onnx_models ~/.cache/chroma/therefore won't find the directory. Either use the-Cform shown in Step 3 (tar czf ... offline-bundle/ -C ~/.cache/chroma onnx_models/) soonnx_models/extracts at the working directory, or update the cp step to point at the actual extracted path.
tar czf neuralmind-offline.tgz ./offline-bundle \
~/.cache/chroma/onnx_models
# Move the tarball to the air-gapped machine, then:
tar xzf neuralmind-offline.tgz
pip install --no-index --find-links offline-bundle neuralmind graphifyy
mkdir -p ~/.cache/chroma && cp -r onnx_models ~/.cache/chroma/
π‘ Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # On a connected machine, with the same Python version as the target: | ||
| pip download neuralmind graphifyy --dest ./offline-bundle | ||
| python -c "from chromadb.utils import embedding_functions as ef; \ | ||
| ef.DefaultEmbeddingFunction()()" # warm the model cache |
| tags: | | ||
| ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.RELEASE_TAG }} | ||
| ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest |
| env: | ||
| REGISTRY: ghcr.io | ||
| IMAGE_NAME: ${{ github.repository }} | ||
| RELEASE_TAG: ${{ inputs.tag || github.ref_name }} |
| if ! gh release view "${TAG}" --repo "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then | ||
| echo "::warning title=Release not found yet::${TAG} doesn't exist; release.yml usually creates it. Skipping upload β re-run this workflow once the release exists." | ||
| exit 0 | ||
| fi | ||
| if gh release upload "${TAG}" "${ASSET}" --repo "${GITHUB_REPOSITORY}" --clobber; then | ||
| echo "SBOM attached as ${ASSET}" | ||
| else | ||
| echo "::warning title=SBOM upload failed::Release ${TAG} may be immutable. SBOM is in the workflow artifacts." |
β¦ SBOM race + air-gapped TL;DR Bundle of 5 fixes from Copilot review on PR #129: scripts/docker-publish.yml: - Switched to docker/metadata-action for tag generation. This handles two Copilot findings at once: - **case sensitivity**: GHCR rejects uppercase in image references; metadata-action lowercases ${{ github.repository }} when building tags, so a future repo rename or mixed-case fork doesn't break the push. - **:latest on pre-release tags**: previously tagged every v* push as :latest. Now :latest is only set when the tag has no `-` suffix (via type=raw,...,enable=${{ !contains(...) }}), so v0.10.0-rc1 / v1.0.0-beta1 won't override stable :latest. - The verify-pull job reads the version-tagged ref from the metadata-action outputs (first line of `tags`) instead of reconstructing it from env. scripts/sbom.yml: - Added a polling loop (~6 min, 20s intervals) around `gh release view` so the SBOM lands on the GitHub Release on first publish even when release.yml is still in flight when this workflow starts. The existing workflow_artifact fallback is preserved for genuine failure cases. Avoids the "first release has no SBOM attached" UX gap Copilot flagged. docs/use-cases/air-gapped.md TL;DR snippet: - Fixed the tar invocation: previously used absolute path ~/.cache/chroma/onnx_models which tar strips to home/<user>/.cache/... on extract, so the later `cp -r onnx_models ...` couldn't find it. Switched to the `-C ~/.cache/chroma onnx_models` form that Step 3 already uses correctly. - Fixed the embedding-function call: ef.DefaultEmbeddingFunction()() with no args raises TypeError. ChromaDB embedding functions expect a list of documents. Changed to (['warm']) to match the detailed Step 2 below. Verified locally: - Both workflow YAML files parse via `python3 -c 'import yaml; ...'` - No production-code changes - The metadata-action tag pattern logic verified in the YAML expression: type=raw,value=latest,enable=${{ !contains('v0.9.0', '-') }} β true, so v0.9.0 will get both :v0.9.0 and :latest; v0.10.0-rc1 β false, only :v0.10.0-rc1. Review URLs: - #129 (comment) - #129 (comment) - #129 (comment) - #129 (comment) https://claude.ai/code/session_01SH6iHNAqeMJHXdq7ubVcuJ
There was a problem hiding this comment.
π‘ Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5ac1c2d738
βΉοΈ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with π.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if ! gh release view "${TAG}" --repo "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then | ||
| echo "::warning title=Release not found yet::${TAG} doesn't exist; release.yml usually creates it. Skipping upload β re-run this workflow once the release exists." | ||
| exit 0 |
There was a problem hiding this comment.
Retry release lookup instead of skipping SBOM upload
On push to a release tag, this workflow runs in parallel with release.yml, but this step exits successfully as soon as gh release view cannot find the release yet. Because there is no wait/retry path, the normal first run on a new tag can permanently skip attaching neuralmind-vX.Y.Z.sbom.json unless someone manually reruns the workflow, which breaks the βattached to every tagged releaseβ behavior advertised by this change.
Useful? React with πΒ / π.
v0.9.0 "Enterprise-Ready" β Phase 3 of the release arc. Pure CI infrastructure + docs; no production code changes. - feat(ci): GHCR multi-platform image auto-build on tag push (#129). ghcr.io/dfrostar/neuralmind:vX.Y.Z + :latest (stable tags only), linux/amd64 + linux/arm64, non-root runtime. docker/metadata-action for lowercase-safe naming + smart :latest gating. - feat(ci): CycloneDX SBOM generation + release-asset attachment. Anchore syft via anchore/sbom-action; 6-min poll loop around `gh release view` so it lands on first publish. - docs(use-cases): air-gapped install walkthrough (wheel bundle + ChromaDB ONNX cache, with Docker variant via `docker save`). - docs(compliance): NIST AI RMF + SOC 2 + GDPR one-pager with a "how to verify yourself" command for every claim. 5 Copilot + 1 Codex review items addressed in commit fdf8b4e. Full release notes: RELEASE_NOTES_v0.9.0.md. Closes #120 once the maintainer verifies the GHCR + SBOM workflows succeed on the v0.9.0 tag. https://claude.ai/code/session_01SH6iHNAqeMJHXdq7ubVcuJ
Summary
Phase 3 of the release arc β turn the v0.6.0 β v0.7.0 β v0.8.0 foundation into something a CTO, security team, or regulated-industry operator can actually adopt. Addresses #120.
Four logical commits (CI infrastructure + docs, no production code changes):
feat(ci): GHCR multi-platform image auto-build on tag push.github/workflows/docker-publish.ymlβ everyv*tag publishesghcr.io/dfrostar/neuralmind:vX.Y.Zand:latest, multi-platform (linux/amd64+linux/arm64), with a verify-pull smoke testfeat(ci): CycloneDX SBOM generation + release-asset attachment.github/workflows/sbom.ymlβ everyv*tag generates a CycloneDX JSON SBOM via Anchore syft and attaches it to the GitHub Release asneuralmind-vX.Y.Z.sbom.jsondocs(use-cases): air-gapped install walkthroughdocs/use-cases/air-gapped.mdβ bundle-and-sneakernet pattern for PyPI wheels + ChromaDB embedding model, plus the Docker variant viadocker savedocs(compliance): NIST + SOC 2 + GDPR one-pager + v0.9.0 release notesdocs/COMPLIANCE-SUMMARY.mdβ single reviewable surface consolidating claims from SECURITY-GUIDE.md + ENTERPRISE.md, plusRELEASE_NOTES_v0.9.0.mdDeferred (per "ship code + minimum docs" cadence)
Test plan
python3 -c "import yaml; yaml.safe_load(...)"docker manifest inspect)Versioning
Two
feat(ci):commits + twodocs(...)commits β release-please should propose v0.9.0 with the matching changelog scope. Lessons learned from v0.7βv0.7.1βv0.8.0 dance: the merge commit title for this PR should use conventional-commit format so release-please picks up the feat scope correctly. Title chosen accordingly.Order vs PR #128
This PR doesn't depend on #128 (Release-As v0.8.0 override) merging first. They sit on separate branches and address separate releases. Natural sequence:
RELEASE_PLEASE_TOKENsecret is added (from PR fix(ci): use PAT for release-please so tag pushes trigger release.ymlΒ #126), both publishes are zero-touch on the tag pushIssues
Closes #120 (Phase 3 Enterprise-Ready) once the maintainer verifies the GHCR + SBOM workflows succeed on the v0.9.0 tag.
https://claude.ai/code/session_01SH6iHNAqeMJHXdq7ubVcuJ
Generated by Claude Code