groupoid 0.1.0.dev6__tar.gz → 0.1.0.dev7__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 (85) hide show
  1. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/.github/dependabot.yml +0 -1
  2. groupoid-0.1.0.dev7/.github/workflows/artifact-transfer.yml +46 -0
  3. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/.github/workflows/ci.yml +17 -37
  4. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/.github/workflows/docs-deploy.yml +1 -2
  5. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/.github/workflows/release.yml +18 -12
  6. groupoid-0.1.0.dev7/.zenodo.json +34 -0
  7. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/CHANGELOG.md +35 -1
  8. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/CITATION.cff +2 -1
  9. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/CONTRIBUTING.md +8 -6
  10. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/LIMITATIONS.md +17 -6
  11. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/PKG-INFO +7 -4
  12. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/README.md +6 -2
  13. groupoid-0.1.0.dev7/SECURITY.md +31 -0
  14. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/STATUS.md +6 -4
  15. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/codemeta.json +1 -1
  16. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/groupoid/__init__.py +1 -1
  17. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/groupoid/persistence.py +25 -12
  18. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/groupoid.egg-info/PKG-INFO +7 -4
  19. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/groupoid.egg-info/SOURCES.txt +1 -1
  20. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/groupoid.egg-info/requires.txt +0 -1
  21. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/pyproject.toml +1 -2
  22. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/tests/test_persistence.py +22 -10
  23. groupoid-0.1.0.dev6/.github/scripts/install_torch_cpu.sh +0 -25
  24. groupoid-0.1.0.dev6/.zenodo.json +0 -39
  25. groupoid-0.1.0.dev6/SECURITY.md +0 -43
  26. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  27. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  28. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/.github/ISSUE_TEMPLATE/mathematical_correction.md +0 -0
  29. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/.github/workflows/auto-tag-release.yml +0 -0
  30. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/.gitignore +0 -0
  31. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/.pre-commit-config.yaml +0 -0
  32. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/CORRECTION_NOTICE.md +0 -0
  33. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/LICENSE +0 -0
  34. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/PROBLEMS.md +0 -0
  35. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/REPRODUCE.md +0 -0
  36. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/benchmarks/__init__.py +0 -0
  37. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/benchmarks/conftest.py +0 -0
  38. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/benchmarks/test_karcher_benchmark.py +0 -0
  39. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/docs/api.md +0 -0
  40. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/docs/index.md +0 -0
  41. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/docs/overrides/404.html +0 -0
  42. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/docs/quickstart.md +0 -0
  43. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/docs/theory.md +0 -0
  44. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/examples/README.md +0 -0
  45. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/examples/__init__.py +0 -0
  46. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/examples/synthetic_curve_demo.py +0 -0
  47. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/experiments/PREREGISTRATION.md +0 -0
  48. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/experiments/README.md +0 -0
  49. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/experiments/RESULTS.md +0 -0
  50. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/experiments/analyze.py +0 -0
  51. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/experiments/results.json +0 -0
  52. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/experiments/run_benchmark.py +0 -0
  53. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/groupoid/aggregation.py +0 -0
  54. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/groupoid/cohomology.py +0 -0
  55. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/groupoid/groupoid.py +0 -0
  56. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/groupoid/laplacian.py +0 -0
  57. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/groupoid/manifold.py +0 -0
  58. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/groupoid/optimizer.py +0 -0
  59. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/groupoid/sheaf.py +0 -0
  60. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/groupoid/transport.py +0 -0
  61. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/groupoid.egg-info/dependency_links.txt +0 -0
  62. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/groupoid.egg-info/top_level.txt +0 -0
  63. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/mkdocs.yml +0 -0
  64. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/scripts/bind_release_sbom.py +0 -0
  65. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/scripts/reproduce_sheaf_laplacian.py +0 -0
  66. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/setup.cfg +0 -0
  67. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/tests/__init__.py +0 -0
  68. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/tests/test_aggregation_disconnected.py +0 -0
  69. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/tests/test_audit_regressions.py +0 -0
  70. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/tests/test_cohomology_cocycle.py +0 -0
  71. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/tests/test_coverage_gaps.py +0 -0
  72. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/tests/test_defensive_paths.py +0 -0
  73. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/tests/test_imports.py +0 -0
  74. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/tests/test_integration.py +0 -0
  75. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/tests/test_optimizer_validation.py +0 -0
  76. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/tests/test_persistence_dimension_labels.py +0 -0
  77. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/tests/test_pipeline_integration.py +0 -0
  78. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/tests/test_release_sbom_binding.py +0 -0
  79. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/tests/test_release_workflow_integrity.py +0 -0
  80. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/tests/test_scientific_corrections.py +0 -0
  81. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/tests/test_sheaf_laplacian.py +0 -0
  82. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/tests/test_smoke.py +0 -0
  83. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/tests/test_transport_validation.py +0 -0
  84. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/tests/theory/__init__.py +0 -0
  85. {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/tests/theory/test_mathematical_invariants.py +0 -0
@@ -42,7 +42,6 @@ updates:
42
42
  # of one PR per package.
43
43
  runtime:
44
44
  patterns:
45
- - "torch"
46
45
  - "geomstats"
47
46
  - "pymanopt"
48
47
  - "ripser"
@@ -0,0 +1,46 @@
1
+ name: Artifact transfer integrity
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ paths: [".github/workflows/**"]
7
+ pull_request:
8
+ branches: [main]
9
+ paths: [".github/workflows/**"]
10
+
11
+ permissions:
12
+ contents: read
13
+
14
+ jobs:
15
+ round-trip:
16
+ runs-on: ubuntu-latest
17
+ timeout-minutes: 5
18
+ steps:
19
+ - name: Create fixture and independent expected checksum
20
+ shell: bash
21
+ run: |
22
+ set -euo pipefail
23
+ mkdir -p payload/nested
24
+ printf '%s\n' "$GITHUB_SHA" > payload/nested/fixture.txt
25
+ (cd payload && sha256sum nested/fixture.txt) > expected.sha256
26
+ - name: Upload fixture
27
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
28
+ with:
29
+ name: artifact-transfer-proof
30
+ path: payload/
31
+ if-no-files-found: error
32
+ retention-days: 1
33
+ - name: Remove original fixture before downloading
34
+ run: rm -r payload
35
+ - name: Download fixture
36
+ uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
37
+ with:
38
+ name: artifact-transfer-proof
39
+ path: restored/
40
+ - name: Verify restored bytes and directory layout
41
+ shell: bash
42
+ run: |
43
+ set -euo pipefail
44
+ cd restored
45
+ test "$(find . -type f | wc -l)" -eq 1
46
+ sha256sum --check ../expected.sha256
@@ -14,7 +14,7 @@ jobs:
14
14
  runs-on: ubuntu-latest
15
15
  steps:
16
16
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
17
- - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
17
+ - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
18
18
  with:
19
19
  python-version: "3.11"
20
20
  - name: Install dependencies
@@ -24,8 +24,8 @@ jobs:
24
24
  # numpy / pydantic stubs (NDArray dtype args, the BaseModel base class).
25
25
  # Linting only against `ruff black mypy` left numpy/pydantic uninstalled,
26
26
  # so --strict raised disallow_subclassing_any on the BaseModel subclass.
27
- # Consistent with the test / security / docs jobs. CPU torch keeps it lean.
28
- .github/scripts/install_torch_cpu.sh
27
+ # All jobs now install the declared runtime dependencies, without
28
+ # the unused optional PyTorch backend.
29
29
  pip install -e ".[dev]"
30
30
  - name: Run ruff
31
31
  run: ruff check .
@@ -41,13 +41,12 @@ jobs:
41
41
  python-version: ["3.10", "3.11", "3.12"]
42
42
  steps:
43
43
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
44
- - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
44
+ - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
45
45
  with:
46
46
  python-version: ${{ matrix.python-version }}
47
47
  - name: Install dependencies
48
48
  run: |
49
49
  python -m pip install --upgrade pip
50
- .github/scripts/install_torch_cpu.sh
51
50
  pip install -e ".[dev]"
52
51
  - name: Run tests with coverage gate
53
52
  run: >-
@@ -59,7 +58,7 @@ jobs:
59
58
  runs-on: ubuntu-latest
60
59
  steps:
61
60
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
62
- - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
61
+ - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
63
62
  with:
64
63
  python-version: "3.11"
65
64
  - name: Install dependencies
@@ -68,42 +67,25 @@ jobs:
68
67
  # Install the project's real runtime closure so pip-audit scans the
69
68
  # packages GROUPOID actually depends on. The previous `safety` step
70
69
  # installed only the tools and never the project, so it audited the
71
- # wrong environment. CPU torch keeps the install lean.
72
- .github/scripts/install_torch_cpu.sh
70
+ # wrong environment. No unused PyTorch install is injected.
73
71
  pip install -e .
74
72
  pip install bandit pip-audit
75
- # Pin setuptools LAST. torch requires setuptools<82 and pulls in a
76
- # runner-bundled setuptools that can predate the PYSEC-2025-49 fix
77
- # (78.1.1); upgrading before `pip install -e .` is simply undone by torch.
78
- # The window >=78.1.1,<82 clears the advisory while staying
79
- # dependency-consistent with torch (verified: pip check clean).
80
- python -m pip install --upgrade "setuptools>=78.1.1,<82"
73
+ # Setuptools is build tooling, but remains in the audited CI environment.
74
+ # No torch<82 constraint remains; require the patched release.
75
+ python -m pip install --upgrade "setuptools>=84.1.0"
76
+ python -m pip check
81
77
  - name: Run bandit
82
78
  run: bandit -r groupoid/ -c pyproject.toml
83
79
  - name: Audit dependencies for known vulnerabilities (pip-audit)
84
- # pip-audit (OSV-backed, maintained) replaces the deprecated `safety check`
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. 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
80
+ # Hard gate on the installed runtime and audit-tool closure.
81
+ # No CVE/PYSEC ignore flags or waived vulnerable packages.
82
+ run: pip-audit
101
83
 
102
84
  pre-commit:
103
85
  runs-on: ubuntu-latest
104
86
  steps:
105
87
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
106
- - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
88
+ - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
107
89
  with:
108
90
  python-version: "3.11"
109
91
  - name: Install pre-commit
@@ -115,13 +97,12 @@ jobs:
115
97
  runs-on: ubuntu-latest
116
98
  steps:
117
99
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
118
- - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
100
+ - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
119
101
  with:
120
102
  python-version: "3.11"
121
103
  - name: Install dependencies
122
104
  run: |
123
105
  python -m pip install --upgrade pip
124
- .github/scripts/install_torch_cpu.sh
125
106
  pip install -e ".[dev]"
126
107
  # Benchmarks run for information only. There is no regression gate:
127
108
  # timing baselines recorded on one machine are not comparable to a
@@ -135,7 +116,7 @@ jobs:
135
116
  pytest benchmarks/ -v --benchmark-json=benchmark_results.json || true
136
117
  - name: Upload benchmark results
137
118
  if: always()
138
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
119
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
139
120
  with:
140
121
  name: benchmark-results
141
122
  path: benchmark_results.json
@@ -145,13 +126,12 @@ jobs:
145
126
  runs-on: ubuntu-latest
146
127
  steps:
147
128
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
148
- - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
129
+ - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
149
130
  with:
150
131
  python-version: "3.11"
151
132
  - name: Install dependencies
152
133
  run: |
153
134
  python -m pip install --upgrade pip
154
- .github/scripts/install_torch_cpu.sh
155
135
  pip install -e ".[docs]"
156
136
  - name: Build docs
157
137
  run: mkdocs build --strict
@@ -17,13 +17,12 @@ jobs:
17
17
  runs-on: ubuntu-latest
18
18
  steps:
19
19
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
20
- - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
20
+ - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
21
21
  with:
22
22
  python-version: "3.11"
23
23
  - name: Install dependencies
24
24
  run: |
25
25
  python -m pip install --upgrade pip
26
- .github/scripts/install_torch_cpu.sh
27
26
  pip install -e ".[docs]"
28
27
  - name: Build docs
29
28
  run: mkdocs build --strict
@@ -145,7 +145,7 @@ jobs:
145
145
  exit 1
146
146
  fi
147
147
  echo "release target ${RELEASE_TARGET} matches pyproject version ${version}"
148
- - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
148
+ - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
149
149
  with:
150
150
  python-version: "3.12"
151
151
  cache: pip
@@ -283,7 +283,7 @@ jobs:
283
283
  with:
284
284
  subject-path: "dist/*"
285
285
  - name: Upload dist artifacts
286
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
286
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
287
287
  with:
288
288
  name: release-dist
289
289
  path: dist/
@@ -300,11 +300,11 @@ jobs:
300
300
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
301
301
  with:
302
302
  ref: ${{ github.event.inputs.ref || github.ref }}
303
- - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
303
+ - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
304
304
  with:
305
305
  python-version: "3.12"
306
306
  cache: pip
307
- - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
307
+ - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
308
308
  with:
309
309
  name: release-dist
310
310
  path: dist
@@ -347,7 +347,7 @@ jobs:
347
347
  fi
348
348
  python scripts/bind_release_sbom.py --verify-only --sbom sbom.cdx.json --wheel "${wheels[0]}"
349
349
  - name: Upload SBOM artifact
350
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
350
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
351
351
  with:
352
352
  name: release-sbom
353
353
  path: sbom.cdx.json
@@ -365,11 +365,11 @@ jobs:
365
365
  id-token: write # attestation OIDC
366
366
  attestations: write # actions/attest
367
367
  steps:
368
- - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
368
+ - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
369
369
  with:
370
370
  name: release-dist
371
371
  path: dist
372
- - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
372
+ - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
373
373
  with:
374
374
  name: release-sbom
375
375
  path: .
@@ -407,7 +407,7 @@ jobs:
407
407
  name: pypi
408
408
  url: https://pypi.org/project/groupoid/
409
409
  steps:
410
- - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
410
+ - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
411
411
  with:
412
412
  name: release-dist
413
413
  path: dist
@@ -479,7 +479,7 @@ jobs:
479
479
  # step below still runs, so COMPLETE still proves PyPI is correct.
480
480
  - name: Publish to PyPI (Trusted Publishing, OIDC)
481
481
  if: needs.build.outputs.adopted != 'true'
482
- uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
482
+ uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 # release/v1
483
483
  with:
484
484
  packages-dir: dist
485
485
  print-hash: true
@@ -501,8 +501,14 @@ jobs:
501
501
  import hashlib, json, pathlib, sys, time, urllib.error, urllib.request
502
502
 
503
503
  dist = pathlib.Path("dist")
504
+ # PEP 740 .publish.attestation sidecars may be written to dist/ by
505
+ # the PyPI publishing action. They are attestations attached to the
506
+ # distributions, not separate package files in PyPI JSON urls.
507
+ # Compare every actual wheel/sdist byte without treating sidecars
508
+ # as missing distributions.
504
509
  local = {p.name: hashlib.sha256(p.read_bytes()).hexdigest()
505
- for p in sorted(dist.iterdir()) if p.is_file()}
510
+ for p in sorted(dist.iterdir())
511
+ if p.is_file() and p.name.endswith((".whl", ".tar.gz"))}
506
512
  wheel = next(n for n in local if n.endswith(".whl"))
507
513
  project, version = wheel.split("-")[0], wheel.split("-")[1]
508
514
  url = f"https://pypi.org/pypi/{project}/{version}/json"
@@ -565,11 +571,11 @@ jobs:
565
571
  id-token: write # Sigstore signing OIDC
566
572
  steps:
567
573
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
568
- - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
574
+ - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
569
575
  with:
570
576
  name: release-dist
571
577
  path: dist
572
- - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
578
+ - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
573
579
  with:
574
580
  name: release-sbom
575
581
  path: .
@@ -0,0 +1,34 @@
1
+ {
2
+ "upload_type": "software",
3
+ "title": "GROUPOID: Groupoid-based aggregation for federated learning on Riemannian manifolds",
4
+ "version": "0.1.0.dev7",
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>Corrective release v0.1.0.dev7:</strong> the finite H0 bottleneck distance to an empty diagram is now half the longest unmatched interval, rather than an incorrect zero (both comparison directions tested). The unused mandatory PyTorch dependency was removed, reducing installation and audit complexity, and CI dependency scanning now runs without vulnerability exceptions. Pre-alpha research maturity, known limits on the H0-only diagnostic, and historical experiment results are unchanged. Existing environments may retain PyTorch after an upgrade.</p><p><strong>Library correction in v0.1.0.dev6:</strong> in v0.1.0.dev5 and earlier, the pole ladder used a step schedule that did not reach the endpoint for more than one rung, and the function named schild_ladder was a pole-ladder variant rather than Schild's construction; tangent-vector transport results from those versions with n_rungs greater than 1, including the default two-rung tangent transport operator, are incorrect. v0.1.0.dev6 corrects both ladders and adds input validation for unknown transport methods, n_rungs below 1, negative, non-finite or zero-sum aggregation weights, and non-finite holonomy maps. The preregistered benchmark uses explicit transport maps, not the ladders; its stored results are unchanged and were reproduced bit-for-bit under the dev6 candidate. The project remains a pre-alpha research prototype.</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": "https://github.com/smaniches/GROUPOID",
30
+ "relation": "isSupplementTo",
31
+ "scheme": "url"
32
+ }
33
+ ]
34
+ }
@@ -8,6 +8,39 @@ this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [0.1.0.dev7] - 2026-10-29
12
+
13
+ Pre-alpha corrective release distributing the October 29 fixes already merged
14
+ on `main` (#77, #78 and #81). No new experiments, measured federated-learning
15
+ performance gains, or general convergence claims are introduced. Archived
16
+ preregistration and historical benchmark outputs remain unchanged.
17
+
18
+ ### Fixed
19
+
20
+ - **Finite H0 bottleneck versus an empty diagram (#77).** The previous
21
+ `track_divergence` fallback returned `0.0` when either finite H0 diagram
22
+ was empty. The mathematically correct cost is half the largest unmatched
23
+ persistence interval. For a two-point cloud separated by 4 versus a
24
+ singleton, the result is now **2.0** in either direction, not zero;
25
+ empty-versus-empty remains zero. Regression tests cover both directions.
26
+ The metric remains an H0-only geometric diagnostic, **not** a certificate
27
+ of federated model convergence or predictive stability.
28
+
29
+ ### Changed
30
+
31
+ - **Removed an unused mandatory dependency (#81).** `torch` was declared as
32
+ required but is not imported by the implemented library. New installations
33
+ no longer require PyTorch or its dependency graph. The security CI installs
34
+ the actual supported runtime closure, requires patched `setuptools`, checks
35
+ `pip check`, and runs `pip-audit` without advisory ignores. The full
36
+ Python 3.10–3.12 tests, docs, and code-quality checks passed on `main`.
37
+ **Compatibility note:** PyTorch is no longer installed automatically;
38
+ callers requiring it for their own workflows must install it separately.
39
+ Upgrading an existing environment does not uninstall previously installed
40
+ PyTorch or other packages; independently audit those environments.
41
+ - Updated SHA-pinned `actions/setup-python` and PyPI publishing action (#78).
42
+ No algorithmic behavior was changed by this CI maintenance.
43
+
11
44
  ## [0.1.0.dev6]
12
45
 
13
46
  Pre-alpha development release distributing the transport and validation fixes
@@ -268,7 +301,8 @@ Date shown is the tag commit (`v0.1.0.dev0` -> 2a02954). `CITATION.cff` records
268
301
  `date-released: 2026-05-25`, when the core implementation landed; the tag was
269
302
  later placed on the metadata commit.
270
303
 
271
- [Unreleased]: https://github.com/smaniches/GROUPOID/compare/v0.1.0.dev6...HEAD
304
+ [Unreleased]: https://github.com/smaniches/GROUPOID/compare/v0.1.0.dev7...HEAD
305
+ [0.1.0.dev7]: https://github.com/smaniches/GROUPOID/compare/v0.1.0.dev6...v0.1.0.dev7
272
306
  [0.1.0.dev6]: https://github.com/smaniches/GROUPOID/compare/v0.1.0.dev5...v0.1.0.dev6
273
307
  [0.1.0.dev5]: https://github.com/smaniches/GROUPOID/compare/v0.1.0.dev4...v0.1.0.dev5
274
308
  [0.1.0.dev4]: https://github.com/smaniches/GROUPOID/compare/v0.1.0.dev2...v0.1.0.dev4
@@ -2,7 +2,8 @@ cff-version: 1.2.0
2
2
  message: "If you use this software, please cite the Concept DOI below so the citation resolves to the latest version."
3
3
  type: software
4
4
  title: "GROUPOID: Groupoid-based aggregation for federated learning on Riemannian manifolds"
5
- version: 0.1.0.dev6
5
+ version: 0.1.0.dev7
6
+ date-released: 2026-10-29
6
7
  license: Apache-2.0
7
8
  repository-code: "https://github.com/smaniches/GROUPOID"
8
9
  doi: 10.5281/zenodo.20563974
@@ -80,18 +80,20 @@ please open an issue using the "Mathematical Correction" template.
80
80
  1. Open a release-cut PR that bumps the version across every metadata
81
81
  file that carries it: `pyproject.toml`, `groupoid/__init__.py`,
82
82
  `codemeta.json`, `CITATION.cff` (version and `date-released`),
83
- `STATUS.md` (both mentions), and `.zenodo.json` (version, the erratum
84
- description, and `isNewVersionOf` advanced to the prior version's
85
- Zenodo DOI). Roll `CHANGELOG.md`'s `Unreleased` section into a new
86
- version heading with compare links. See PRs #40 or #48 for the
83
+ `STATUS.md` (version), and `.zenodo.json` (version, the correction
84
+ description, and its concept DOI `isVersionOf` relationship). Only include
85
+ `isNewVersionOf` when the immediately preceding record DOI has been
86
+ independently verified; never inherit a stale predecessor. Roll
87
+ `CHANGELOG.md`'s `Unreleased` section into a new version heading with
88
+ compare links. See PRs #40 or #48 for the
87
89
  template.
88
90
  2. Merge the PR to `main`.
89
91
 
90
92
  That's it — merging is the release trigger, split across two workflows
91
93
  with one job each:
92
94
 
93
- - `.github/workflows/auto-tag-release.yml` runs on every push to `main`
94
- that touches `pyproject.toml`. It reads the version out of
95
+ - `.github/workflows/auto-tag-release.yml` runs after successful CI for a
96
+ push to the **current** `main` commit. It reads the version out of
95
97
  `pyproject.toml`, and if no `v<version>` tag exists yet, it creates and
96
98
  pushes one (as `github-actions[bot]`) after checking the version has a
97
99
  recognized format and that `CHANGELOG.md` documents it. A push with no
@@ -89,6 +89,15 @@ system. The following limitations apply.
89
89
  The informative loop signal lives in `max_persistence` / the finite
90
90
  bars; meaningful component counts require passing a finite
91
91
  `max_edge_length` between the intra- and inter-cluster scales.
92
+ - The opt-in `track_divergence` metric measures only changes in the
93
+ **finite H0** persistence diagram. It is invariant under translations,
94
+ rotations, and other pairwise-distance-preserving changes, and ignores
95
+ changes confined to H1 or higher homology. Thus a zero bottleneck
96
+ does **not** demonstrate client-model convergence or predictive
97
+ stability. If one diagram has no finite H0 bars, its distance to a
98
+ nonempty diagram is half the longest unmatched interval's lifetime,
99
+ not zero. Validation against model optimization would require
100
+ independent parameter-space or task-loss evidence.
92
101
  - No formal convergence rate analysis exists for the groupoid
93
102
  aggregation method.
94
103
 
@@ -98,12 +107,14 @@ system. The following limitations apply.
98
107
  - Requires `scipy < 1.16` for the same reason.
99
108
  - The `ripser` and `persim` packages may have build issues on
100
109
  some platforms (C++ compilation required).
101
- - `torch`, `pymanopt`, `POT`, and `einops` are declared as required
102
- runtime dependencies but are not imported by any current code path (the full
103
- test suite passes with all four uninstalled). Installing `groupoid` therefore
104
- pulls in packages, most notably `torch`, that the current code does not use.
105
- The `torch` advisory tracked in SECURITY.md applies to the installed
106
- dependency, not to any code path GROUPOID exercises.
110
+ - The former required `torch` dependency was removed because the current
111
+ implementation and entire documented test suite do not import it. It had
112
+ constrained `filelock` and `setuptools` to vulnerable versions; no current
113
+ GROUPOID code requires PyTorch. Future PyTorch functionality requires
114
+ a new separately tested dependency contract.
115
+ - `pymanopt`, `POT`, and `einops` are still declared as required runtime
116
+ dependencies but not imported in the current implementation. Their removal
117
+ is a separate compatibility change, not part of this security correction.
107
118
 
108
119
  ## Test coverage
109
120
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: groupoid
3
- Version: 0.1.0.dev6
3
+ Version: 0.1.0.dev7
4
4
  Summary: Research prototype: groupoid-based aggregation for federated learning on Riemannian manifolds
5
5
  Author-email: Santiago Maniches <santiago.maniches@gmail.com>
6
6
  License-Expression: Apache-2.0
@@ -30,7 +30,6 @@ Requires-Dist: networkx>=3.0
30
30
  Requires-Dist: einops>=0.7
31
31
  Requires-Dist: numpy<2.0,>=1.24
32
32
  Requires-Dist: scipy<1.16,>=1.10
33
- Requires-Dist: torch>=2.0
34
33
  Requires-Dist: pydantic>=2.0
35
34
  Requires-Dist: loguru>=0.7
36
35
  Provides-Extra: privacy
@@ -139,8 +138,12 @@ integration tests:
139
138
  retains a homology-dimension label, and `track_divergence` compares H0
140
139
  against H0 only (it does not pool features across dimensions); this is
141
140
  verified against an independent minimum-spanning-tree reconstruction of
142
- the H0 diagram. The Betti numbers are degenerate under the default
143
- `thresh=inf` filtration; see [LIMITATIONS.md](LIMITATIONS.md).
141
+ the H0 diagram. A zero value is a geometric diagnostic, **not** a
142
+ certificate of client-model convergence: translations and H1-only
143
+ changes can leave it unchanged. A finite H0 diagram compared with
144
+ an empty one incurs the nonzero bottleneck-to-diagonal cost.
145
+ The Betti numbers are degenerate under the default `thresh=inf`
146
+ filtration; see [LIMITATIONS.md](LIMITATIONS.md).
144
147
 
145
148
  ## Implemented and validated, not yet integrated
146
149
 
@@ -74,8 +74,12 @@ integration tests:
74
74
  retains a homology-dimension label, and `track_divergence` compares H0
75
75
  against H0 only (it does not pool features across dimensions); this is
76
76
  verified against an independent minimum-spanning-tree reconstruction of
77
- the H0 diagram. The Betti numbers are degenerate under the default
78
- `thresh=inf` filtration; see [LIMITATIONS.md](LIMITATIONS.md).
77
+ the H0 diagram. A zero value is a geometric diagnostic, **not** a
78
+ certificate of client-model convergence: translations and H1-only
79
+ changes can leave it unchanged. A finite H0 diagram compared with
80
+ an empty one incurs the nonzero bottleneck-to-diagonal cost.
81
+ The Betti numbers are degenerate under the default `thresh=inf`
82
+ filtration; see [LIMITATIONS.md](LIMITATIONS.md).
79
83
 
80
84
  ## Implemented and validated, not yet integrated
81
85
 
@@ -0,0 +1,31 @@
1
+ # Security Policy
2
+
3
+ ## Scope
4
+
5
+ GROUPOID is a research prototype. It is not designed for deployment in
6
+ security-sensitive environments.
7
+
8
+ ## Reporting a vulnerability
9
+
10
+ If you discover a security issue, please report it by emailing
11
+ santiago.maniches@gmail.com. Do not open a public issue.
12
+
13
+ ## Supported versions
14
+
15
+ Only the latest commit on `main` is supported. There are no stable
16
+ releases.
17
+
18
+ ## Known limitations
19
+
20
+ - No differential privacy is implemented despite optional dependencies
21
+ being listed. Do not rely on this software for privacy guarantees.
22
+ - Input validation is minimal. The library assumes trusted inputs from
23
+ the caller.
24
+ - The older dependency closure required `torch` despite no implemented code
25
+ path importing it. This pulled in vulnerable `filelock` and constrained
26
+ `setuptools` below patched releases. The current source dependency closure
27
+ removes that unused requirement and the corresponding CI installer. CI
28
+ verifies the actual installed environment using `pip check` and a hard
29
+ `pip-audit` gate without CVE/PYSEC ignore flags. This does not retroactively
30
+ change any published package version, and it does not establish that other
31
+ environments or future dependency updates are vulnerability-free.
@@ -25,9 +25,11 @@
25
25
  against H0 only; this is verified against an independent
26
26
  minimum-spanning-tree reconstruction of the H0 diagram (the finite H0
27
27
  death times equal the MST edge weights), and an H1-only change is shown
28
- not to leak into the H0 divergence. It is wired into the aggregation
29
- pipeline via the aggregator's opt-in `track_divergence` flag. See the
30
- Betti-degeneracy caveat in LIMITATIONS.md.
28
+ not to leak into the H0 divergence. Degenerate one-empty-diagram
29
+ comparisons use the nonzero bottleneck-to-diagonal distance.
30
+ It is wired into the aggregator via the opt-in `track_divergence`
31
+ flag but cannot establish model convergence. See the Betti-degeneracy
32
+ caveat in LIMITATIONS.md.
31
33
  - The optimizer module is validated against known-correct references:
32
34
  Riemannian SGD (with and without momentum) and Adam descend to a known
33
35
  target on S^2 (geodesic-distance objective), and the momentum/first-
@@ -88,5 +90,5 @@ not capture.
88
90
 
89
91
  ## Versioning
90
92
 
91
- This project uses `0.1.0.dev6` to indicate pre-release development.
93
+ This project uses `0.1.0.dev7` to indicate pre-release development.
92
94
  The API is unstable and will change without notice.
@@ -6,7 +6,7 @@
6
6
  "codeRepository": "https://github.com/smaniches/GROUPOID",
7
7
  "programmingLanguage": "Python",
8
8
  "license": "Apache-2.0",
9
- "version": "0.1.0.dev6",
9
+ "version": "0.1.0.dev7",
10
10
  "author": [
11
11
  {
12
12
  "@type": "Person",
@@ -1,6 +1,6 @@
1
1
  """GROUPOID: Groupoid-based federated learning with Riemannian geometry."""
2
2
 
3
- __version__ = "0.1.0.dev6"
3
+ __version__ = "0.1.0.dev7"
4
4
 
5
5
  from groupoid.aggregation import (
6
6
  DisconnectedClientGraphError,
@@ -1,14 +1,17 @@
1
1
  """Persistent homology for federated model divergence detection.
2
2
 
3
3
  Applies topological data analysis to track how the distribution of
4
- client model parameters evolves across federated rounds. The key
5
- invariant: if the persistence diagram is stable across rounds, the
6
- federation is converging; if new topological features appear, clients
7
- are diverging into distinct clusters.
8
-
9
- Uses Vietoris-Rips filtration on the space of model parameters to
10
- compute persistence diagrams, then measures stability via bottleneck
11
- and Wasserstein distances.
4
+ client model parameters evolves across federated rounds. A stable
5
+ persistence diagram is not a convergence certificate:
6
+ pairwise-distance-preserving translations/rotations and some genuine
7
+ model-configuration changes can have unchanged diagrams. The tool
8
+ reports a dimension-specific geometric diagnostic, not convergence or
9
+ model-performance evidence.
10
+
11
+ Uses Vietoris-Rips filtration on point clouds of model parameters
12
+ and compares finite H0 diagrams across rounds with bottleneck distance.
13
+ It does not calculate Wasserstein distance or test higher-dimensional
14
+ changes in the inter-round comparison.
12
15
  """
13
16
 
14
17
  from __future__ import annotations
@@ -160,9 +163,9 @@ def track_divergence(
160
163
  ) -> PersistenceSummary:
161
164
  """Track federation divergence across rounds.
162
165
 
163
- Computes persistence of current parameter distribution and, if a
164
- previous summary exists, computes the bottleneck distance to
165
- measure how much the topological structure has changed.
166
+ Computes persistence of a point cloud and, with a prior summary,
167
+ compares its finite H0 diagrams by bottleneck distance. A zero
168
+ value does not imply convergence of the underlying client models.
166
169
 
167
170
  Parameters
168
171
  ----------
@@ -198,6 +201,16 @@ def track_divergence(
198
201
  summary.bottleneck_to_previous = float(dist)
199
202
  logger.info("Bottleneck distance to previous round: {:.4f}", dist)
200
203
  else:
201
- summary.bottleneck_to_previous = 0.0
204
+ # The bottleneck distance to the empty diagram is NOT zero:
205
+ # every unmatched finite interval must match the diagonal,
206
+ # costing half its lifetime in L-infinity. This applies in
207
+ # either direction. Only two empty diagrams have distance 0.
208
+ unmatched = current_h0 if len(current_h0) > 0 else prev_h0
209
+ if len(unmatched) > 0:
210
+ summary.bottleneck_to_previous = float(
211
+ np.max(unmatched[:, 1] - unmatched[:, 0]) / 2.0
212
+ )
213
+ else:
214
+ summary.bottleneck_to_previous = 0.0
202
215
 
203
216
  return summary
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: groupoid
3
- Version: 0.1.0.dev6
3
+ Version: 0.1.0.dev7
4
4
  Summary: Research prototype: groupoid-based aggregation for federated learning on Riemannian manifolds
5
5
  Author-email: Santiago Maniches <santiago.maniches@gmail.com>
6
6
  License-Expression: Apache-2.0
@@ -30,7 +30,6 @@ Requires-Dist: networkx>=3.0
30
30
  Requires-Dist: einops>=0.7
31
31
  Requires-Dist: numpy<2.0,>=1.24
32
32
  Requires-Dist: scipy<1.16,>=1.10
33
- Requires-Dist: torch>=2.0
34
33
  Requires-Dist: pydantic>=2.0
35
34
  Requires-Dist: loguru>=0.7
36
35
  Provides-Extra: privacy
@@ -139,8 +138,12 @@ integration tests:
139
138
  retains a homology-dimension label, and `track_divergence` compares H0
140
139
  against H0 only (it does not pool features across dimensions); this is
141
140
  verified against an independent minimum-spanning-tree reconstruction of
142
- the H0 diagram. The Betti numbers are degenerate under the default
143
- `thresh=inf` filtration; see [LIMITATIONS.md](LIMITATIONS.md).
141
+ the H0 diagram. A zero value is a geometric diagnostic, **not** a
142
+ certificate of client-model convergence: translations and H1-only
143
+ changes can leave it unchanged. A finite H0 diagram compared with
144
+ an empty one incurs the nonzero bottleneck-to-diagonal cost.
145
+ The Betti numbers are degenerate under the default `thresh=inf`
146
+ filtration; see [LIMITATIONS.md](LIMITATIONS.md).
144
147
 
145
148
  ## Implemented and validated, not yet integrated
146
149
 
@@ -19,7 +19,7 @@ pyproject.toml
19
19
  .github/ISSUE_TEMPLATE/bug_report.md
20
20
  .github/ISSUE_TEMPLATE/feature_request.md
21
21
  .github/ISSUE_TEMPLATE/mathematical_correction.md
22
- .github/scripts/install_torch_cpu.sh
22
+ .github/workflows/artifact-transfer.yml
23
23
  .github/workflows/auto-tag-release.yml
24
24
  .github/workflows/ci.yml
25
25
  .github/workflows/docs-deploy.yml
@@ -7,7 +7,6 @@ networkx>=3.0
7
7
  einops>=0.7
8
8
  numpy<2.0,>=1.24
9
9
  scipy<1.16,>=1.10
10
- torch>=2.0
11
10
  pydantic>=2.0
12
11
  loguru>=0.7
13
12
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "groupoid"
7
- version = "0.1.0.dev6"
7
+ version = "0.1.0.dev7"
8
8
  description = "Research prototype: groupoid-based aggregation for federated learning on Riemannian manifolds"
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
@@ -45,7 +45,6 @@ dependencies = [
45
45
  "einops>=0.7",
46
46
  "numpy>=1.24,<2.0",
47
47
  "scipy>=1.10,<1.16",
48
- "torch>=2.0",
49
48
  "pydantic>=2.0",
50
49
  "loguru>=0.7",
51
50
  ]
@@ -11,7 +11,8 @@ against point clouds with known structure, not just line execution:
11
11
  scales -- this validates component counting.
12
12
  - ``track_divergence`` is exercised across all three of its branches: no
13
13
  previous summary, a previous summary with non-empty H0 diagrams (bottleneck
14
- computed), and the degenerate empty-diagram fallback.
14
+ computed), and mathematically correct empty-to-nonempty and
15
+ empty-to-empty bottleneck distances.
15
16
 
16
17
  Caveat validated and documented: with the default ``thresh=inf`` filtration,
17
18
  ``betti_0``/``betti_1`` count only bars that die at infinity, so on a single
@@ -126,12 +127,23 @@ class TestTrackDivergence:
126
127
  assert shifted.bottleneck_to_previous == pytest.approx(0.0, abs=1e-6)
127
128
 
128
129
  @pytest.mark.filterwarnings("ignore:The input point cloud has more columns:UserWarning")
129
- def test_empty_finite_diagram_falls_back_to_zero(self):
130
- """When the current cloud has no finite H0 bars (a single point has
131
- only the infinite component), the empty-diagram branch sets the
132
- bottleneck distance to 0.0 rather than calling persim. (ripser emits a
133
- benign columns>rows warning for the degenerate single-point input.)"""
134
- prev = track_divergence(_two_clusters(seed=4))
135
- single_point = np.array([[0.0, 0.0]])
136
- result = track_divergence(single_point, previous_summary=prev)
137
- assert result.bottleneck_to_previous == 0.0
130
+ def test_single_empty_diagram_has_nonzero_bottleneck(self):
131
+ """A finite H0 bar [0, 4] must match the diagonal at cost 2."""
132
+ pair = np.array([[0.0, 0.0], [4.0, 0.0]])
133
+ singleton = np.array([[0.0, 0.0]])
134
+ pair_summary = track_divergence(pair, max_dim=0)
135
+ single_summary = track_divergence(singleton, max_dim=0)
136
+
137
+ # Both orders use the same symmetric bottleneck metric.
138
+ going_to_empty = track_divergence(singleton, previous_summary=pair_summary, max_dim=0)
139
+ coming_from_empty = track_divergence(pair, previous_summary=single_summary, max_dim=0)
140
+ assert going_to_empty.bottleneck_to_previous == pytest.approx(2.0, abs=1e-6)
141
+ assert coming_from_empty.bottleneck_to_previous == pytest.approx(2.0, abs=1e-6)
142
+
143
+ @pytest.mark.filterwarnings("ignore:The input point cloud has more columns:UserWarning")
144
+ def test_two_empty_finite_h0_diagrams_have_zero_bottleneck(self):
145
+ """Two singleton point clouds have no finite H0 bars at all."""
146
+ singleton = np.array([[0.0, 0.0]])
147
+ prior = track_divergence(singleton, max_dim=0)
148
+ current = track_divergence(singleton + 100.0, previous_summary=prior, max_dim=0)
149
+ assert current.bottleneck_to_previous == 0.0
@@ -1,25 +0,0 @@
1
- #!/usr/bin/env bash
2
- # Installs CPU-only torch from the PyTorch index with retries.
3
- #
4
- # That index (download.pytorch.org/whl/cpu, Cloudflare R2-backed as
5
- # download-r2.pytorch.org) intermittently drops the TLS handshake
6
- # (SSLV3_ALERT_HANDSHAKE_FAILURE) from GitHub-hosted runners. pip's own
7
- # built-in retries reuse the same connection pool within one process and
8
- # do not help here; a fresh `pip install` invocation gets a fresh TLS
9
- # connection and has in practice succeeded where the retries within a
10
- # single invocation did not. Five attempts with linear backoff (10s, 20s,
11
- # 30s, 40s) before giving up.
12
- set -euo pipefail
13
-
14
- max_attempts=5
15
- attempt=1
16
- until pip install torch --index-url https://download.pytorch.org/whl/cpu; do
17
- if [ "${attempt}" -ge "${max_attempts}" ]; then
18
- echo "::error::torch install failed after ${max_attempts} attempts" >&2
19
- exit 1
20
- fi
21
- sleep_seconds=$((attempt * 10))
22
- echo "torch install attempt ${attempt}/${max_attempts} failed; retrying in ${sleep_seconds}s" >&2
23
- sleep "${sleep_seconds}"
24
- attempt=$((attempt + 1))
25
- done
@@ -1,39 +0,0 @@
1
- {
2
- "upload_type": "software",
3
- "title": "GROUPOID: Groupoid-based aggregation for federated learning on Riemannian manifolds",
4
- "version": "0.1.0.dev6",
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>Library correction in v0.1.0.dev6:</strong> in v0.1.0.dev5 and earlier, the pole ladder used a step schedule that did not reach the endpoint for more than one rung, and the function named schild_ladder was a pole-ladder variant rather than Schild's construction; tangent-vector transport results from those versions with n_rungs greater than 1, including the default two-rung tangent transport operator, are incorrect. v0.1.0.dev6 corrects both ladders and adds input validation for unknown transport methods, n_rungs below 1, negative, non-finite or zero-sum aggregation weights, and non-finite holonomy maps. The preregistered benchmark uses explicit transport maps, not the ladders; its stored results are unchanged and were reproduced bit-for-bit under the dev6 candidate. The project remains a pre-alpha research prototype.</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.21963047",
30
- "relation": "isNewVersionOf",
31
- "scheme": "doi"
32
- },
33
- {
34
- "identifier": "https://github.com/smaniches/GROUPOID",
35
- "relation": "isSupplementTo",
36
- "scheme": "url"
37
- }
38
- ]
39
- }
@@ -1,43 +0,0 @@
1
- # Security Policy
2
-
3
- ## Scope
4
-
5
- GROUPOID is a research prototype. It is not designed for deployment in
6
- security-sensitive environments.
7
-
8
- ## Reporting a vulnerability
9
-
10
- If you discover a security issue, please report it by emailing
11
- santiago.maniches@gmail.com. Do not open a public issue.
12
-
13
- ## Supported versions
14
-
15
- Only the latest commit on `main` is supported. There are no stable
16
- releases.
17
-
18
- ## Known limitations
19
-
20
- - No differential privacy is implemented despite optional dependencies
21
- being listed. Do not rely on this software for privacy guarantees.
22
- - Input validation is minimal. The library assumes trusted inputs from
23
- the caller.
24
- - The `torch` runtime dependency is flagged by `pip-audit` for advisory
25
- CVE-2025-3000. OSV expresses its affected range as a Git/commit range whose
26
- listed versions run up to v2.6.0, with no published `fixed` release. Because
27
- that upper bound is a source commit rather than a resolvable version,
28
- `pip-audit` conservatively flags the installed `torch` (currently 2.12.0, past
29
- the listed v2.6.0 affected range) instead of clearing it. CI tracks the
30
- advisory via a documented `--ignore-vuln`; there is no fixed release to
31
- upgrade to, and the ignore is revisited when upstream publishes one. The
32
- advisory concerns crafted-checkpoint / JIT deserialization (`torch.load` and
33
- related paths), which GROUPOID's own code does not exercise.
34
- - The CI-pinned `setuptools` (held to `>=78.1.1,<82` because `torch` requires
35
- `setuptools<82`) is flagged by `pip-audit` for advisory PYSEC-2026-3447
36
- (CVE-2026-59890): prior to setuptools 83.0.0, `MANIFEST.in` exclude,
37
- `global-exclude`, `recursive-exclude`, and `prune` directives could be
38
- bypassed on macOS APFS/HFS+ by NFD-normalized file names, packing excluded
39
- files into source distributions. The fixed release (83.0.0) is outside the
40
- window torch permits, and the affected code path requires building an sdist
41
- on a macOS filesystem — all GROUPOID CI and release builds run on
42
- `ubuntu-latest`. CI tracks the advisory via a documented `--ignore-vuln`;
43
- the ignore is revisited once torch allows `setuptools>=83`.
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes