merge: #1365 — security posture: lock past 32 CVEs with a 7-day cooldown, image scanning restored, Renovate actually running (refs #1365)
All checks were successful
CI / lint (push) Successful in 43s
Infra CI / zotero (push) Successful in 21s
CI / test (push) Successful in 2m22s
CI / notebooks-smoke (push) Successful in 1m41s
Deploy / changes (push) Successful in 15s
Infra CI / docs (push) Successful in 13s
Infra CI / notebooks (push) Successful in 56s
Infra CI / api (push) Successful in 13s
Infra CI / llm (push) Successful in 21s
Infra CI / mc (push) Successful in 17s
Package Supply Chain / pkg-supply-chain (push) Successful in 2m20s
Deploy / zotero (push) Has been skipped
Deploy / docs (push) Successful in 1m2s
Deploy / api (push) Successful in 1m9s
Deploy / notebooks (push) Successful in 5m59s
Deploy / llm (push) Successful in 1m33s
Deploy / mc (push) Successful in 41s
Deploy / report (push) Successful in 15s

This commit is contained in:
kert
2026-10-27 12:09:34 -04:00
29 changed files with 6555 additions and 5107 deletions

View File

@@ -71,7 +71,13 @@ jobs:
uses: https://github.com/actions/checkout@v4
- name: Install crane
run: curl -sL https://github.com/google/go-containerregistry/releases/latest/download/go-containerregistry_Linux_x86_64.tar.gz | tar xz -C /usr/local/bin crane
run: |
cd /tmp
base=https://github.com/google/go-containerregistry/releases/download/v0.22.1
tgz=go-containerregistry_Linux_x86_64.tar.gz
curl -fsSLO "$base/$tgz"
curl -fsSL "$base/checksums.txt" | grep -F " $tgz" | sha256sum -c -
tar xzf "$tgz" -C /usr/local/bin crane
- name: Log in to registry
run: crane auth login git:3000 -u "${{ secrets.REGISTRY_USER }}" -p "${{ secrets.REGISTRY_TOKEN }}"
@@ -80,7 +86,7 @@ jobs:
- name: Install trivy
run: curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
run: curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/v0.76.0/contrib/install.sh | sh -s -- -b /usr/local/bin v0.76.0
- name: Scan notebooks
run: trivy image --severity HIGH,CRITICAL --exit-code 0 --format json -o notebooks-scan.json local/notebooks:build
@@ -123,7 +129,13 @@ jobs:
uses: https://github.com/actions/checkout@v4
- name: Install crane
run: curl -sL https://github.com/google/go-containerregistry/releases/latest/download/go-containerregistry_Linux_x86_64.tar.gz | tar xz -C /usr/local/bin crane
run: |
cd /tmp
base=https://github.com/google/go-containerregistry/releases/download/v0.22.1
tgz=go-containerregistry_Linux_x86_64.tar.gz
curl -fsSLO "$base/$tgz"
curl -fsSL "$base/checksums.txt" | grep -F " $tgz" | sha256sum -c -
tar xzf "$tgz" -C /usr/local/bin crane
- name: Log in to registry
run: crane auth login git:3000 -u "${{ secrets.REGISTRY_USER }}" -p "${{ secrets.REGISTRY_TOKEN }}"
@@ -132,7 +144,7 @@ jobs:
- name: Install trivy
run: curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
run: curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/v0.76.0/contrib/install.sh | sh -s -- -b /usr/local/bin v0.76.0
- name: Scan zotero
run: trivy image --severity HIGH,CRITICAL --exit-code 0 --format json -o zotero-scan.json local/zotero:build
@@ -175,7 +187,13 @@ jobs:
uses: https://github.com/actions/checkout@v4
- name: Install crane
run: curl -sL https://github.com/google/go-containerregistry/releases/latest/download/go-containerregistry_Linux_x86_64.tar.gz | tar xz -C /usr/local/bin crane
run: |
cd /tmp
base=https://github.com/google/go-containerregistry/releases/download/v0.22.1
tgz=go-containerregistry_Linux_x86_64.tar.gz
curl -fsSLO "$base/$tgz"
curl -fsSL "$base/checksums.txt" | grep -F " $tgz" | sha256sum -c -
tar xzf "$tgz" -C /usr/local/bin crane
- name: Log in to registry
run: crane auth login git:3000 -u "${{ secrets.REGISTRY_USER }}" -p "${{ secrets.REGISTRY_TOKEN }}"
@@ -184,7 +202,7 @@ jobs:
- name: Install trivy
run: curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
run: curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/v0.76.0/contrib/install.sh | sh -s -- -b /usr/local/bin v0.76.0
- name: Scan docs
run: trivy image --severity HIGH,CRITICAL --exit-code 0 --format json -o docs-scan.json local/docs:build
@@ -227,7 +245,13 @@ jobs:
uses: https://github.com/actions/checkout@v4
- name: Install crane
run: curl -sL https://github.com/google/go-containerregistry/releases/latest/download/go-containerregistry_Linux_x86_64.tar.gz | tar xz -C /usr/local/bin crane
run: |
cd /tmp
base=https://github.com/google/go-containerregistry/releases/download/v0.22.1
tgz=go-containerregistry_Linux_x86_64.tar.gz
curl -fsSLO "$base/$tgz"
curl -fsSL "$base/checksums.txt" | grep -F " $tgz" | sha256sum -c -
tar xzf "$tgz" -C /usr/local/bin crane
- name: Log in to registry
run: crane auth login git:3000 -u "${{ secrets.REGISTRY_USER }}" -p "${{ secrets.REGISTRY_TOKEN }}"
@@ -236,7 +260,7 @@ jobs:
- name: Install trivy
run: curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
run: curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/v0.76.0/contrib/install.sh | sh -s -- -b /usr/local/bin v0.76.0
- name: Scan api
run: trivy image --severity HIGH,CRITICAL --exit-code 0 --format json -o api-scan.json local/api:build
@@ -279,7 +303,13 @@ jobs:
uses: https://github.com/actions/checkout@v4
- name: Install crane
run: curl -sL https://github.com/google/go-containerregistry/releases/latest/download/go-containerregistry_Linux_x86_64.tar.gz | tar xz -C /usr/local/bin crane
run: |
cd /tmp
base=https://github.com/google/go-containerregistry/releases/download/v0.22.1
tgz=go-containerregistry_Linux_x86_64.tar.gz
curl -fsSLO "$base/$tgz"
curl -fsSL "$base/checksums.txt" | grep -F " $tgz" | sha256sum -c -
tar xzf "$tgz" -C /usr/local/bin crane
- name: Log in to registry
run: crane auth login git:3000 -u "${{ secrets.REGISTRY_USER }}" -p "${{ secrets.REGISTRY_TOKEN }}"
@@ -288,7 +318,7 @@ jobs:
- name: Install trivy
run: curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
run: curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/v0.76.0/contrib/install.sh | sh -s -- -b /usr/local/bin v0.76.0
- name: Scan llm
run: trivy image --severity HIGH,CRITICAL --exit-code 0 --format json -o llm-scan.json local/llm:build
@@ -331,7 +361,13 @@ jobs:
uses: https://github.com/actions/checkout@v4
- name: Install crane
run: curl -sL https://github.com/google/go-containerregistry/releases/latest/download/go-containerregistry_Linux_x86_64.tar.gz | tar xz -C /usr/local/bin crane
run: |
cd /tmp
base=https://github.com/google/go-containerregistry/releases/download/v0.22.1
tgz=go-containerregistry_Linux_x86_64.tar.gz
curl -fsSLO "$base/$tgz"
curl -fsSL "$base/checksums.txt" | grep -F " $tgz" | sha256sum -c -
tar xzf "$tgz" -C /usr/local/bin crane
- name: Log in to registry
run: crane auth login git:3000 -u "${{ secrets.REGISTRY_USER }}" -p "${{ secrets.REGISTRY_TOKEN }}"
@@ -339,6 +375,12 @@ jobs:
CRANE_INSECURE: "true"
- name: Install trivy
run: curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/v0.76.0/contrib/install.sh | sh -s -- -b /usr/local/bin v0.76.0
- name: Scan mc
run: trivy image --severity HIGH,CRITICAL --exit-code 0 --format json -o mc-scan.json local/mc:build
- name: Compute short SHA
run: echo "SHORT_SHA=$(echo $GITHUB_SHA | head -c 8)" >> "$GITHUB_ENV"

View File

@@ -16,7 +16,13 @@ jobs:
uses: https://github.com/actions/checkout@v4
- name: Install crane
run: curl -sL https://github.com/google/go-containerregistry/releases/latest/download/go-containerregistry_Linux_x86_64.tar.gz | tar xz -C /usr/local/bin crane
run: |
cd /tmp
base=https://github.com/google/go-containerregistry/releases/download/v0.22.1
tgz=go-containerregistry_Linux_x86_64.tar.gz
curl -fsSLO "$base/$tgz"
curl -fsSL "$base/checksums.txt" | grep -F " $tgz" | sha256sum -c -
tar xzf "$tgz" -C /usr/local/bin crane
- name: Log in to registry
run: crane auth login git:3000 -u "${{ secrets.REGISTRY_USER }}" -p "${{ secrets.REGISTRY_TOKEN }}"
@@ -24,7 +30,7 @@ jobs:
CRANE_INSECURE: "true"
- name: Install trivy
run: curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
run: curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/v0.76.0/contrib/install.sh | sh -s -- -b /usr/local/bin v0.76.0
- name: Set up uv
run: curl -LsSf https://astral.sh/uv/install.sh | sh
@@ -32,86 +38,127 @@ jobs:
UV_INSTALL_DIR: /usr/local/bin
- name: Build notebooks
run: docker build --no-cache -f infra/images/notebooks.Dockerfile -t local/notebooks:build .
id: build_notebooks
continue-on-error: true
run: docker build --pull --no-cache -f infra/images/notebooks.Dockerfile -t local/notebooks:build .
- name: Scan notebooks
if: ${{ steps.build_notebooks.outcome == 'success' }}
run: trivy image --severity HIGH,CRITICAL --exit-code 0 --format json -o notebooks-scan.json local/notebooks:build
- name: Push notebooks
if: ${{ steps.build_notebooks.outcome == 'success' }}
run: |
docker save local/notebooks:build -o /tmp/notebooks.tar
crane push /tmp/notebooks.tar git:3000/homelab/stack/notebooks:hardened --insecure
crane push /tmp/notebooks.tar git:3000/homelab/stack/notebooks:latest --insecure
- name: Build zotero
run: docker build --no-cache -f infra/images/zotero.Dockerfile -t local/zotero:build .
id: build_zotero
continue-on-error: true
run: docker build --pull --no-cache -f infra/images/zotero.Dockerfile -t local/zotero:build .
- name: Scan zotero
if: ${{ steps.build_zotero.outcome == 'success' }}
run: trivy image --severity HIGH,CRITICAL --exit-code 0 --format json -o zotero-scan.json local/zotero:build
- name: Push zotero
if: ${{ steps.build_zotero.outcome == 'success' }}
run: |
docker save local/zotero:build -o /tmp/zotero.tar
crane push /tmp/zotero.tar git:3000/homelab/stack/zotero:hardened --insecure
crane push /tmp/zotero.tar git:3000/homelab/stack/zotero:latest --insecure
- name: Build docs
run: docker build --no-cache -f infra/images/docs.Dockerfile -t local/docs:build .
id: build_docs
continue-on-error: true
run: docker build --pull --no-cache -f infra/images/docs.Dockerfile -t local/docs:build .
- name: Scan docs
if: ${{ steps.build_docs.outcome == 'success' }}
run: trivy image --severity HIGH,CRITICAL --exit-code 0 --format json -o docs-scan.json local/docs:build
- name: Push docs
if: ${{ steps.build_docs.outcome == 'success' }}
run: |
docker save local/docs:build -o /tmp/docs.tar
crane push /tmp/docs.tar git:3000/homelab/stack/docs:hardened --insecure
crane push /tmp/docs.tar git:3000/homelab/stack/docs:latest --insecure
- name: Build api
run: docker build --no-cache -f infra/images/api.Dockerfile -t local/api:build .
id: build_api
continue-on-error: true
run: docker build --pull --no-cache -f infra/images/api.Dockerfile -t local/api:build .
- name: Scan api
if: ${{ steps.build_api.outcome == 'success' }}
run: trivy image --severity HIGH,CRITICAL --exit-code 0 --format json -o api-scan.json local/api:build
- name: Push api
if: ${{ steps.build_api.outcome == 'success' }}
run: |
docker save local/api:build -o /tmp/api.tar
crane push /tmp/api.tar git:3000/homelab/stack/api:hardened --insecure
crane push /tmp/api.tar git:3000/homelab/stack/api:latest --insecure
- name: Build llm
run: docker build --no-cache -f infra/images/llm.Dockerfile -t local/llm:build .
id: build_llm
continue-on-error: true
run: docker build --pull --no-cache -f infra/images/llm.Dockerfile -t local/llm:build .
- name: Scan llm
if: ${{ steps.build_llm.outcome == 'success' }}
run: trivy image --severity HIGH,CRITICAL --exit-code 0 --format json -o llm-scan.json local/llm:build
- name: Push llm
if: ${{ steps.build_llm.outcome == 'success' }}
run: |
docker save local/llm:build -o /tmp/llm.tar
crane push /tmp/llm.tar git:3000/homelab/stack/llm:hardened --insecure
crane push /tmp/llm.tar git:3000/homelab/stack/llm:latest --insecure
- name: Build mc
run: docker build --no-cache -f infra/images/mc.Dockerfile -t local/mc:build infra/rustfs/
id: build_mc
continue-on-error: true
run: docker build --pull --no-cache -f infra/images/mc.Dockerfile -t local/mc:build infra/rustfs/
- name: Scan mc
if: ${{ steps.build_mc.outcome == 'success' }}
run: trivy image --severity HIGH,CRITICAL --exit-code 0 --format json -o mc-scan.json local/mc:build
- name: Push mc
if: ${{ steps.build_mc.outcome == 'success' }}
run: |
docker save local/mc:build -o /tmp/mc.tar
crane push /tmp/mc.tar git:3000/homelab/stack/mc:hardened --insecure
crane push /tmp/mc.tar git:3000/homelab/stack/mc:latest --insecure
- name: Scan notebooks
run: trivy image --severity HIGH,CRITICAL --exit-code 0 --format json -o notebooks-scan.json local/notebooks:build
- name: Scan zotero
run: trivy image --severity HIGH,CRITICAL --exit-code 0 --format json -o zotero-scan.json local/zotero:build
- name: Scan docs
run: trivy image --severity HIGH,CRITICAL --exit-code 0 --format json -o docs-scan.json local/docs:build
- name: Scan api
run: trivy image --severity HIGH,CRITICAL --exit-code 0 --format json -o api-scan.json local/api:build
- name: Scan llm
run: trivy image --severity HIGH,CRITICAL --exit-code 0 --format json -o llm-scan.json local/llm:build
- name: Close resolved or file new vuln issues
env:
GITEA_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
run: |
uv sync --no-dev
for f in notebooks-scan.json zotero-scan.json docs-scan.json api-scan.json llm-scan.json; do
for f in notebooks-scan.json zotero-scan.json docs-scan.json api-scan.json llm-scan.json mc-scan.json; do
if [ -f "$f" ]; then
uv run python -m api.diag.vuln --close "$f" || \
uv run python -m api.diag.vuln "$f" || true
fi
done
- name: Fail if any image failed to build
run: |
failed=""
[ "${{ steps.build_notebooks.outcome }}" = success ] || failed="$failed notebooks"
[ "${{ steps.build_zotero.outcome }}" = success ] || failed="$failed zotero"
[ "${{ steps.build_docs.outcome }}" = success ] || failed="$failed docs"
[ "${{ steps.build_api.outcome }}" = success ] || failed="$failed api"
[ "${{ steps.build_llm.outcome }}" = success ] || failed="$failed llm"
[ "${{ steps.build_mc.outcome }}" = success ] || failed="$failed mc"
if [ -n "$failed" ]; then
echo "Images that failed to build (not scanned, not pushed):$failed"
exit 1
fi
- name: File failure issue
if: failure()
env:

View File

@@ -45,13 +45,15 @@ jobs:
run: uv run python dev/scripts/pkg_drift.py
- name: Install trivy
run: curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
run: curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/v0.76.0/contrib/install.sh | sh -s -- -b /usr/local/bin v0.76.0
- name: Vulnerability scan
run: |
set -euo pipefail
trivy fs --scanners vuln --format json -o data/pkg-vulns.json uv.lock
trivy fs --scanners vuln --format json -o data/pkg-vulns-pyproject.json pyproject.toml
# The docs site's build toolchain — never scanned before (116 CVEs, 2026-10-27).
trivy fs --scanners vuln --format json -o data/pkg-vulns-docs.json docs/pnpm-lock.yaml
- name: Auto-create issues for drift and vulns
env:

View File

@@ -14,7 +14,13 @@ jobs:
uses: https://github.com/actions/checkout@v4
- name: Install crane
run: curl -sL https://github.com/google/go-containerregistry/releases/latest/download/go-containerregistry_Linux_x86_64.tar.gz | tar xz -C /usr/local/bin crane
run: |
cd /tmp
base=https://github.com/google/go-containerregistry/releases/download/v0.22.1
tgz=go-containerregistry_Linux_x86_64.tar.gz
curl -fsSLO "$base/$tgz"
curl -fsSL "$base/checksums.txt" | grep -F " $tgz" | sha256sum -c -
tar xzf "$tgz" -C /usr/local/bin crane
- name: Log in to registry
run: crane auth login git:3000 -u "${{ secrets.REGISTRY_USER }}" -p "${{ secrets.REGISTRY_TOKEN }}"
@@ -22,7 +28,7 @@ jobs:
CRANE_INSECURE: "true"
- name: Install trivy
run: curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
run: curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/v0.76.0/contrib/install.sh | sh -s -- -b /usr/local/bin v0.76.0
- name: Set up uv
run: curl -LsSf https://astral.sh/uv/install.sh | sh
@@ -101,12 +107,15 @@ jobs:
- name: Scan llm
run: trivy image --severity HIGH,CRITICAL --exit-code 0 --format json -o llm-scan.json local/llm:build
- name: Scan mc
run: trivy image --severity HIGH,CRITICAL --exit-code 0 --format json -o mc-scan.json local/mc:build
- name: Report vulnerabilities
env:
GITEA_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
run: |
uv sync --no-dev
for f in notebooks-scan.json zotero-scan.json docs-scan.json api-scan.json llm-scan.json; do
for f in notebooks-scan.json zotero-scan.json docs-scan.json api-scan.json llm-scan.json mc-scan.json; do
[ -f "$f" ] && uv run python -m api.diag.vuln "$f" || true
done

View File

@@ -8,14 +8,21 @@ on:
jobs:
renovate:
runs-on: ubuntu-latest
container: ghcr.io/renovatebot/renovate:latest
# Pinned: a floating `latest` let a Renovate major change behaviour
# unreviewed. Renovate bumps this pin itself (github-actions manager).
container: ghcr.io/renovatebot/renovate:44.171.1
steps:
- uses: https://github.com/actions/checkout@v4
- name: Run Renovate
run: renovate
env:
RENOVATE_CONFIG_FILE: renovate.json
# Self-hosted (global) settings live here; renovate.json is the
# repo config. Without RENOVATE_REPOSITORIES every run was a silent
# no-op ("No repositories found") — 28 green runs, zero PRs.
RENOVATE_PLATFORM: gitea
RENOVATE_ENDPOINT: http://git:3000/api/v1
RENOVATE_REPOSITORIES: homelab/stack
RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }}
RENOVATE_GIT_AUTHOR: "Renovate Bot <renovate@fhirworx.io>"
LOG_LEVEL: "info"

View File

@@ -121,11 +121,12 @@ services:
networks:
- storage
environment:
- MC_HOST_local=http://${RUSTFS_ACCESS_KEY}:${RUSTFS_SECRET_KEY}@rustfs:9000
- RC_HOST_local=http://${RUSTFS_ACCESS_KEY}:${RUSTFS_SECRET_KEY}@rustfs:9000
- RC_CONFIG_DIR=/home/mc/.config/rc
user: "10002:10002"
read_only: true
tmpfs:
- /home/mc/.mc:uid=10002,gid=10002
- /home/mc/.config/rc:uid=10002,gid=10002
security_opt:
- no-new-privileges:true
cap_drop:

View File

@@ -238,7 +238,7 @@
},
{
"name": "pymupdf",
"version": ">=1.24",
"version": ">=1.27.2,<2",
"extras": ""
},
{
@@ -263,7 +263,7 @@
},
{
"name": "duckdb",
"version": ">=1.0.0",
"version": ">=1.5.4",
"extras": ""
},
{
@@ -283,7 +283,7 @@
},
{
"name": "duckdb",
"version": ">=1.0.0",
"version": ">=1.5.4",
"extras": ""
},
{
@@ -303,7 +303,7 @@
},
{
"name": "pyjwt",
"version": ">=2.13.0",
"version": ">=2.15.1",
"extras": ""
},
{
@@ -318,7 +318,7 @@
},
{
"name": "fsspec",
"version": ">=2024.1.0",
"version": ">=2026.6.0",
"extras": ""
},
{
@@ -386,6 +386,11 @@
"version": ">=2.0.0",
"extras": ""
},
{
"name": "pyyaml",
"version": ">=6.0.0",
"extras": ""
},
{
"name": "pydantic",
"version": ">=2.0.0",
@@ -428,7 +433,7 @@
},
{
"name": "duckdb",
"version": ">=1.0.0",
"version": ">=1.5.4",
"extras": ""
},
{
@@ -438,7 +443,7 @@
},
{
"name": "duckdb",
"version": ">=1.0.0",
"version": ">=1.5.4",
"extras": ""
},
{
@@ -446,9 +451,14 @@
"version": ">=2.17.0",
"extras": ""
},
{
"name": "pydantic",
"version": ">=2.0.0",
"extras": ""
},
{
"name": "duckdb",
"version": ">=1.0.0",
"version": ">=1.5.4",
"extras": ""
},
{
@@ -473,7 +483,7 @@
},
{
"name": "fsspec",
"version": ">=2024.1.0",
"version": ">=2026.6.0",
"extras": ""
},
{
@@ -508,12 +518,12 @@
},
{
"name": "opentelemetry-api",
"version": ">=1.25.0",
"version": ">=1.45.1",
"extras": ""
},
{
"name": "opentelemetry-sdk",
"version": ">=1.25.0",
"version": ">=1.45.1",
"extras": ""
},
{
@@ -599,47 +609,47 @@
"npm": [
{
"name": "@docusaurus/core",
"version": "^3.7.0"
"version": "^3.10.2"
},
{
"name": "@docusaurus/preset-classic",
"version": "^3.7.0"
"version": "^3.10.2"
},
{
"name": "@easyops-cn/docusaurus-search-local",
"version": "^0.55.1"
"version": "^0.55.3"
},
{
"name": "@mdx-js/react",
"version": "^3.0.0"
"version": "^3.1.1"
},
{
"name": "clsx",
"version": "^2.0.0"
"version": "^2.1.1"
},
{
"name": "prism-react-renderer",
"version": "^2.3.0"
"version": "^2.4.1"
},
{
"name": "react",
"version": "^18.0.0"
"version": "^18.3.1"
},
{
"name": "react-dom",
"version": "^18.0.0"
"version": "^18.3.1"
},
{
"name": "@docusaurus/module-type-aliases",
"version": "^3.7.0"
"version": "^3.10.2"
},
{
"name": "@docusaurus/types",
"version": "^3.7.0"
"version": "^3.10.2"
},
{
"name": "typescript",
"version": "~5.6.0"
"version": "~5.6.3"
}
],
"ci_tools": [
@@ -647,10 +657,6 @@
"name": "uv",
"version": "latest"
},
{
"name": "crane",
"version": "latest"
},
{
"name": "trivy",
"version": "latest"

View File

@@ -14,6 +14,15 @@ from backends.github import (
_HEADER,
)
# Pinned scanner/registry tooling. A `latest`/`main` install would make the
# tools that guard the supply chain an unpinned dependency themselves.
# Renovate bumps these via the regex manager in renovate.json; re-run
# gen_config.py after a bump.
# renovate: datasource=github-releases depName=aquasecurity/trivy
TRIVY_VERSION = "0.76.0"
# renovate: datasource=github-releases depName=google/go-containerregistry
CRANE_VERSION = "0.22.1"
# ── Override action refs with full GitHub URLs ───────────────────
# Gitea Actions resolves short refs (e.g. actions/checkout@v4) against
# the local Gitea instance. Use full URLs to pull from GitHub.
@@ -57,7 +66,9 @@ def _build_push_step(
name = img["name"]
tags = [t.strip() for t in tags_expr.split(",") if t.strip()]
local_tag = f"local/{name}:build"
cache_flag = " --no-cache" if no_cache else ""
# A from-scratch rebuild must also re-pull base images — --no-cache alone
# reuses whatever stale base the runner already has.
cache_flag = " --pull --no-cache" if no_cache else ""
lines = f"""\
- name: Build {name}
run: docker build{cache_flag} -f {img["dockerfile"]} -t {local_tag} {img["context"]}"""
@@ -76,10 +87,15 @@ def _build_push_step(
def _install_trivy_step() -> str:
"""Install trivy as a native binary — no Docker-in-Docker needed."""
return """\
"""Install a pinned trivy as a native binary — no Docker-in-Docker needed.
The installer from the release tag verifies the tarball's sha256 against
the release checksums.
"""
v = TRIVY_VERSION
return f"""\
- name: Install trivy
run: curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin"""
run: curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/v{v}/contrib/install.sh | sh -s -- -b /usr/local/bin v{v}"""
def _trivy_step(
@@ -129,7 +145,13 @@ def _docker_login_step(registry: str) -> str:
"""Install crane and authenticate — daemonless push tool."""
return f"""\
- name: Install crane
run: curl -sL https://github.com/google/go-containerregistry/releases/latest/download/go-containerregistry_Linux_x86_64.tar.gz | tar xz -C /usr/local/bin crane
run: |
cd /tmp
base=https://github.com/google/go-containerregistry/releases/download/v{CRANE_VERSION}
tgz=go-containerregistry_Linux_x86_64.tar.gz
curl -fsSLO "$base/$tgz"
curl -fsSL "$base/checksums.txt" | grep -F " $tgz" | sha256sum -c -
tar xzf "$tgz" -C /usr/local/bin crane
- name: Log in to registry
run: crane auth login {registry} -u "${{{{ secrets.REGISTRY_USER }}}}" -p "${{{{ secrets.REGISTRY_TOKEN }}}}"
@@ -412,21 +434,46 @@ def _gen_harden(
uv_version: str,
**_kw: object,
) -> tuple[str, str]:
build_steps = []
# Each image is built, scanned and pushed on its own, and a failed build
# only skips that image: one dead upstream base (the archived minio/mc,
# Sept–Oct 2026) used to abort the job before any scan ran, leaving every
# image unscanned for weeks. The gate step at the end still fails the job
# — after the vuln report — so the breakage is filed, not swallowed.
scan_names = {i["name"] for i in scans}
image_steps = []
for img in images:
tags = (
f"{registry}/{owner_repo}/{img['name']}:hardened,"
f"{registry}/{owner_repo}/{img['name']}:latest"
)
build_steps.append(
_build_push_step(img, tags, registry, owner_repo, no_cache=True)
)
build_block = "\n\n".join(build_steps)
name = img["name"]
step_id = f"build_{name.replace('-', '_')}"
built = f"steps.{step_id}.outcome == 'success'"
steps = [
f"""\
- name: Build {name}
id: {step_id}
continue-on-error: true
run: docker build --pull --no-cache -f {img["dockerfile"]} -t local/{name}:build {img["context"]}"""
]
if name in scan_names:
sev = img.get("trivy_severity", "HIGH,CRITICAL")
ec = img.get("trivy_exit_code", 0)
steps.append(f"""\
- name: Scan {name}
if: ${{{{ {built} }}}}
run: trivy image --severity {sev} --exit-code {ec} --format json -o {name}-scan.json local/{name}:build""")
steps.append(f"""\
- name: Push {name}
if: ${{{{ {built} }}}}
run: |
docker save local/{name}:build -o /tmp/{name}.tar
crane push /tmp/{name}.tar {registry}/{owner_repo}/{name}:hardened --insecure
crane push /tmp/{name}.tar {registry}/{owner_repo}/{name}:latest --insecure""")
image_steps.append("\n\n".join(steps))
image_block = "\n\n".join(image_steps)
scan_steps = []
for img in scans:
scan_steps.append(_trivy_step(img, "hardened", registry, owner_repo))
scan_block = "\n\n".join(scan_steps)
gate_lines = "\n ".join(
f'[ "${{{{ steps.build_{i["name"].replace("-", "_")}.outcome }}}}" = success ]'
f' || failed="$failed {i["name"]}"'
for i in images
)
vuln_files = " ".join(f"{i['name']}-scan.json" for i in scans)
@@ -451,9 +498,7 @@ jobs:
{_setup_uv_step(uv_version)}
{build_block}
{scan_block}
{image_block}
- name: Close resolved or file new vuln issues
env:
@@ -467,6 +512,15 @@ jobs:
fi
done
- name: Fail if any image failed to build
run: |
failed=""
{gate_lines}
if [ -n "$failed" ]; then
echo "Images that failed to build (not scanned, not pushed):$failed"
exit 1
fi
{_failure_step("Harden", "build-scan-report")}
"""
return (".gitea/workflows/harden.yml", content)

View File

@@ -20,7 +20,11 @@ from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
MANIFEST_PATH = ROOT / "data" / "pkg-manifest.json"
VULN_PATH = ROOT / "data" / "pkg-vulns.json"
# trivy JSON reports, one per lockfile (uv.lock, docs/pnpm-lock.yaml)
VULN_PATHS = (
ROOT / "data" / "pkg-vulns.json",
ROOT / "data" / "pkg-vulns-docs.json",
)
GITEA_URL = os.environ.get("GITEA_URL", "http://localhost:3000")
GITEA_TOKEN = os.environ.get("GITEA_TOKEN", "")
@@ -99,15 +103,41 @@ def _get_label_ids(names: list[str]) -> list[int]:
return [name_to_id[n] for n in names if n in name_to_id]
def _find_existing_issues(prefix: str) -> dict[str, dict]:
"""Find open issues with titles starting with prefix."""
issues = _api(
"GET",
f"repos/{REPO}/issues?state=open&type=issues&limit=50&labels={LABEL_CI}",
)
if not issues:
return {}
return {i["title"]: i for i in issues if i["title"].startswith(prefix)}
def _find_existing_issues(prefix: str) -> dict[str, list[dict]]:
"""Find open issues with titles starting with prefix, grouped by title.
Walks every page: a single 50-issue page hid most of the tracker once
CI-failure issues piled up, so known CVEs were re-filed nightly (174
issues for 91 titles) and resolved ones were never closed.
"""
found: dict[str, list[dict]] = {}
page = 1
while True:
issues = _api(
"GET",
f"repos/{REPO}/issues?state=open&type=issues&limit=50"
f"&page={page}&labels={LABEL_CI}",
)
if not issues:
return found
for i in issues:
if i["title"].startswith(prefix):
found.setdefault(i["title"], []).append(i)
page += 1
def _close_all(title: str, issues: list[dict], *, dry_run: bool) -> None:
"""Close every open issue carrying ``title`` (duplicates included)."""
for issue in issues:
if dry_run:
print(f" would close #{issue['number']}: {title}")
else:
_api(
"PATCH",
f"repos/{REPO}/issues/{issue['number']}",
{"state": "closed"},
)
print(f" closed #{issue['number']}: {title}")
def handle_drift(*, dry_run: bool = False) -> int:
@@ -172,7 +202,7 @@ def handle_drift(*, dry_run: bool = False) -> int:
created += 1
# Close issues for packages that are no longer missing
for title, issue in existing.items():
for title, issues in existing.items():
# Extract package name from title
m = re.match(r"\[pkg-missing\] (\w+)/(.+?)(?:\s|$)", title)
if not m:
@@ -186,40 +216,36 @@ def handle_drift(*, dry_run: bool = False) -> int:
if pkg_name in sub_d.get("untracked", []):
still_missing = True
break
if not still_missing:
if dry_run:
print(f" would close: {title}")
else:
_api(
"PATCH",
f"repos/{REPO}/issues/{issue['number']}",
{"state": "closed"},
)
print(f" closed #{issue['number']}: {title}")
issues = sorted(issues, key=lambda i: i.get("number", 0))
_close_all(title, issues[1:] if still_missing else issues, dry_run=dry_run)
return created
def handle_vulns(*, dry_run: bool = False) -> int:
"""Create issues for vulnerabilities found in package scans."""
if not VULN_PATH.exists():
print(" vulns: no scan results found (data/pkg-vulns.json)")
return 0
"""Create issues for vulnerabilities found in package scans.
data = json.loads(VULN_PATH.read_text())
Also closes issues whose CVE no longer appears — including when a scan
comes back clean, which is exactly when the most issues resolve.
"""
reports = [p for p in VULN_PATHS if p.exists()]
if not reports:
print(" vulns: no scan results found (data/pkg-vulns*.json)")
return 0
# trivy JSON format: {"Results": [{"Vulnerabilities": [...]}]}
vulns: list[dict] = []
for result in data.get("Results", []):
for v in result.get("Vulnerabilities", []):
vulns.append(v)
for path in reports:
data = json.loads(path.read_text())
for result in data.get("Results", []):
for v in result.get("Vulnerabilities") or []:
vulns.append(v)
if not vulns:
print(" vulns: no vulnerabilities found")
return 0
milestone_id = _get_milestone_id()
label_ids = _get_label_ids([LABEL_CI, LABEL_QUALITY])
milestone_id = _get_milestone_id() if vulns else None
label_ids = _get_label_ids([LABEL_CI, LABEL_QUALITY]) if vulns else []
existing = _find_existing_issues("[pkg-vuln]")
created = 0
@@ -245,6 +271,7 @@ def handle_vulns(*, dry_run: bool = False) -> int:
if dry_run:
print(f" would create: {title}")
existing[title] = []
else:
issue_data = {"title": title, "body": body, "labels": label_ids}
if milestone_id:
@@ -253,24 +280,23 @@ def handle_vulns(*, dry_run: bool = False) -> int:
if result:
print(f" created #{result['number']}: {title}")
created += 1
# the same CVE can surface twice (two lockfiles, two paths)
existing[title] = [result]
# Close issues for CVEs that are no longer present
# Close issues for CVEs that are no longer present, and duplicates of
# ones that are (keep the oldest open).
active_cves = {
f"[pkg-vuln] {v.get('VulnerabilityID', '')} in "
f"{v.get('PkgName', '')}@{v.get('InstalledVersion', '')}"
for v in vulns
}
for title, issue in existing.items():
if title not in active_cves:
if dry_run:
print(f" would close: {title}")
else:
_api(
"PATCH",
f"repos/{REPO}/issues/{issue['number']}",
{"state": "closed"},
)
print(f" closed #{issue['number']}: {title}")
for title, issues in existing.items():
issues = sorted(issues, key=lambda i: i.get("number", 0))
_close_all(
title,
issues[1:] if title in active_cves else issues,
dry_run=dry_run,
)
return created

View File

@@ -11,19 +11,19 @@
"serve": "docusaurus serve"
},
"dependencies": {
"@docusaurus/core": "^3.7.0",
"@docusaurus/preset-classic": "^3.7.0",
"@easyops-cn/docusaurus-search-local": "^0.55.1",
"@mdx-js/react": "^3.0.0",
"clsx": "^2.0.0",
"prism-react-renderer": "^2.3.0",
"react": "^18.0.0",
"react-dom": "^18.0.0"
"@docusaurus/core": "^3.10.2",
"@docusaurus/preset-classic": "^3.10.2",
"@easyops-cn/docusaurus-search-local": "^0.55.3",
"@mdx-js/react": "^3.1.1",
"clsx": "^2.1.1",
"prism-react-renderer": "^2.4.1",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "^3.7.0",
"@docusaurus/types": "^3.7.0",
"typescript": "~5.6.0"
"@docusaurus/module-type-aliases": "^3.10.2",
"@docusaurus/types": "^3.10.2",
"typescript": "~5.6.3"
},
"browserslist": {
"production": [
@@ -40,5 +40,5 @@
"engines": {
"node": ">=18.0"
},
"packageManager": "pnpm@10.12.1"
"packageManager": "pnpm@10.34.7"
}

5572
docs/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
# syntax=docker/dockerfile:1
FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim
FROM ghcr.io/astral-sh/uv:0.13.2-python3.13-trixie-slim
WORKDIR /app
@@ -11,6 +11,9 @@ ARG PYPI_INDEX_URL=""
# healthcheck timeout, so Docker marks the container unhealthy even
# though /health responds in milliseconds.)
RUN apt-get update && apt-get upgrade -y && apt-get install -y --no-install-recommends curl && rm -rf /var/lib/apt/lists/*
# uv manages the venv; the base image's system pip is never used, and its
# vendored certifi/urllib3/msgpack/setuptools copies lag and get flagged.
RUN python3 -m pip uninstall -y -q pip
# Copy project files for install
COPY pyproject.toml uv.lock README.md ./
@@ -26,7 +29,9 @@ ENV UV_PYTHON_PREFERENCE=only-system \
UV_LINK_MODE=copy \
UV_PROJECT_ENVIRONMENT=.venv \
UV_INDEX_URL=${PYPI_INDEX_URL}
RUN uv sync --no-dev --extra cli --extra perf && uv pip install -e .
# --locked: a stale uv.lock fails the build instead of silently re-resolving
# to packages nobody scanned (pkg-supply-chain scans uv.lock).
RUN uv sync --locked --no-dev --extra cli --extra perf && uv pip install -e .
# Config
COPY stack.toml ./

View File

@@ -1,5 +1,5 @@
# Stage 1: Extract docs from Python source + export bib library
FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim AS extract
FROM ghcr.io/astral-sh/uv:0.13.2-python3.13-trixie-slim AS extract
WORKDIR /build
@@ -21,7 +21,7 @@ RUN uv run --with griffe python docs/scripts/extract_docs.py
# Stage 2: Build Docusaurus static site
FROM node:22-slim AS build
RUN corepack enable && corepack prepare pnpm@10.12.1 --activate
RUN corepack enable && corepack prepare pnpm@10.34.7 --activate
WORKDIR /docs

View File

@@ -1,5 +1,5 @@
# syntax=docker/dockerfile:1
FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim
FROM ghcr.io/astral-sh/uv:0.13.2-python3.13-trixie-slim
WORKDIR /app
@@ -8,6 +8,9 @@ ARG PYPI_INDEX_URL=""
# Patch base image CVEs + install curl for the healthcheck.
RUN apt-get update && apt-get upgrade -y && apt-get install -y --no-install-recommends curl && rm -rf /var/lib/apt/lists/*
# uv manages the venv; the base image's system pip is never used, and its
# vendored certifi/urllib3/msgpack/setuptools copies lag and get flagged.
RUN python3 -m pip uninstall -y -q pip
# Word → PDF for the document viewer (llm.convert): headless Writer plus
# metric-compatible fonts so Calibri/Cambria/Times/Arial letters paginate
@@ -25,7 +28,9 @@ ENV UV_PYTHON_PREFERENCE=only-system \
UV_LINK_MODE=copy \
UV_PROJECT_ENVIRONMENT=.venv \
UV_INDEX_URL=${PYPI_INDEX_URL}
RUN uv sync --no-dev --extra llm --extra perf && uv pip install -e .
# --locked: a stale uv.lock fails the build instead of silently re-resolving
# to packages nobody scanned (pkg-supply-chain scans uv.lock).
RUN uv sync --locked --no-dev --extra llm --extra perf && uv pip install -e .
# Config (conf reads stack.toml by walking up from CWD)
COPY stack.toml ./

View File

@@ -1,13 +1,17 @@
# syntax=docker/dockerfile:1
# MinIO Client (mc) sidecar — least-privilege admin helper for RustFS.
# The image moved off Docker Hub (docker.io/minio/mc now 404s — pull access
# denied) to quay.io; pinned to a release tag so a base-image change is a
# reviewed diff, not a silent CI break.
# Object-store admin sidecar for RustFS — least-privilege IAM/bucket helper.
# The service keeps its `mc` name, but the binary is RustFS's own `rc`
# (github.com/rustfs/cli): MinIO archived the open-source mc — no security
# updates, quay.io/minio/mc now 401s and dl.min.io answers 410 Gone. rc speaks
# the same alias grammar (`RC_HOST_local=http://key:secret@host:port`) and
# admin verbs: `docker exec mc rc admin policy create local/ <name> <file>`.
# Pinned tags so a base-image change is a reviewed diff (Renovate bumps both).
# Runs as non-root, read-only filesystem, no capabilities.
FROM quay.io/minio/mc:RELEASE.2025-08-13T08-35-41Z AS mc
FROM rustfs/rc:v0.1.36 AS rc
FROM alpine:3
RUN addgroup -g 10002 -S mc && adduser -u 10002 -S mc -G mc
COPY --from=mc /usr/bin/mc /usr/bin/mc
FROM alpine:3.24
RUN apk upgrade --no-cache \
&& addgroup -g 10002 -S mc && adduser -u 10002 -S mc -G mc
COPY --from=rc /usr/bin/rc /usr/bin/rc
USER mc:mc
ENTRYPOINT ["mc"]
ENTRYPOINT ["rc"]

View File

@@ -61,7 +61,7 @@ RUN rm -rf marimo/_static marimo/_lsp \
# ---- stage 2: wheel ---------------------------------------------------------
FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim AS wheel
FROM ghcr.io/astral-sh/uv:0.13.2-python3.13-trixie-slim AS wheel
WORKDIR /src
COPY --from=fe /src/ /src/
RUN uv build --wheel
@@ -92,14 +92,19 @@ RUN groupmod -n ${USERNAME} ubuntu \
&& usermod -l ${USERNAME} -d /home/${USERNAME} -m -s /bin/bash ubuntu \
&& chown -R ${USER_UID}:${USER_GID} /home/${USERNAME}
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
COPY --from=ghcr.io/astral-sh/uv:latest /uvx /usr/local/bin/uvx
COPY --from=ghcr.io/astral-sh/uv:0.13.2 /uv /usr/local/bin/uv
COPY --from=ghcr.io/astral-sh/uv:0.13.2 /uvx /usr/local/bin/uvx
WORKDIR /home/${USERNAME}
# Bring in the fhirworx marimo wheel from the wheel stage.
COPY --from=wheel /src/dist/ /tmp/marimo-wheel/
# No release cooldown here (unlike pyproject.toml's exclude-newer): this
# workspace pulls cudf-polars-cu12 from pypi.nvidia.com, which publishes no
# upload dates, so any exclude-newer filters out every release and the build
# fails. uv has no per-package opt-out for date-less indexes.
# Install python + init workspace venv. Use the local wheel for marimo so
# the fhirworx frontend ships in the image; no upstream PyPI fetch for
# marimo itself.

View File

@@ -6,7 +6,11 @@ readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"pydantic>=2.0.0",
"pymupdf>=1.24", # AGPL-3.0 — flag if shipping outside internal use
# AGPL-3.0 — flag if shipping outside internal use. <2: pymupdf 2.x folds
# in the former pymupdf_layout code (Polyform Noncommercial / Artifex
# commercial) and pulls onnxruntime into every image — a licence call,
# not a routine bump.
"pymupdf>=1.27.2,<2",
"python-docx>=1.1",
"xlrd>=2.0.2",
]
@@ -32,13 +36,13 @@ api = [
"fastapi>=0.139.0",
"uvicorn>=0.41.0",
"httpx>=0.28.1",
"pyjwt>=2.13.0",
"pyjwt>=2.15.1",
"cryptography>=50.0.0",
]
bcda = [
"stack[conf]",
"httpx>=0.28.1",
"fsspec>=2024.1.0",
"fsspec>=2026.6.0",
"narwhals>=2.17.0",
"pyarrow>=23.0.1",
]
@@ -110,7 +114,7 @@ rex = [
"stack[conf]",
"narwhals>=2.17.0",
"pyarrow>=23.0.1",
"fsspec>=2024.1.0",
"fsspec>=2026.6.0",
]
prisma = [
"stack[conf]",
@@ -124,8 +128,8 @@ prisma = [
]
perf = [
"stack[conf]",
"opentelemetry-api>=1.25.0",
"opentelemetry-sdk>=1.25.0",
"opentelemetry-api>=1.45.1",
"opentelemetry-sdk>=1.45.1",
"opentelemetry-exporter-otlp-proto-grpc>=1.25.0",
"opentelemetry-exporter-prometheus>=0.46b0",
"opentelemetry-instrumentation-fastapi>=0.46b0",
@@ -222,6 +226,18 @@ parallel = true
source = ["src"]
concurrency = ["multiprocessing", "thread"]
[tool.uv]
# Supply-chain cooldown: resolve only releases at least a week old, so a
# hijacked or yanked-within-days upload never lands in the lock. Renovate's
# minimumReleaseAge matches. Exempt one package with
# `exclude-newer-package` when a CVE fix can't wait. Lives here, not in a
# uv.toml: images copy only pyproject.toml + uv.lock, and the lock records
# the span, so `uv sync --locked` fails wherever the setting isn't visible.
exclude-newer = "7 days"
# Prevents broken .venv symlinks when uv runs under different users.
link-mode = "copy"
python-preference = "managed"
[tool.uv.build-backend]
module-name = ["aco", "api", "bcda", "bib", "bls", "ccw", "cli", "cms", "conf", "llm", "mail", "opps", "perf", "pfs", "prisma", "qpp", "rec", "rex", "sem"]
namespace = true

View File

@@ -1,34 +1,63 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended"],
"platform": "gitea",
"endpoint": "http://git:3000/api/v1",
"gitAuthor": "Renovate Bot <renovate@fhirworx.io>",
"automerge": false,
"labels": ["dependencies"],
"automerge": false,
"minimumReleaseAge": "7 days",
"osvVulnerabilityAlerts": true,
"vulnerabilityAlerts": {
"labels": ["dependencies", "security"]
},
"lockFileMaintenance": {
"enabled": true,
"schedule": ["before 6am on monday"]
},
"packageRules": [
{
"matchUpdateTypes": ["minor", "patch"],
"matchUpdateTypes": ["minor", "patch", "digest", "lockFileMaintenance"],
"automerge": true
},
{
"description": "A Python minor (3.13 -> 3.15) is a runtime migration, not a patch: never automerge it.",
"matchDepNames": ["python"],
"automerge": false
},
{
"description": "pymupdf 2.x bundles the Polyform-Noncommercial layout code and onnxruntime — a licence decision (see pyproject.toml), not a routine bump.",
"matchPackageNames": ["pymupdf"],
"allowedVersions": "<2"
},
{
"description": "Generated-workflow tool pins: gen_config.py must re-emit .gitea/workflows after the bump, so a human merges these.",
"matchFileNames": ["dev/scripts/backends/gitea.py"],
"automerge": false,
"prBodyNotes": ["Run `uv run python dev/scripts/gen_config.py` on this branch and commit the regenerated workflows before merging."]
}
],
"pip_requirements": {
"fileMatch": ["pyproject.toml"]
},
"docker-compose": {
"fileMatch": ["compose.yml"]
},
"dockerfile": {
"fileMatch": ["infra/images/*.Dockerfile"]
"managerFilePatterns": ["/(^|/)[^/]+\\.Dockerfile$/"]
},
"github-actions": {
"description": "Only the hand-written Renovate workflow; the rest of .gitea/workflows is generated by gen_config.py.",
"managerFilePatterns": ["/^\\.gitea/workflows/renovate\\.yml$/"]
},
"customManagers": [
{
"customType": "regex",
"description": "Bump `ARG *_VERSION=` pins annotated with a `# renovate:` comment (marimo, yq, otel-cli, …) that the dockerfile manager can't see.",
"fileMatch": ["(^|/)[^/]+\\.Dockerfile$"],
"managerFilePatterns": ["/(^|/)[^/]+\\.Dockerfile$/"],
"matchStrings": [
"# renovate: datasource=(?<datasource>\\S+) depName=(?<depName>\\S+)(?: extractVersion=(?<extractVersion>\\S+))?\\s+ARG \\w+?_VERSION=(?<currentValue>\\S+)"
]
},
{
"customType": "regex",
"description": "Bump the trivy/crane pins the CI workflow generator installs.",
"managerFilePatterns": ["/^dev/scripts/backends/gitea\\.py$/"],
"matchStrings": [
"# renovate: datasource=(?<datasource>\\S+) depName=(?<depName>\\S+)\\s+\\w+_VERSION = \"(?<currentValue>[^\"]+)\""
],
"extractVersionTemplate": "^v?(?<version>.*)$"
}
]
}

View File

@@ -15,14 +15,16 @@ def int_brand_with_generic_available(
Operations: filter, select, unique
Dependencies: pharmacy.rxnorm_generic_available
"""
today = date.today().isoformat()
# The table declares a date, but seeds can arrive as ISO strings; Polars 2
# no longer casts String -> Date, so parse explicitly.
start = nw.col("product_startmarketingdate")
schema = pharmacy__rxnorm_generic_available.collect_schema()
if schema["product_startmarketingdate"] == nw.String:
start = start.str.to_date("%Y-%m-%d")
return (
pharmacy__rxnorm_generic_available.filter(
(nw.col("ndc_product_tty").is_in(["SCD", "GPCK"]))
& (
nw.col("product_startmarketingdate").cast(nw.Date)
<= nw.lit(today).cast(nw.Date)
)
& (start <= nw.lit(date.today(), nw.Date))
)
.select(
nw.col("product_rxcui").alias("brand_with_generic_available"),

View File

@@ -235,7 +235,7 @@ df = ctx.load("core.encounter")
# Columns are automatically renamed from Unity Catalog names
assert "encounter_id" in df.columns # Mapped from encntr_id
assert "person_id" in df.columns # Mapped from mbr_id
assert "person_id" in df.columns # Mapped from mbr_id
```
## Workspace Configuration
@@ -310,6 +310,7 @@ encounters = ctx.load("core.encounter")
# Use in pipeline
from aco.pipe import readmissions
results = readmissions.pipeline.run(ctx.load)
```
@@ -474,11 +475,13 @@ Error: User does not have CREATE permission on catalog 'aco'
```python
# 1. Develop locally with DuckDB
from aco.lake import DuckDBContext
ctx_local = DuckDBContext(database="notebooks/aco.duckdb")
results = execute(readmissions.pipeline, ctx_local)
# 2. Deploy to Unity Catalog
from aco.lake import IcebergContext
ctx_unity = IcebergContext(
catalog_uri="https://dbc-7474655000864661.cloud.databricks.com/api/2.1/unity-catalog/iceberg",
warehouse="aco",

View File

@@ -233,6 +233,27 @@ def compute_test_targets(
return deduped, ", ".join(reasons) if reasons else "changed files"
# Variables git exports to hooks that pin every nested git call to *this*
# repository. Tests that build scratch repos in tmp dirs inherited them and
# committed into the repo being committed to (and wrote its shared config).
_HOOK_GIT_VARS = (
"GIT_DIR",
"GIT_INDEX_FILE",
"GIT_WORK_TREE",
"GIT_PREFIX",
"GIT_COMMON_DIR",
"GIT_OBJECT_DIRECTORY",
"GIT_ALTERNATE_OBJECT_DIRECTORIES",
)
def _test_env() -> dict[str, str]:
"""The hook's environment minus git's repo-locating variables."""
import os
return {k: v for k, v in os.environ.items() if k not in _HOOK_GIT_VARS}
def run_step(label: str, cmd: list[str], **kwargs) -> int:
"""Run a subprocess step, printing the label."""
print(f"==> pre-commit: {label}")
@@ -318,6 +339,7 @@ def main() -> int:
["uv", "run", "python", "-m", "pytest"]
+ targets
+ ["--no-cov", "--tb=short", "-q", "-n", "auto"],
env=_test_env(),
)
if rc != 0:
return rc

View File

@@ -320,4 +320,3 @@ path_filter = ["infra/images/llm.Dockerfile", "src/**", "pyproject.toml"]
dockerfile = "infra/images/mc.Dockerfile"
context = "infra/rustfs/"
path_filter = ["infra/rustfs/**", "infra/images/mc.Dockerfile"]
scan = false

View File

@@ -104,9 +104,16 @@ def test_image_jobs_gate_on_changes_outputs_not_head_commit():
# ── Behavioural: run the detector script against real git history ──────
def _clean_env() -> dict[str, str]:
"""os.environ without GIT_* — under a pre-commit hook GIT_DIR and
GIT_INDEX_FILE point at the real repo, and every scratch-repo command
here would commit into it instead."""
return {k: v for k, v in os.environ.items() if not k.startswith("GIT_")}
def _git(*args: str, cwd: Path) -> str:
env = {
**os.environ,
**_clean_env(),
"GIT_AUTHOR_NAME": "t",
"GIT_AUTHOR_EMAIL": "t@example.invalid",
"GIT_COMMITTER_NAME": "t",
@@ -170,7 +177,7 @@ def _run_detector(checkout: Path, before: str, sha: str, tmp_path: Path) -> dict
["bash", "-e", "-c", step["run"]],
cwd=checkout,
env={
**os.environ,
**_clean_env(),
"BEFORE": before,
"GITHUB_SHA": sha,
"GITHUB_OUTPUT": str(out_file),

View File

@@ -9,6 +9,7 @@ from sem.hooks import (
_changed_test_dirs,
_classify,
_staged_files,
_test_env,
_top_level_tests,
check_syntax,
compute_test_targets,
@@ -195,6 +196,28 @@ class TestRunStep:
assert rc == 1
class TestTestEnv:
def test_strips_repo_locating_git_vars(self, monkeypatch):
monkeypatch.setenv("GIT_DIR", "/repo/.git")
monkeypatch.setenv("GIT_INDEX_FILE", "/repo/.git/index")
monkeypatch.setenv("GIT_AUTHOR_NAME", "kept")
env = _test_env()
assert "GIT_DIR" not in env
assert "GIT_INDEX_FILE" not in env
assert env["GIT_AUTHOR_NAME"] == "kept"
def test_nested_git_sees_its_own_repo(self, tmp_path, monkeypatch):
"""Under a hook, `git init` in a tmp dir must not land in GIT_DIR."""
import subprocess
monkeypatch.setenv("GIT_DIR", str(tmp_path / "hooked.git"))
scratch = tmp_path / "scratch"
scratch.mkdir()
subprocess.run(["git", "init", "-q"], cwd=scratch, env=_test_env(), check=True)
assert (scratch / ".git").is_dir()
assert not (tmp_path / "hooked.git").exists()
class TestMain:
@patch("sem.hooks._staged_files", return_value=[])
def test_no_staged_files_returns_zero(self, mock_staged):

View File

@@ -45,9 +45,10 @@ class TestBindMountSourcesExist:
missing = []
for svc, host_path, full in mounts:
# ./data/ is gitignored runtime state — populated by service
# bootstrap on first run, not present in a fresh checkout (CI).
if host_path.startswith("./data/"):
# ./data/ and ./.state/ are gitignored runtime state — populated
# by service bootstrap on first run, not present in a fresh
# checkout (CI).
if host_path.startswith(("./data/", "./.state/")):
continue
resolved = ROOT / host_path
if not resolved.exists():

View File

@@ -1470,7 +1470,7 @@ class TestSemHooksMainEntry:
):
call_count = [0]
def step_side_effect(label, cmd):
def step_side_effect(label, cmd, **_kw):
call_count[0] += 1
if "notebook run" in label:
return 1 # line 348

View File

@@ -100,7 +100,7 @@ class TestSemHooksFormatCheckFails:
call_count = [0]
def step_side_effect(label, cmd):
def step_side_effect(label, cmd, **_kw):
call_count[0] += 1
if "format" in label:
return 1
@@ -144,7 +144,7 @@ class TestSemHooksPytestFails:
call_count = [0]
def step_side_effect(label, cmd):
def step_side_effect(label, cmd, **_kw):
call_count[0] += 1
if "pytest" in label:
return 1
@@ -170,7 +170,7 @@ class TestSemHooksNotebookFail:
call_count = [0]
def step_side_effect(label, cmd):
def step_side_effect(label, cmd, **_kw):
call_count[0] += 1
if "marimo" in label:
return 1

5373
uv.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +0,0 @@
# Project-level uv configuration.
# Prevents broken .venv symlinks when uv runs under different users.
link-mode = "copy"
python-preference = "managed"