groupoid 0.1.0.dev4__tar.gz → 0.1.0.dev5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/.github/dependabot.yml +9 -0
  2. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/.github/workflows/auto-tag-release.yml +33 -14
  3. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/.github/workflows/ci.yml +27 -20
  4. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/.github/workflows/docs-deploy.yml +2 -2
  5. groupoid-0.1.0.dev5/.github/workflows/release.yml +222 -0
  6. groupoid-0.1.0.dev5/.zenodo.json +39 -0
  7. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/CHANGELOG.md +49 -2
  8. groupoid-0.1.0.dev5/CITATION.cff +35 -0
  9. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/CONTRIBUTING.md +16 -7
  10. groupoid-0.1.0.dev5/CORRECTION_NOTICE.md +147 -0
  11. groupoid-0.1.0.dev5/LIMITATIONS.md +114 -0
  12. {groupoid-0.1.0.dev4/groupoid.egg-info → groupoid-0.1.0.dev5}/PKG-INFO +47 -37
  13. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/PROBLEMS.md +60 -28
  14. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/README.md +45 -35
  15. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/SECURITY.md +10 -0
  16. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/STATUS.md +21 -17
  17. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/codemeta.json +2 -2
  18. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/docs/api.md +16 -2
  19. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/docs/index.md +16 -13
  20. groupoid-0.1.0.dev5/docs/quickstart.md +100 -0
  21. groupoid-0.1.0.dev5/docs/theory.md +200 -0
  22. groupoid-0.1.0.dev5/experiments/README.md +68 -0
  23. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/groupoid/__init__.py +19 -3
  24. groupoid-0.1.0.dev5/groupoid/aggregation.py +380 -0
  25. groupoid-0.1.0.dev5/groupoid/cohomology.py +170 -0
  26. groupoid-0.1.0.dev5/groupoid/groupoid.py +120 -0
  27. groupoid-0.1.0.dev5/groupoid/transport.py +187 -0
  28. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5/groupoid.egg-info}/PKG-INFO +47 -37
  29. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/groupoid.egg-info/SOURCES.txt +5 -0
  30. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/pyproject.toml +2 -2
  31. groupoid-0.1.0.dev5/scripts/bind_release_sbom.py +198 -0
  32. groupoid-0.1.0.dev5/tests/test_cohomology_cocycle.py +169 -0
  33. groupoid-0.1.0.dev5/tests/test_coverage_gaps.py +186 -0
  34. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/tests/test_imports.py +9 -1
  35. groupoid-0.1.0.dev5/tests/test_integration.py +208 -0
  36. groupoid-0.1.0.dev5/tests/test_pipeline_integration.py +89 -0
  37. groupoid-0.1.0.dev5/tests/test_release_sbom_binding.py +156 -0
  38. groupoid-0.1.0.dev5/tests/test_release_workflow_integrity.py +100 -0
  39. groupoid-0.1.0.dev5/tests/test_scientific_corrections.py +552 -0
  40. groupoid-0.1.0.dev5/tests/theory/test_mathematical_invariants.py +120 -0
  41. groupoid-0.1.0.dev4/.github/workflows/release.yml +0 -107
  42. groupoid-0.1.0.dev4/.zenodo.json +0 -39
  43. groupoid-0.1.0.dev4/CITATION.cff +0 -35
  44. groupoid-0.1.0.dev4/LIMITATIONS.md +0 -87
  45. groupoid-0.1.0.dev4/docs/quickstart.md +0 -123
  46. groupoid-0.1.0.dev4/docs/theory.md +0 -107
  47. groupoid-0.1.0.dev4/experiments/README.md +0 -64
  48. groupoid-0.1.0.dev4/groupoid/aggregation.py +0 -299
  49. groupoid-0.1.0.dev4/groupoid/cohomology.py +0 -99
  50. groupoid-0.1.0.dev4/groupoid/groupoid.py +0 -90
  51. groupoid-0.1.0.dev4/groupoid/transport.py +0 -188
  52. groupoid-0.1.0.dev4/tests/test_cohomology_cocycle.py +0 -245
  53. groupoid-0.1.0.dev4/tests/test_coverage_gaps.py +0 -242
  54. groupoid-0.1.0.dev4/tests/test_integration.py +0 -253
  55. groupoid-0.1.0.dev4/tests/test_pipeline_integration.py +0 -130
  56. groupoid-0.1.0.dev4/tests/theory/test_mathematical_invariants.py +0 -166
  57. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  58. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  59. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/.github/ISSUE_TEMPLATE/mathematical_correction.md +0 -0
  60. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/.github/scripts/install_torch_cpu.sh +0 -0
  61. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/.gitignore +0 -0
  62. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/.pre-commit-config.yaml +0 -0
  63. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/LICENSE +0 -0
  64. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/REPRODUCE.md +0 -0
  65. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/benchmarks/__init__.py +0 -0
  66. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/benchmarks/conftest.py +0 -0
  67. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/benchmarks/test_karcher_benchmark.py +0 -0
  68. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/docs/overrides/404.html +0 -0
  69. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/examples/README.md +0 -0
  70. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/examples/__init__.py +0 -0
  71. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/examples/synthetic_curve_demo.py +0 -0
  72. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/experiments/PREREGISTRATION.md +0 -0
  73. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/experiments/RESULTS.md +0 -0
  74. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/experiments/analyze.py +0 -0
  75. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/experiments/results.json +0 -0
  76. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/experiments/run_benchmark.py +0 -0
  77. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/groupoid/laplacian.py +0 -0
  78. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/groupoid/manifold.py +0 -0
  79. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/groupoid/optimizer.py +0 -0
  80. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/groupoid/persistence.py +0 -0
  81. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/groupoid/sheaf.py +0 -0
  82. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/groupoid.egg-info/dependency_links.txt +0 -0
  83. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/groupoid.egg-info/requires.txt +0 -0
  84. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/groupoid.egg-info/top_level.txt +0 -0
  85. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/mkdocs.yml +0 -0
  86. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/scripts/reproduce_sheaf_laplacian.py +0 -0
  87. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/setup.cfg +0 -0
  88. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/tests/__init__.py +0 -0
  89. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/tests/test_aggregation_disconnected.py +0 -0
  90. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/tests/test_defensive_paths.py +0 -0
  91. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/tests/test_optimizer_validation.py +0 -0
  92. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/tests/test_persistence.py +0 -0
  93. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/tests/test_persistence_dimension_labels.py +0 -0
  94. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/tests/test_sheaf_laplacian.py +0 -0
  95. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/tests/test_smoke.py +0 -0
  96. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/tests/test_transport_validation.py +0 -0
  97. {groupoid-0.1.0.dev4 → groupoid-0.1.0.dev5}/tests/theory/__init__.py +0 -0
@@ -28,6 +28,15 @@ updates:
28
28
  # updates patch vulnerable dependencies regardless of this allow filter.
29
29
  allow:
30
30
  - dependency-type: "direct"
31
+ # numpy 2.x is a verified runtime incompatibility, not a routine bump:
32
+ # geomstats (a required runtime dependency) fails at import under
33
+ # numpy>=2.0 because `numpy.trapz` was removed, so the tested
34
+ # numpy>=1.24,<2.0 contract in pyproject.toml cannot widen (see closed
35
+ # PR #57). Scoped by version, not a blanket numpy ignore: compatible
36
+ # numpy 1.x version updates and 1.x security updates still flow.
37
+ ignore:
38
+ - dependency-name: "numpy"
39
+ versions: [">=2.0.0"]
31
40
  groups:
32
41
  # Batch updates by area into a single PR per group to limit noise instead
33
42
  # of one PR per package.
@@ -1,18 +1,27 @@
1
1
  name: Auto Tag Release
2
2
 
3
- # Creates the tag for a release-cut merge to main; it does nothing else.
3
+ # Creates the tag for a release-cut merge to main, then explicitly dispatches
4
4
  # release.yml (build, attestations, PyPI publish, signing, GitHub Release)
5
- # is triggered solely by that tag push, exactly as it would be from a
6
- # manual `git tag vX.Y.Z && git push origin vX.Y.Z`. Keeping tag creation
7
- # and publishing in two separate, single-purpose workflows means the
8
- # actual publish path has exactly one trigger (a tag push) to reason
9
- # about, instead of depending on GitHub's anti-recursion behavior for
10
- # GITHUB_TOKEN-authored pushes not re-triggering the same push event.
5
+ # against that tag.
11
6
  #
12
- # If a version needs a tag this workflow's format check rejects (see
13
- # below), tag it manually -- this automation only ever creates a tag
14
- # when one is missing, so a pre-existing manual tag always takes
15
- # precedence and this workflow becomes a no-op for that version.
7
+ # The dispatch is explicit, not a tag-push trigger, because GitHub suppresses
8
+ # push-triggered workflow runs caused by a GITHUB_TOKEN-authored push: pushing
9
+ # the tag here does NOT cause release.yml's `on.push.tags` trigger to fire.
10
+ # workflow_dispatch is the documented exception to that suppression, which is
11
+ # why this workflow calls `gh workflow run` instead of relying on the tag
12
+ # push itself. (An earlier version of this workflow assumed the tag push
13
+ # alone would trigger release.yml; it did not, and no release was produced
14
+ # for v0.1.0.dev4 until this was fixed and the release re-run manually.)
15
+ #
16
+ # release.yml's own `push.tags` trigger remains as a fallback/manual path
17
+ # (a human pushing a tag with their own credentials is not subject to this
18
+ # suppression), alongside its `workflow_dispatch` trigger for republishing an
19
+ # existing tag to PyPI only.
20
+ #
21
+ # If a version needs a tag this workflow's format check rejects (see below),
22
+ # tag it manually -- this automation only ever creates a tag when one is
23
+ # missing, so a pre-existing manual tag always takes precedence and this
24
+ # workflow becomes a no-op for that version.
16
25
 
17
26
  on:
18
27
  push:
@@ -27,13 +36,16 @@ jobs:
27
36
  tag:
28
37
  runs-on: ubuntu-latest
29
38
  permissions:
30
- contents: write # push the tag; nothing else in this workflow needs it
39
+ contents: write # push the tag
40
+ actions: write # dispatch release.yml via `gh workflow run`
31
41
  steps:
32
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
42
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
33
43
  with:
34
44
  fetch-depth: 0 # need full tag history to check for an existing tag
35
45
 
36
- - name: Create the release tag if this version doesn't have one yet
46
+ - name: Create the release tag if this version doesn't have one yet, and dispatch the release
47
+ env:
48
+ GH_TOKEN: ${{ github.token }}
37
49
  run: |
38
50
  set -euo pipefail
39
51
 
@@ -70,3 +82,10 @@ jobs:
70
82
  git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
71
83
  git tag -a "${tag}" -m "Release ${tag}"
72
84
  git push origin "refs/tags/${tag}"
85
+
86
+ echo "Dispatching release.yml for ${tag}."
87
+ gh workflow run release.yml \
88
+ --repo "${GITHUB_REPOSITORY}" \
89
+ --ref "${tag}" \
90
+ -f "ref=${tag}" \
91
+ -f "create_github_release=true"
@@ -13,8 +13,8 @@ jobs:
13
13
  lint:
14
14
  runs-on: ubuntu-latest
15
15
  steps:
16
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
17
- - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
16
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
17
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
18
18
  with:
19
19
  python-version: "3.11"
20
20
  - name: Install dependencies
@@ -40,8 +40,8 @@ jobs:
40
40
  matrix:
41
41
  python-version: ["3.10", "3.11", "3.12"]
42
42
  steps:
43
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
44
- - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
43
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
44
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
45
45
  with:
46
46
  python-version: ${{ matrix.python-version }}
47
47
  - name: Install dependencies
@@ -58,8 +58,8 @@ jobs:
58
58
  security:
59
59
  runs-on: ubuntu-latest
60
60
  steps:
61
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
62
- - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
61
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
62
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
63
63
  with:
64
64
  python-version: "3.11"
65
65
  - name: Install dependencies
@@ -83,20 +83,27 @@ jobs:
83
83
  - name: Audit dependencies for known vulnerabilities (pip-audit)
84
84
  # pip-audit (OSV-backed, maintained) replaces the deprecated `safety check`
85
85
  # (deprecated in safety 3.x in favour of the account-gated `safety scan`).
86
- # HARD gate: any advisory with a fix version fails the job. The single
87
- # documented exception is CVE-2025-3000 (torch): no fixed release exists
88
- # upstream, and OSV expresses the affected range (listed versions up to
89
- # v2.6.0) as a Git/commit boundary pip-audit cannot version-resolve, so it
90
- # conservatively flags the installed torch. Disclosed in SECURITY.md; the
91
- # torch.load / JIT deserialization path is not exercised by GROUPOID. Revisit
92
- # the ignore once upstream publishes a fixed release.
93
- run: pip-audit --ignore-vuln CVE-2025-3000
86
+ # HARD gate: any advisory with a fix version fails the job. Two documented
87
+ # exceptions, both disclosed in SECURITY.md:
88
+ # - CVE-2025-3000 (torch): no fixed release exists upstream, and OSV
89
+ # expresses the affected range (listed versions up to v2.6.0) as a
90
+ # Git/commit boundary pip-audit cannot version-resolve, so it
91
+ # conservatively flags the installed torch. The torch.load / JIT
92
+ # deserialization path is not exercised by GROUPOID. Revisit the ignore
93
+ # once upstream publishes a fixed release.
94
+ # - PYSEC-2026-3447 (CVE-2026-59890, setuptools <83): the fix (83.0.0) is
95
+ # unreachable while torch requires setuptools<82 (see the install step
96
+ # above). The flaw is a macOS-only (APFS/HFS+) Unicode-normalization
97
+ # bypass of MANIFEST.in exclude rules when building sdists; all CI and
98
+ # release builds run on ubuntu-latest. Drop the ignore once torch
99
+ # permits setuptools>=83.
100
+ run: pip-audit --ignore-vuln CVE-2025-3000 --ignore-vuln PYSEC-2026-3447
94
101
 
95
102
  pre-commit:
96
103
  runs-on: ubuntu-latest
97
104
  steps:
98
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
99
- - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
105
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
106
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
100
107
  with:
101
108
  python-version: "3.11"
102
109
  - name: Install pre-commit
@@ -107,8 +114,8 @@ jobs:
107
114
  benchmark:
108
115
  runs-on: ubuntu-latest
109
116
  steps:
110
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
111
- - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
117
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
118
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
112
119
  with:
113
120
  python-version: "3.11"
114
121
  - name: Install dependencies
@@ -137,8 +144,8 @@ jobs:
137
144
  docs:
138
145
  runs-on: ubuntu-latest
139
146
  steps:
140
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
141
- - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
147
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
148
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
142
149
  with:
143
150
  python-version: "3.11"
144
151
  - name: Install dependencies
@@ -16,8 +16,8 @@ jobs:
16
16
  build:
17
17
  runs-on: ubuntu-latest
18
18
  steps:
19
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
20
- - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
19
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
20
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
21
21
  with:
22
22
  python-version: "3.11"
23
23
  - name: Install dependencies
@@ -0,0 +1,222 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v[0-9]*"
7
+ workflow_dispatch:
8
+ inputs:
9
+ ref:
10
+ description: "Existing tag/ref to build and publish (e.g. v0.1.0.dev1). Leave blank for a normal tag-triggered release."
11
+ required: false
12
+ default: ""
13
+ create_github_release:
14
+ description: "Also sign with Sigstore and create a GitHub Release (which triggers the Zenodo deposit) for this tag. Default false: PyPI publish only, for republishing an already-released tag without creating a duplicate GitHub Release. auto-tag-release.yml sets this true when it dispatches a new version's first release."
15
+ required: false
16
+ default: false
17
+ type: boolean
18
+
19
+ permissions:
20
+ contents: read
21
+
22
+ jobs:
23
+ # Privilege separation: this job holds the attestation credentials, so it
24
+ # must never install the built wheel or resolve its runtime dependency
25
+ # graph. SBOM generation (which does both) runs in the unprivileged `sbom`
26
+ # job; the SBOM attestation runs in `attest-sbom`, which installs nothing.
27
+ build:
28
+ runs-on: ubuntu-latest
29
+ permissions:
30
+ contents: read
31
+ id-token: write # provenance OIDC
32
+ attestations: write # actions/attest-build-provenance
33
+ steps:
34
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
35
+ with:
36
+ ref: ${{ github.event.inputs.ref || github.ref }}
37
+ fetch-depth: 0
38
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
39
+ with:
40
+ python-version: "3.12"
41
+ cache: pip
42
+ - name: Install build tooling
43
+ run: python -m pip install --upgrade pip build
44
+ - name: Build sdist + wheel
45
+ run: python -m build
46
+ - name: Assert exactly one release wheel
47
+ shell: bash
48
+ run: |
49
+ set -euo pipefail
50
+ shopt -s nullglob
51
+ wheels=(dist/*.whl)
52
+ if (( ${#wheels[@]} != 1 )); then
53
+ echo "::error::expected exactly one release wheel, found ${#wheels[@]}"
54
+ exit 1
55
+ fi
56
+ - name: Provenance attestation (dist)
57
+ uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
58
+ with:
59
+ subject-path: "dist/*"
60
+ - name: Upload dist artifacts
61
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
62
+ with:
63
+ name: release-dist
64
+ path: dist/
65
+
66
+ # Unprivileged by design: installing the wheel resolves and executes
67
+ # third-party package installs, so this job gets no id-token, no
68
+ # attestations, no write access of any kind.
69
+ sbom:
70
+ needs: build
71
+ runs-on: ubuntu-latest
72
+ permissions:
73
+ contents: read
74
+ steps:
75
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
76
+ with:
77
+ ref: ${{ github.event.inputs.ref || github.ref }}
78
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
79
+ with:
80
+ python-version: "3.12"
81
+ cache: pip
82
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
83
+ with:
84
+ name: release-dist
85
+ path: dist
86
+ - name: Generate CycloneDX SBOM from exact release wheel
87
+ shell: bash
88
+ run: |
89
+ set -euo pipefail
90
+ shopt -s nullglob
91
+ wheels=(dist/*.whl)
92
+ if (( ${#wheels[@]} != 1 )); then
93
+ echo "::error::expected exactly one release wheel, found ${#wheels[@]}"
94
+ exit 1
95
+ fi
96
+ wheel="${wheels[0]}"
97
+
98
+ python -m pip install --upgrade pip cyclonedx-bom
99
+
100
+ # Keep build tooling out of the analyzed environment. pip's --python
101
+ # option installs the exact wheel and its resolved runtime graph into
102
+ # a venv that was created without pip.
103
+ python -m venv --without-pip .sbom-runtime
104
+ python -m pip --python .sbom-runtime install "$wheel"
105
+
106
+ cyclonedx-py environment .sbom-runtime/bin/python \
107
+ --pyproject pyproject.toml \
108
+ --mc-type library \
109
+ --output-reproducible \
110
+ --output-format JSON \
111
+ --output-file sbom.cdx.json
112
+ python scripts/bind_release_sbom.py --sbom sbom.cdx.json --wheel "$wheel"
113
+ - name: Independently verify SBOM root against exact wheel
114
+ shell: bash
115
+ run: |
116
+ set -euo pipefail
117
+ shopt -s nullglob
118
+ wheels=(dist/*.whl)
119
+ if (( ${#wheels[@]} != 1 )); then
120
+ echo "::error::expected exactly one release wheel, found ${#wheels[@]}"
121
+ exit 1
122
+ fi
123
+ python scripts/bind_release_sbom.py --verify-only --sbom sbom.cdx.json --wheel "${wheels[0]}"
124
+ - name: Upload SBOM artifact
125
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
126
+ with:
127
+ name: release-sbom
128
+ path: sbom.cdx.json
129
+
130
+ # The only privileged SBOM step. It consumes the already-built wheel and the
131
+ # already-verified SBOM; it installs no packages, resolves no dependencies,
132
+ # and executes no project code. The SBOM describes the installed wheel's
133
+ # runtime closure, so it is attested to the wheel subject only — never the
134
+ # sdist.
135
+ attest-sbom:
136
+ needs: [build, sbom]
137
+ runs-on: ubuntu-latest
138
+ permissions:
139
+ contents: read
140
+ id-token: write # attestation OIDC
141
+ attestations: write # actions/attest-sbom
142
+ steps:
143
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
144
+ with:
145
+ name: release-dist
146
+ path: dist
147
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
148
+ with:
149
+ name: release-sbom
150
+ path: .
151
+ - uses: actions/attest-sbom@c604332985a26aa8cf1bdc465b92731239ec6b9e # v4.1.0
152
+ with:
153
+ subject-path: "dist/*.whl"
154
+ sbom-path: "sbom.cdx.json"
155
+
156
+ publish-pypi:
157
+ # Publication waits for the full integrity chain: build provenance
158
+ # (build), SBOM generation + verification (sbom), and the wheel-bound
159
+ # SBOM attestation (attest-sbom).
160
+ needs: [build, sbom, attest-sbom]
161
+ runs-on: ubuntu-latest
162
+ permissions:
163
+ contents: read
164
+ id-token: write # PyPI Trusted Publishing OIDC
165
+ environment:
166
+ name: pypi
167
+ url: https://pypi.org/project/groupoid/
168
+ steps:
169
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
170
+ with:
171
+ name: release-dist
172
+ path: dist
173
+ - name: Publish to PyPI (Trusted Publishing, OIDC)
174
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
175
+ with:
176
+ packages-dir: dist
177
+ print-hash: true
178
+ # Idempotent: a re-dispatch or an out-of-band manual republish of a
179
+ # version already on PyPI should not fail the job.
180
+ skip-existing: true
181
+
182
+ sign-and-release:
183
+ needs: [build, sbom, attest-sbom]
184
+ # On a real tag push, or an explicit request via create_github_release=true
185
+ # (see auto-tag-release.yml, which dispatches this workflow with that flag
186
+ # set for a version's first release, since GitHub suppresses push-triggered
187
+ # workflow runs caused by its own GITHUB_TOKEN). A plain workflow_dispatch
188
+ # republish (create_github_release left at its false default) must NOT
189
+ # create a duplicate GitHub Release or trigger a second Zenodo deposit.
190
+ if: github.event_name == 'push' || github.event.inputs.create_github_release == 'true'
191
+ runs-on: ubuntu-latest
192
+ permissions:
193
+ contents: write # create GitHub Release
194
+ id-token: write # Sigstore signing OIDC
195
+ steps:
196
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
197
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
198
+ with:
199
+ name: release-dist
200
+ path: dist
201
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
202
+ with:
203
+ name: release-sbom
204
+ path: .
205
+ - name: Sign artifacts with Sigstore
206
+ uses: sigstore/gh-action-sigstore-python@790bc6befb9d733738f18d8f895854b453640ec9 # v3.5.0
207
+ with:
208
+ inputs: |
209
+ dist/*.tar.gz
210
+ dist/*.whl
211
+ - name: Create GitHub Release
212
+ uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
213
+ with:
214
+ # Explicit rather than relying on ref inference: on a tag push
215
+ # github.ref_name is already the tag, but being explicit keeps this
216
+ # correct regardless of trigger type.
217
+ tag_name: ${{ github.event.inputs.ref || github.ref_name }}
218
+ generate_release_notes: true
219
+ files: |
220
+ dist/*
221
+ sbom.cdx.json
222
+ *.sigstore.json
@@ -0,0 +1,39 @@
1
+ {
2
+ "upload_type": "software",
3
+ "title": "GROUPOID: Groupoid-based aggregation for federated learning on Riemannian manifolds",
4
+ "version": "0.1.0.dev5",
5
+ "description": "<p>Pre-alpha research prototype exploring groupoid-based aggregation for federated learning on Riemannian manifolds. The supported point-valued aggregation path uses explicit invertible point actions, a cycle-basis holonomy defect diagnostic, the cellular sheaf Laplacian, and Karcher mean aggregation via geomstats. Tangent-vector parallel transport is provided separately through Schild and pole ladder utilities.</p><p><strong>Scientific correction in v0.1.0.dev5:</strong> earlier releases described the cycle-basis holonomy statistic as a first-cohomology (H^1) norm. The implementation actually computes the maximum Frobenius deviation of holonomy from identity over the particular NetworkX cycle basis. Exact zero has a flatness interpretation under explicit connectedness, completeness, invertibility, reciprocal opposite-edge registration when both directions are supplied, and complete evaluation of every cycle emitted by the NetworkX cycle-basis routine (an argument specific to the NetworkX Paton implementation exercised here, not to arbitrary cycle bases), but the nonzero magnitude is basis-dependent and is not invariant under arbitrary invertible changes of frame. The stored preregistered benchmark values are unchanged; the historical H2 rho and permutation p-value are reinterpreted as statistics for this fixed cycle-basis defect, not for a canonical H^1 norm. This version also withdraws register_transport_from_points from the supported point-valued aggregation path: its ambient square operator is assembled from transported tangent vectors and does not generally define the invertible manifold point action that aggregation requires. Tangent-vector ladder validation remains supported. The ambient tangent-matrix helper is restricted to vector-shaped point representations. Direct registered matrices are supported only under the explicit point-action contract documented in the repository, including reciprocal opposite arrows when both orientations are stored. See CORRECTION_NOTICE.md for the full correction record.</p><p>The earlier v0.1.0.dev0 version DOI 10.5281/zenodo.20563975 remains superseded and must not be cited for results. Exact historical versions should be cited by their version-specific DOI; project-level DOI metadata is maintained through the Zenodo version family.</p>",
6
+ "creators": [
7
+ {
8
+ "name": "Maniches, Santiago",
9
+ "affiliation": "TOPOLOGICA LLC",
10
+ "orcid": "0009-0005-6480-1987"
11
+ }
12
+ ],
13
+ "license": "Apache-2.0",
14
+ "keywords": [
15
+ "federated learning",
16
+ "Riemannian geometry",
17
+ "groupoid",
18
+ "sheaf theory",
19
+ "holonomy",
20
+ "topological data analysis"
21
+ ],
22
+ "related_identifiers": [
23
+ {
24
+ "identifier": "10.5281/zenodo.20563974",
25
+ "relation": "isVersionOf",
26
+ "scheme": "doi"
27
+ },
28
+ {
29
+ "identifier": "10.5281/zenodo.21230068",
30
+ "relation": "isNewVersionOf",
31
+ "scheme": "doi"
32
+ },
33
+ {
34
+ "identifier": "https://github.com/smaniches/GROUPOID",
35
+ "relation": "isSupplementTo",
36
+ "scheme": "url"
37
+ }
38
+ ]
39
+ }
@@ -8,7 +8,53 @@ this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
- Nothing yet.
11
+ ## [0.1.0.dev5]
12
+
13
+ Scientific correction release. See `CORRECTION_NOTICE.md` for the evidence and
14
+ scope. Historical preregistration and result artifacts are preserved unchanged.
15
+
16
+ ### Fixed
17
+ - **The former `H^1` scalar is now identified by the quantity actually
18
+ computed.** `cycle_basis_holonomy_defect` returns
19
+ `max_{gamma in B} ||Hol(gamma) - I||_F` over the NetworkX cycle basis. The
20
+ historical `compute_h1` name remains as a deprecated compatibility alias.
21
+ The exact zero set has a flatness interpretation under explicit connected,
22
+ complete, invertible edge-transport assumptions with every cycle emitted by
23
+ `networkx.cycle_basis` evaluated -- an argument specific to the emission
24
+ order of the NetworkX Paton implementation exercised here, not to arbitrary
25
+ cycle bases -- but the nonzero magnitude is cycle-basis-dependent and is not
26
+ invariant under arbitrary invertible changes of frame. Finite threshold
27
+ decisions are therefore representation-dependent diagnostics, not canonical
28
+ cohomological verdicts.
29
+ - **Tangent-vector transport is no longer promoted into an unsupported
30
+ point-valued morphism.** `compute_tangent_transport_matrix` now names the
31
+ ambient-coordinate tangent operator explicitly. The old
32
+ `compute_transport_matrix` name remains as a deprecated alias.
33
+ `register_transport_from_points` is retained only as a compatibility stub
34
+ and fails closed because tangent parallel transport does not by itself define
35
+ the invertible point action required by the current aggregator.
36
+ - **Direct transport registration now states and checks the point-action
37
+ contract exercised by aggregation.** Registered matrices must be finite,
38
+ square and invertible, and actual forward and return images must remain on
39
+ the configured manifold. This validates exercised point actions, not
40
+ arbitrary matrices as generic geometric transports. The supported S^2
41
+ evidence continues to use explicit SO(3) rotations.
42
+ - **Benchmark H2 interpretation corrected without changing its historical
43
+ numerical result.** The committed rho=0.587 and permutation p=0.00010 are
44
+ retained as the association between aggregation error and the fixed
45
+ cycle-basis holonomy-defect statistic across pooled corruption levels; the
46
+ near-zero within-level correlations remain an explicit caveat.
47
+ - **Preregistration provenance language calibrated.** Current explanatory
48
+ documentation distinguishes Git commit-order evidence from the separate
49
+ project-record assertion that the preregistration was pushed before local
50
+ benchmark execution. The locked preregistration is unchanged.
51
+ - **Release automation now explicitly dispatches the release workflow after
52
+ creating a tag.** The initial 0.1.0.dev4 auto-tag path used the workflow
53
+ `GITHUB_TOKEN`; the resulting tag push did not trigger the tag-push release
54
+ workflow. The 0.1.0.dev4 GitHub Release was subsequently produced after a
55
+ manual workflow run. The current auto-tag workflow explicitly dispatches
56
+ `release.yml` so future automated release cuts do not rely on a suppressed
57
+ bot-authored push event.
12
58
 
13
59
  ## [0.1.0.dev4] - 2026-07-06
14
60
 
@@ -154,7 +200,8 @@ Date shown is the tag commit (`v0.1.0.dev0` -> 2a02954). `CITATION.cff` records
154
200
  `date-released: 2026-05-25`, when the core implementation landed; the tag was
155
201
  later placed on the metadata commit.
156
202
 
157
- [Unreleased]: https://github.com/smaniches/GROUPOID/compare/v0.1.0.dev4...HEAD
203
+ [Unreleased]: https://github.com/smaniches/GROUPOID/compare/v0.1.0.dev5...HEAD
204
+ [0.1.0.dev5]: https://github.com/smaniches/GROUPOID/compare/v0.1.0.dev4...v0.1.0.dev5
158
205
  [0.1.0.dev4]: https://github.com/smaniches/GROUPOID/compare/v0.1.0.dev2...v0.1.0.dev4
159
206
  [0.1.0.dev3]: https://github.com/smaniches/GROUPOID/compare/v0.1.0.dev2...af42cb4
160
207
  [0.1.0.dev2]: https://github.com/smaniches/GROUPOID/compare/v0.1.0.dev1...v0.1.0.dev2
@@ -0,0 +1,35 @@
1
+ cff-version: 1.2.0
2
+ message: "If you use this software, please cite the Concept DOI below so the citation resolves to the latest version."
3
+ type: software
4
+ title: "GROUPOID: Groupoid-based aggregation for federated learning on Riemannian manifolds"
5
+ version: 0.1.0.dev5
6
+ license: Apache-2.0
7
+ repository-code: "https://github.com/smaniches/GROUPOID"
8
+ doi: 10.5281/zenodo.20563974
9
+ identifiers:
10
+ - type: doi
11
+ value: 10.5281/zenodo.20563974
12
+ description: "Concept DOI (all versions)."
13
+ - type: doi
14
+ value: 10.5281/zenodo.20563975
15
+ description: "Version DOI (v0.1.0.dev0) - SUPERSEDED. Do not cite this historical snapshot for current results."
16
+ authors:
17
+ - family-names: Maniches
18
+ given-names: Santiago
19
+ email: santiago.maniches@gmail.com
20
+ affiliation: TOPOLOGICA LLC
21
+ orcid: "https://orcid.org/0009-0005-6480-1987"
22
+ keywords:
23
+ - federated learning
24
+ - Riemannian geometry
25
+ - groupoid
26
+ - sheaf theory
27
+ - holonomy
28
+ - topological data analysis
29
+ abstract: >-
30
+ Pre-alpha research prototype for groupoid-based aggregation on Riemannian
31
+ manifolds. The supported point-valued path uses explicit invertible point
32
+ actions, a basis-dependent cycle-holonomy defect diagnostic, the cellular
33
+ sheaf Laplacian, and Karcher mean aggregation. Tangent-vector parallel
34
+ transport utilities are provided separately. Earlier H^1-norm terminology
35
+ is superseded by the construct definition in CORRECTION_NOTICE.md.
@@ -88,8 +88,7 @@ please open an issue using the "Mathematical Correction" template.
88
88
  2. Merge the PR to `main`.
89
89
 
90
90
  That's it — merging is the release trigger, split across two workflows
91
- with one job each so the actual publish path has exactly one trigger to
92
- reason about:
91
+ with one job each:
93
92
 
94
93
  - `.github/workflows/auto-tag-release.yml` runs on every push to `main`
95
94
  that touches `pyproject.toml`. It reads the version out of
@@ -97,13 +96,23 @@ reason about:
97
96
  pushes one (as `github-actions[bot]`) after checking the version has a
98
97
  recognized format and that `CHANGELOG.md` documents it. A push with no
99
98
  version change, or whose tag already exists, is a no-op.
100
- - That tag push triggers `.github/workflows/release.yml` exactly as a
101
- manual `git tag vX.Y.Z && git push origin vX.Y.Z` would: build,
99
+ - It then explicitly dispatches `.github/workflows/release.yml` against
100
+ that tag with `create_github_release: true`, rather than relying on the
101
+ tag push itself to trigger it. This is necessary, not a stylistic
102
+ choice: GitHub suppresses push-triggered workflow runs caused by a
103
+ `GITHUB_TOKEN`-authored push, so the tag push from
104
+ `auto-tag-release.yml` does *not* fire `release.yml`'s own
105
+ `push.tags` trigger (`workflow_dispatch` is the documented exception to
106
+ that suppression). `release.yml` then does what it always did: build,
102
107
  provenance and SBOM attestation, publish to PyPI via Trusted
103
108
  Publishing, Sigstore signing, and a GitHub Release (which in turn
104
- triggers the Zenodo deposit for the new version DOI). `release.yml`
105
- itself is unchanged from before this automation existed — it only
106
- ever runs from a tag push or a manual `workflow_dispatch`.
109
+ triggers the Zenodo deposit for the new version DOI).
110
+ - `release.yml`'s `push.tags` trigger remains as a fallback/manual path —
111
+ a human pushing a tag with their own credentials is not subject to the
112
+ `GITHUB_TOKEN` suppression above, so that still works normally. Its
113
+ `workflow_dispatch` trigger (`create_github_release` left at its default
114
+ `false`) republishes an existing tag to PyPI only, without creating a
115
+ duplicate GitHub Release or Zenodo deposit.
107
116
 
108
117
  The actual PyPI publish step still runs under the `pypi` GitHub
109
118
  Environment; if that environment has required reviewers configured, the