Commit 7b7677f
authored
Lockdown round 2: PGP verification, hardened runners, retire risky workflows (#25)
* Lockdown round 2: PGP verification, hardened runners, retire risky workflows
Builds on the lockdown landed in #19. Four independent improvements:
1. Maven PGP verification scaffold (closes Finding 5 path).
- pom.xml: opt-in `verify-pgp` profile wires
org.simplify4u.plugins:pgpverify-maven-plugin:1.18.3 with strict
settings (failNoSignature, failWeakSignature, failNoKey,
verifyPomFiles, verifyPlugins, verifyPluginDependencies).
- .maven-keys.list: keysmap header with format docs, sentinels,
refresh procedure, and a trust-anchor URL per direct dep.
- scripts/security/maven-pgp-{bootstrap,verify}: helpers in the
pin-gh-actions style.
- .github/workflows/verify-maven-pgp.yml: CI gate triggered on edits
to pom.xml / keysmap / helper scripts.
Migration to enforced (follow-up PR): populate keysmap via
bootstrap, cross-check fingerprints, flip profile to
activeByDefault, add workflow to required status checks.
2. Hardened runner group on every job in every workflow.
`runs-on: larger` → `runs-on: { group:
databricks-protected-runner-group, labels: linux-ubuntu-latest }`.
Trust-boundary now an org-controlled property; Labs lockdown policy.
3. Retire the workflow_run Pwn-Request shape (closes Finding 1).
Delete .github/workflows/doc-tests.yml in full. Repo now contains
zero workflow_run triggers — cannot be re-introduced by toggling an
`if:`. CODEOWNERS forces any new workflow through review.
4. Retire disabled release workflows (closes Finding 8).
Delete publish-maven.yml and release.yml. Re-introducing publishing
requires a deliberate new PR rather than flipping `if: false`.
Net: workflow surface area shrinks ~300 lines, four findings closed
or on-track, no findings worsened. Full diff analysis at
prompts/security/2026-05-05-supply-chain-security-analysis-diff.md.
Co-authored-by: Isaac
* Lockdown round 3: JFrog OIDC, DBR 17.3 pins, local-act validation, real CI fixes
Round 3 builds on the lockdown landed in PR #25 by closing the remaining
HIGH finding from review (JFrog OIDC for pip/Maven), aligning every pinned
package version against DBR 17.3 LTS, and adding a local CI dry-run scaffold
that has already paid for itself by surfacing three real workflow bugs.
1. JFrog OIDC for pip + Maven + npm.
- New composite action .github/actions/jfrog-auth/ (ported from UCX).
Auto-detects mvn/pip3/npm/sbt/coursier/uv/bun on PATH and configures
each via ~/.m2/settings.xml, PIP_CONFIG_FILE, ~/.npmrc, NETRC.
- Wired into scala_build + python_build composites (after setup-python
so mvn + pip3 are detectable), and directly into verify-maven-pgp.yml
(defense-in-depth: artifacts via JFrog mirror, sigs verified by
pgpverify-maven-plugin) and deploy-docs.yml (npm).
- Added permissions: id-token: write to all 8 jobs that pip/mvn/npm
install. Verified pom.xml has no resolution-time <repositories> so
<mirrorOf>*</mirrorOf> is safe.
2. Mixed runner strategy (fixes wrong-org regression from PR #25).
- PR #25 used databricks-protected-runner-group (databricks org, not
registered for databrickslabs). Replaced repo-wide:
- Heavy jobs (mvn install + GDAL + pytest): larger-runners / larger
- Light jobs (artifact handling, dep resolution, npm only):
databrickslabs-protected-runner-group / linux-ubuntu-latest
- Per-job justification comments added so the heavy/light split is
self-explanatory.
- .github/CODECOV.md and .github/docs/ci-runner-and-cache.md updated
to describe the mixed strategy.
3. DBR 17.3 LTS package alignment across CI / Dockerfile / cluster init /
lint venv / notebook tests.
Source: https://docs.databricks.com/aws/en/release-notes/runtime/17.3lts
- DBR-aligned: pip 25.0.1, setuptools 74.0.0, wheel 0.45.1, cython 3.0.12,
numpy 2.1.3, pandas 2.2.3, requests 2.32.3, pyspark 4.0.0, ipython
8.30.0, ipykernel 6.29.5, ipywidgets 7.8.1, jupyterlab 4.3.4,
jupyter_server 2.14.1, jupyter_core 5.7.2, jupyter_client 8.6.3,
tornado 6.4.2, nbformat 5.10.4, nbconvert 7.16.4.
- Not in DBR, pinned to PyPI stable: build 1.4.4, pytest-cov 7.1.0,
geopandas 1.1.3 (pandas 2.2.x compatible), keplergl 0.3.2 (last
stable compatible with ipywidgets 7.x line).
- Intentional divergences: pytest 8.4.2 (CI matrix; we don't run
tests in DBR), pyzmq 25.1.1 (Dockerfile only; ZMQ workaround).
4. SECURITY.md at repo root, modeled on UCX's template (Beta-aware).
5. New gbx:versions:audit Cursor command — inventories every place a
pinned package version lives so future DBR LTS bumps run end-to-end:
read the audit, ask Claude to bump given the new release notes URL,
re-run the audit to verify nothing drifted.
6. Local-act CI dry-run scaffold (scripts/ci-local/, .cursor/commands/
gbx-ci-act). Lets us iterate on workflow changes locally before
re-enabling GHA. Real .github/ tree is never modified; the scaffold
uses a workspace mirror at .cache/act-workspace/ with stub overlays
for jfrog-auth (no OIDC locally) and upload_artifacts (act's bundled
protobuf doesn't grok upload-artifact@v4's mime_type field). Runner
image is catthehacker/ubuntu:runner-24.04 with corp pip/maven/npm
proxies pre-baked. End-to-end validated:
- verify-maven-pgp.yml: runs cleanly through to the pgp-verify step;
fails by design on empty .maven-keys.list (the correct scaffold
signal). Confirms the verify-maven-pgp workflow itself is wired
correctly — when GHA is re-enabled and PR #25 merges to master,
the workflow will fire on the merge commit (it touches pom.xml +
workflow + scripts/security/maven-pgp-*) and fail with the
expected wall of "Unsigned artifact ... in keys map" errors,
turning green only after the follow-up keysmap-population PR.
- build_scala.yml: 774/774 tests pass via local act (47 min under
Rosetta amd64 emulation on Apple Silicon).
- build_python.yml: 101/101 tests pass via local act (~10 min);
job exits clean.
7. Real CI bugs surfaced + fixed in this commit:
a. pom.xml: pgpverify-maven-plugin 1.18.3 → 1.19.1. Version 1.18.3
doesn't exist on Maven Central (maven org/simplify4u/plugins
jumps 1.18.2 → 1.19.0). Would have failed real CI as
"Could not find artifact" on first run.
b. pom.xml: pinned <scalacPluginVersion>2.3.0</scalacPluginVersion>
in both scoverage-maven-plugin blocks. scoverage-maven-plugin
2.1.5 defaults scalacPluginVersion to 2.5.2, which is NOT
published on Maven Central for scala_2.13.12 (only up through
2.3.0 exists). Would have failed real CI as "Could not find
artifact org.scoverage:scalac-scoverage-plugin_2.13.12:jar:2.5.2".
c. scala_build/action.yml: replaced sed | head -1 with awk in the
coverage-summary step. With GHA's default bash -eo pipefail,
head closing the pipe makes sed exit 141 (SIGPIPE), failing the
step even on successful test runs.
Net for PR #25 reviewers: the verify-maven-pgp workflow has been
verified to run end-to-end via local act; once GHA is re-enabled,
expect it to fail by design on empty keysmap, not on infrastructure
(JFrog auth, mvn dep resolution, pgpverify plugin loading) — those
are all proven working locally with the new pom.xml + jfrog-auth
setup.
Files in this commit: 19 modified + 16 new.
Co-authored-by: Isaac
* Lockdown round 4: PR #25 security review — close every actionable point
Address reviewer feedback on PR #25 (security/repo-lockdown-2). Maps each
point of the feedback to either a concrete code change, a documented
follow-up, or an explicit pushback with evidence.
Major changes
-------------
* Sweep `master` -> `main` after Labs admin renamed the default branch:
workflow triggers (deploy-docs, codeql-analysis, verify-maven-pgp,
build_main doc-inventory gating), badge URLs in README/CODECOV.md,
documentation links across docs/**/*.mdx, and scripts/security/README.md.
* `mvn -C` (strict-checksums) on every CI Maven invocation
(scala_build/action.yml, build_main.yml doc tests, maven-pgp-verify).
Failures now abort instead of warn on artifact checksum mismatch.
* Maven PGP verify gates every build. `.maven-keys.list` populated from 0
to 1055 entries: 26 dev-bootstrap noSig + 249 signed-key entries +
666 act-surfaced version-specific noSig sentinels + supporting docs.
Wired `scripts/security/maven-pgp-verify` as the FIRST Maven step in
scala_build/action.yml so the gate runs before any compile, test, or
install. Block clearly marked "TRUST STATUS: SNAPSHOT, NOT CROSS-CHECKED"
and `scripts/security/README.md` documents the path to flipping
`verify-pgp` to `<activeByDefault>true</activeByDefault>` once the
fingerprints are cross-checked against project KEYS files.
* ubuntugis PPA versions exact-pinned to `3.11.4+dfsg-1~noble0`
(libgdal-dev/gdal-bin/python3-gdal) in scala_build, python_build, and
scripts/geobrix-gdal-init.sh. PPA still floats but CI fails loudly on
PPA drift instead of silently rolling forward.
* `scripts/ci-local/Dockerfile.gha-runner` pinned to
`catthehacker/ubuntu@sha256:5b8d4550...` (main Dockerfile was already
digest-pinned).
* Azul JDK key fetch hardened from TOFU to fingerprint pin
`27BC0C8CB3D81623F59BDADCB1998361219BD9C9` in scripts/docker/Dockerfile.
Build aborts if upstream serves a different key.
* Corp proxy URLs scrubbed from all public files
(pypi-proxy/maven-proxy/npm-proxy.dev.databricks.com). All registry
URLs now build-arg injected from host env (PIP_INDEX_URL /
MAVEN_MIRROR_URL / NPM_REGISTRY_URL); defaults to public registries.
`scripts/docker/build_smart.sh` auto-forwards from host env.
* SECURITY.md documents the `runtime` GitHub Environment requirement
that repo admins must configure (deployment-branches: main only,
required reviewers, REPO_ACCESS_TOKEN scope).
Python supply chain (mitigating LiteLLM-class attacks at install time)
----------------------------------------------------------------------
* CI install path now hash-pinned via uv-generated lockfile.
`python/geobrix/requirements-ci.{in,txt}` — 401-line lockfile with
sha256 hashes on every transitive dep. scala_build/python_build use
`pip install --require-hashes` instead of loose `pip install foo==X`.
* Dev container Dockerfile hash-pinned. Replaced 4 ad-hoc `pip install`
blocks with a single `pip install --require-hashes --ignore-installed
-r requirements-dev-container.txt`. New files:
- `python/geobrix/requirements-dev-container.{in,txt}` (2039-line lock,
~110 packages)
- `.dockerignore` (build context shifted to project root)
- `scripts/docker/build_smart.sh` updated for new context
* `notebooks/tests/requirements.txt` hash-pinned via new `.in` source +
470-line lockfile.
* Cursor commands (`gbx-test-{notebooks,sql-docs,python-docs}.sh`) use
`pip install --no-deps` for the local geobrix package so transitive
deps come exclusively from the hash-pinned lockfile in the image.
Explicitly NOT touched (per maintainer review)
----------------------------------------------
* `%pip install` cells inside notebooks/examples/**/*.ipynb (customer
content).
* Code examples inside docs/docs/**/*.mdx (illustrative for customers).
* `databricks.jfrog.io` references in jfrog-auth action — that's the
public Labs CI mirror, ported verbatim from databrickslabs/ucx, not
a corp internal.
Local validation
----------------
* Native scala: 774 tests / 63 suites / 0 failed / 3:22 wall on rebuilt
hash-pinned image.
* Native python: 101 passed / 0 failed / 2 deselected / 1:34 wall.
* Hash-pinned dev container Docker build: 3:13 wall, 130+ packages all
sha256-verified.
* Act `verify-maven-pgp`: BUILD SUCCESS against populated keysmap.
* Act `build_python` (full e2e): every step succeeded, inline pgpverify
gate fired at 1:41, 101 python tests passed.
Co-authored-by: Isaac
* Lockdown round 5: PR #25 follow-up — supply-chain blind spots + reviewer hygiene
Close the four remaining items from the PR #25 review:
1. Hash-verify Python tarball download in the local-act runner image.
scripts/ci-local/Dockerfile.gha-runner now pins PYTHON_VERSION,
PYTHON_TARBALL_TAG, and PYTHON_SHA256 as build args and pipes the curl
through `sha256sum -c -` before unpacking — previously the tarball was
trusted blindly on a developer machine.
2. Hash-verify the Jupyter / pyzmq / testbook installs in the dev container.
Those packages were all already present in
requirements-dev-container.txt, but a follow-up `pip install --upgrade`
block in scripts/docker/Dockerfile was bypassing --require-hashes and
silently undoing the lockfile guarantee. Removed the redundant block;
also hash-pinned the pip bootstrap step (chicken-and-egg).
3. Pin the ubuntugis PPA signing key by fingerprint instead of TOFU.
Vendored the official Launchpad signing key at
scripts/security/keys/ubuntugis-ppa.asc plus a helper script
scripts/security/apt-add-ubuntugis-ppa that refuses the PPA unless the
key's fingerprint matches the expected value (sourced from Launchpad's
signing_key_fingerprint API). Both CI composite actions now call the
helper; scripts/geobrix-gdal-init.sh embeds the key + fingerprint check
inline so it stays self-contained as a cluster init script.
4. Clarify the GDAL --require-hashes exemption.
GDAL's Python binding version is dynamic (must match the apt/source-built
libgdal we already pin and verify) and OSGeo doesn't publish per-version
PyPI hashes. Comments in scripts/docker/Dockerfile and both CI
composite actions now spell out the reasoning; added --no-binary :all:
so the binding always source-builds against verified libgdal headers
instead of accepting a pre-built manylinux wheel.
Plus reviewer-flagged hygiene:
- Scrubbed every internal policy reference and operational-process link
(env-var-based proxy mechanism is documented neutrally; the env vars
themselves stay since they're a perfectly normal pip pattern).
- Replaced the docs.google.com policy link in scripts/security/README.md
with a plain-text summary.
Two latent bugs caught during local-act validation, both on the
public-contributor path that the docs claim to support but had never been
exercised end-to-end:
- scripts/ci-local/run-act.sh: BUILD_ARGS[@]: unbound variable on bash 3.2
(macOS) when no proxy env vars are set. Used the
${BUILD_ARGS[@]+...} idiom to handle empty arrays under set -u.
- scripts/{ci-local/,docker/}Dockerfile: PIP_INDEX_URL defaulted to empty
string, which causes pip to construct URLs with no host. Made
https://pypi.org/simple/ the explicit default so falling back to public
PyPI actually works.
Verification:
- gbx:test:scala: 774 passed, 0 failed (3:19)
- gbx:test:python: 101 passed, 0 failed (1:15)
- gbx:ci:act -W .github/workflows/build_python.yml: Job succeeded
Runner image rebuilt with Python SHA-256 verify; ubuntugis fingerprint
pin cleared on both composite invocations; --no-binary :all: GDAL sdist
built against PPA-installed libgdal headers; --require-hashes
requirements-ci.txt closure installed cleanly.
Co-authored-by: Isaac
* docs: add Security page; align installation with init-script lockdown
Add docs/docs/security.mdx as a user-facing chapter covering (a) what we
do upstream to harden the GeoBrix supply chain — third-party Action SHA
pinning, PGP-verified Maven dependencies, hash-pinned Python lockfiles,
fingerprint-pinned ubuntugis PPA + pinned GDAL package version with
source-only Python install, hardened ephemeral CI runners, JFrog OIDC
for pip/Maven/npm, gated deploy environment — and (b) what users can do
on top: use the init script verbatim, stage release artifacts in a
Volume they own, pin the GeoBrix version, restrict the GDAL driver
surface for untrusted inputs, and report vulnerabilities privately.
Wire the page into docs/sidebars.js between Developers and Known
Limitations.
Tighten docs/docs/installation.mdx to match the post-lockdown init
script:
- Narrow Prerequisites from "DBR 17.1 or later" to DBR 17.3 LTS. The
init script's pinned GDAL_PPA_VERSION targets the Ubuntu 24.04 noble
base; older or non-LTS DBRs use different bases and the pin either
fails or silently falls back to an unpinned GDAL.
- Add a "what the script does" callout above the rendered init script
block, linking to the Security page for the why.
- Rewrite the GDAL Library Issues troubleshooting section. The old
text described pre-lockdown PPA float ("from time-to-time might
update GDAL version"), which is no longer the risk model. New text
covers fingerprint mismatch (UBUNTUGIS_FPR), retired pinned package,
DBR base image mismatch, and the existing general-checks fallback.
The init script content itself is auto-rendered via raw-loader, so no
changes were needed to keep that section current.
Co-authored-by: Isaac
---------
Co-authored-by: Michael Johns <user.name>66 files changed
Lines changed: 6660 additions & 457 deletions
File tree
- .cursor
- agents
- commands
- .github
- actions
- jfrog-auth
- python_build
- scala_build
- docs
- workflows
- docs
- docs
- advanced
- api
- notebooks
- sample-data
- notebooks/tests
- python/geobrix
- scripts
- ci-local
- jfrog-auth-stub
- upload-artifacts-stub
- docker
- security
- keys
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
470 | 470 | | |
471 | 471 | | |
472 | 472 | | |
| 473 | + | |
| 474 | + | |
| 475 | + | |
| 476 | + | |
| 477 | + | |
| 478 | + | |
| 479 | + | |
| 480 | + | |
| 481 | + | |
| 482 | + | |
| 483 | + | |
| 484 | + | |
| 485 | + | |
| 486 | + | |
| 487 | + | |
| 488 | + | |
| 489 | + | |
| 490 | + | |
| 491 | + | |
| 492 | + | |
| 493 | + | |
| 494 | + | |
| 495 | + | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
| 499 | + | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
| 514 | + | |
| 515 | + | |
| 516 | + | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
| 524 | + | |
| 525 | + | |
| 526 | + | |
| 527 | + | |
| 528 | + | |
| 529 | + | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
| 533 | + | |
| 534 | + | |
| 535 | + | |
| 536 | + | |
| 537 | + | |
| 538 | + | |
| 539 | + | |
| 540 | + | |
| 541 | + | |
| 542 | + | |
| 543 | + | |
| 544 | + | |
| 545 | + | |
| 546 | + | |
| 547 | + | |
| 548 | + | |
| 549 | + | |
| 550 | + | |
| 551 | + | |
| 552 | + | |
| 553 | + | |
| 554 | + | |
| 555 | + | |
| 556 | + | |
473 | 557 | | |
474 | 558 | | |
475 | 559 | | |
| |||
483 | 567 | | |
484 | 568 | | |
485 | 569 | | |
| 570 | + | |
| 571 | + | |
| 572 | + | |
486 | 573 | | |
487 | | - | |
488 | | - | |
| 574 | + | |
| 575 | + | |
| 576 | + | |
| 577 | + | |
| 578 | + | |
| 579 | + | |
| 580 | + | |
| 581 | + | |
489 | 582 | | |
490 | 583 | | |
491 | 584 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
79 | 79 | | |
80 | 80 | | |
81 | 81 | | |
82 | | - | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
83 | 85 | | |
84 | 86 | | |
85 | 87 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
145 | 145 | | |
146 | 146 | | |
147 | 147 | | |
148 | | - | |
149 | | - | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
150 | 153 | | |
151 | 154 | | |
152 | 155 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
192 | 192 | | |
193 | 193 | | |
194 | 194 | | |
195 | | - | |
| 195 | + | |
196 | 196 | | |
197 | 197 | | |
198 | 198 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
118 | 118 | | |
119 | 119 | | |
120 | 120 | | |
121 | | - | |
| 121 | + | |
122 | 122 | | |
123 | 123 | | |
124 | 124 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
0 commit comments