Skip to content

Commit cf61328

Browse files
authored
Release v0.12.0: correctness, security, and MCP evaluations (#75)
* Prepare v0.12.0 correctness and evaluation release * Align documented release verification with supported workflow * Use US spelling in VFS documentation * Fix README configuration guide link * Resolve independent release review findings * Standardize US spelling in changed files * Harden filter operands and R logging * Keep frame locals out of tracebacks * Resolve final release review findings * Preserve safe actionable R errors * Use native uv build for v0.12.0 * Resolve independent release review * Make secure reads portable and metadata-correct * Resolve final release review edge cases * Fail closed on release image validation * Block parenthesized formula code execution * Harden staged MCP file reads
1 parent 40a42ef commit cf61328

81 files changed

Lines changed: 3141 additions & 2701 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/build-base-image.yml

Lines changed: 35 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ on:
1818
push:
1919
branches: [main]
2020
paths:
21-
- 'Dockerfile.base'
21+
- 'Dockerfile'
2222
- '.github/workflows/build-base-image.yml'
2323

2424
env:
@@ -51,11 +51,11 @@ jobs:
5151
id: check-rebuild
5252
run: |
5353
# Check if base image exists and get its creation date
54-
if docker manifest inspect ${{ env.REGISTRY }}/${{ env.BASE_IMAGE_NAME }}:latest >/dev/null 2>&1; then
54+
if docker manifest inspect "${{ env.REGISTRY }}/${{ env.BASE_IMAGE_NAME }}:latest" >/dev/null 2>&1; then
5555
echo "Base image exists"
5656
5757
# Get image creation date (simplified check)
58-
CREATION_DATE=$(docker manifest inspect ${{ env.REGISTRY }}/${{ env.BASE_IMAGE_NAME }}:latest | jq -r '.history[0].created // empty')
58+
CREATION_DATE=$(docker manifest inspect "${{ env.REGISTRY }}/${{ env.BASE_IMAGE_NAME }}:latest" | jq -r '.history[0].created // empty')
5959
6060
if [ -n "$CREATION_DATE" ]; then
6161
# Check if image is older than 7 days
@@ -67,18 +67,18 @@ jobs:
6767
6868
if [ "$AGE_DAYS" -lt 7 ] && [ "${{ github.event.inputs.force_rebuild }}" != "true" ]; then
6969
echo "Image is recent (< 7 days old) and no force rebuild requested"
70-
echo "rebuild_needed=false" >> $GITHUB_OUTPUT
70+
echo "rebuild_needed=false" >> "$GITHUB_OUTPUT"
7171
else
7272
echo "Image is old (>= 7 days) or force rebuild requested"
73-
echo "rebuild_needed=true" >> $GITHUB_OUTPUT
73+
echo "rebuild_needed=true" >> "$GITHUB_OUTPUT"
7474
fi
7575
else
7676
echo "Could not determine image age, rebuilding"
77-
echo "rebuild_needed=true" >> $GITHUB_OUTPUT
77+
echo "rebuild_needed=true" >> "$GITHUB_OUTPUT"
7878
fi
7979
else
8080
echo "Base image does not exist, building"
81-
echo "rebuild_needed=true" >> $GITHUB_OUTPUT
81+
echo "rebuild_needed=true" >> "$GITHUB_OUTPUT"
8282
fi
8383
8484
- name: Extract metadata
@@ -98,7 +98,8 @@ jobs:
9898
uses: docker/build-push-action@v7
9999
with:
100100
context: .
101-
file: ./Dockerfile.base
101+
file: ./Dockerfile
102+
target: base
102103
platforms: linux/amd64,linux/arm64
103104
push: true
104105
tags: ${{ steps.meta.outputs.tags }}
@@ -116,7 +117,7 @@ jobs:
116117
echo "🧪 Testing base image functionality..."
117118
118119
# Test R availability and packages
119-
docker run --rm ${{ env.REGISTRY }}/${{ env.BASE_IMAGE_NAME }}:latest R -e "
120+
docker run --rm "${{ env.REGISTRY }}/${{ env.BASE_IMAGE_NAME }}:latest" R -e "
120121
cat('✅ R version:', R.version.string, '\n')
121122
122123
# Test key packages
@@ -136,19 +137,16 @@ jobs:
136137
}
137138
"
138139
139-
# Test mkcert availability
140-
docker run --rm ${{ env.REGISTRY }}/${{ env.BASE_IMAGE_NAME }}:latest mkcert -version
141-
142140
# Test Python availability
143-
docker run --rm ${{ env.REGISTRY }}/${{ env.BASE_IMAGE_NAME }}:latest python3 --version
141+
docker run --rm "${{ env.REGISTRY }}/${{ env.BASE_IMAGE_NAME }}:latest" python3 --version
144142
145143
echo "✅ Base image tests passed"
146144
147145
- name: Update image metadata
148146
if: steps.check-rebuild.outputs.rebuild_needed == 'true'
149147
run: |
150148
# Extract and display build information
151-
docker run --rm ${{ env.REGISTRY }}/${{ env.BASE_IMAGE_NAME }}:latest cat /opt/rmcp-base-info.json | jq .
149+
docker run --rm "${{ env.REGISTRY }}/${{ env.BASE_IMAGE_NAME }}:latest" cat /opt/rmcp-base-info.json | jq .
152150
153151
echo "📊 Base image build completed:"
154152
echo " Registry: ${{ env.REGISTRY }}/${{ env.BASE_IMAGE_NAME }}"
@@ -161,27 +159,30 @@ jobs:
161159
- name: Create release note
162160
if: steps.check-rebuild.outputs.rebuild_needed == 'true' && github.event_name == 'schedule'
163161
run: |
164-
echo "📦 **Base Image Updated**" >> $GITHUB_STEP_SUMMARY
165-
echo "" >> $GITHUB_STEP_SUMMARY
166-
echo "The RMCP base R environment has been rebuilt with:" >> $GITHUB_STEP_SUMMARY
167-
echo "- 🔄 Latest system packages" >> $GITHUB_STEP_SUMMARY
168-
echo "- 📊 All R statistical packages" >> $GITHUB_STEP_SUMMARY
169-
echo "- 🔒 mkcert for HTTPS development" >> $GITHUB_STEP_SUMMARY
170-
echo "- 🐍 Python development environment" >> $GITHUB_STEP_SUMMARY
171-
echo "" >> $GITHUB_STEP_SUMMARY
172-
echo "**Impact:** CI builds will now be significantly faster! ⚡" >> $GITHUB_STEP_SUMMARY
173-
echo "" >> $GITHUB_STEP_SUMMARY
174-
echo "**Next Steps:** " >> $GITHUB_STEP_SUMMARY
175-
echo "1. Main CI will automatically use the new base image" >> $GITHUB_STEP_SUMMARY
176-
echo "2. Development Docker builds will benefit from faster startup" >> $GITHUB_STEP_SUMMARY
177-
echo "3. No action required from developers" >> $GITHUB_STEP_SUMMARY
162+
{
163+
echo "📦 **Base Image Updated**"
164+
echo ""
165+
echo "The RMCP base R environment has been rebuilt with:"
166+
echo "- 🔄 Latest system packages"
167+
echo "- 📊 All R statistical packages"
168+
echo "- 🐍 Python runtime environment"
169+
echo ""
170+
echo "**Impact:** Production images inherit only runtime dependencies."
171+
echo ""
172+
echo "**Next Steps:** "
173+
echo "1. Main CI will automatically use the new base image"
174+
echo "2. Development Docker builds will benefit from faster startup"
175+
echo "3. No action required from developers"
176+
} >> "$GITHUB_STEP_SUMMARY"
178177
179178
- name: Skip notification
180179
if: steps.check-rebuild.outputs.rebuild_needed == 'false'
181180
run: |
182-
echo "ℹ️ Base image rebuild skipped - image is recent and no changes detected" >> $GITHUB_STEP_SUMMARY
183-
echo "" >> $GITHUB_STEP_SUMMARY
184-
echo "**Current base image:** ${{ env.REGISTRY }}/${{ env.BASE_IMAGE_NAME }}:latest" >> $GITHUB_STEP_SUMMARY
185-
echo "**Reason:** Image is less than 7 days old and no force rebuild requested" >> $GITHUB_STEP_SUMMARY
186-
echo "" >> $GITHUB_STEP_SUMMARY
187-
echo "To force a rebuild, use the manual trigger with 'force_rebuild' option." >> $GITHUB_STEP_SUMMARY
181+
{
182+
echo "ℹ️ Base image rebuild skipped - image is recent and no changes detected"
183+
echo ""
184+
echo "**Current base image:** ${{ env.REGISTRY }}/${{ env.BASE_IMAGE_NAME }}:latest"
185+
echo "**Reason:** Image is less than 7 days old and no force rebuild requested"
186+
echo ""
187+
echo "To force a rebuild, use the manual trigger with 'force_rebuild' option."
188+
} >> "$GITHUB_STEP_SUMMARY"

.github/workflows/ci.yml

Lines changed: 46 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
python-version: "3.11"
2525

2626
- name: Install uv
27-
uses: astral-sh/setup-uv@v9.0.0
27+
uses: astral-sh/setup-uv@v10.0.1
2828
with:
2929
enable-cache: true
3030

@@ -47,6 +47,28 @@ jobs:
4747
# Run all unit tests (Python-only, schema validation, etc.)
4848
uv run pytest tests/unit/ -v --tb=short
4949
50+
windows-vfs:
51+
name: Windows VFS Tests
52+
runs-on: windows-latest
53+
steps:
54+
- uses: actions/checkout@v7
55+
56+
- name: Set up Python
57+
uses: actions/setup-python@v7
58+
with:
59+
python-version: "3.11"
60+
61+
- name: Install uv
62+
uses: astral-sh/setup-uv@v10.0.1
63+
with:
64+
enable-cache: true
65+
66+
- name: Install dependencies
67+
run: uv sync --group dev
68+
69+
- name: Run VFS tests
70+
run: uv run pytest tests/unit/core/test_contract_validation.py -v --tb=short
71+
5072
# Build Docker images for R testing (both development and production)
5173
docker-build:
5274
name: Build R Testing Environment
@@ -78,21 +100,21 @@ jobs:
78100
79101
# Always build if it's a manual trigger
80102
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
81-
echo "should_build=true" >> $GITHUB_OUTPUT
103+
echo "should_build=true" >> "$GITHUB_OUTPUT"
82104
echo "🔄 Building: Manual trigger (forced)"
83105
exit 0
84106
fi
85107
86108
# Always build on main branch pushes (production deployments)
87109
if [ "${{ github.event_name }}" = "push" ] && [ "${{ github.ref }}" = "refs/heads/main" ]; then
88-
echo "should_build=true" >> $GITHUB_OUTPUT
110+
echo "should_build=true" >> "$GITHUB_OUTPUT"
89111
echo "🔄 Building: Main branch push"
90112
exit 0
91113
fi
92114
93115
# For local testing with act, always build (git diff doesn't work reliably)
94116
if [ -n "${ACT:-}" ]; then
95-
echo "should_build=true" >> $GITHUB_OUTPUT
117+
echo "should_build=true" >> "$GITHUB_OUTPUT"
96118
echo "🔄 Building: Local testing with act"
97119
exit 0
98120
fi
@@ -107,7 +129,6 @@ jobs:
107129
# dependency bump changes what the image contains.
108130
docker_relevant_files=(
109131
"Dockerfile"
110-
"Dockerfile.base"
111132
"pyproject.toml"
112133
"uv.lock"
113134
"rmcp/"
@@ -124,14 +145,14 @@ jobs:
124145
done
125146
126147
if [ "$needs_build" = true ]; then
127-
echo "should_build=true" >> $GITHUB_OUTPUT
148+
echo "should_build=true" >> "$GITHUB_OUTPUT"
128149
else
129-
echo "should_build=false" >> $GITHUB_OUTPUT
150+
echo "should_build=false" >> "$GITHUB_OUTPUT"
130151
echo "⏭️ Skipping: No Docker-relevant files changed"
131152
fi
132153
else
133154
# No previous commit available (initial commit or shallow clone)
134-
echo "should_build=true" >> $GITHUB_OUTPUT
155+
echo "should_build=true" >> "$GITHUB_OUTPUT"
135156
echo "🔄 Building: No previous commit available for comparison"
136157
fi
137158
@@ -221,7 +242,7 @@ jobs:
221242
if: steps.check-changes.outputs.should_build == 'false'
222243
run: |
223244
echo "Using existing development image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest"
224-
echo "image=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest" >> $GITHUB_OUTPUT
245+
echo "image=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest" >> "$GITHUB_OUTPUT"
225246
226247
- name: Generate artifact attestation for development image
227248
if: steps.check-changes.outputs.should_build == 'true'
@@ -383,7 +404,11 @@ jobs:
383404
- name: Run smoke tests (basic functionality)
384405
run: |
385406
# Ensure R is in PATH for skip condition checks
386-
which R && export R_AVAILABLE=1 || export R_AVAILABLE=0
407+
if which R; then
408+
export R_AVAILABLE=1
409+
else
410+
export R_AVAILABLE=0
411+
fi
387412
echo "R available: $R_AVAILABLE"
388413
pytest tests/smoke/ -v --tb=short --cov=rmcp --cov-report=xml
389414
@@ -419,14 +444,14 @@ jobs:
419444
SERVER_PID=$!
420445
421446
HEALTHY=0
422-
for i in $(seq 1 30); do
447+
for _ in $(seq 1 30); do
423448
if curl -s --cacert "$(mkcert -CAROOT)/rootCA.pem" https://localhost:8443/health | grep -q healthy; then
424449
HEALTHY=1
425450
break
426451
fi
427452
sleep 1
428453
done
429-
kill $SERVER_PID || true
454+
kill "$SERVER_PID" || true
430455
if [ "$HEALTHY" != "1" ]; then
431456
echo "❌ HTTPS health check failed"
432457
exit 1
@@ -532,7 +557,7 @@ jobs:
532557
python-version: "3.11"
533558

534559
- name: Install uv
535-
uses: astral-sh/setup-uv@v9.0.0
560+
uses: astral-sh/setup-uv@v10.0.1
536561
with:
537562
enable-cache: true
538563

@@ -578,7 +603,7 @@ jobs:
578603
docker images | grep rmcp || echo "No RMCP images found"
579604
580605
# Export for subsequent steps
581-
echo "RMCP_PRODUCTION_IMAGE=$RMCP_PRODUCTION_IMAGE" >> $GITHUB_ENV
606+
echo "RMCP_PRODUCTION_IMAGE=$RMCP_PRODUCTION_IMAGE" >> "$GITHUB_ENV"
582607
583608
- name: Run Docker deployment scenario tests
584609
run: |
@@ -614,6 +639,12 @@ jobs:
614639
615640
echo "✅ All Docker scenario tests completed"
616641
642+
- name: Run production MCP evaluations
643+
run: |
644+
test -n "$RMCP_PRODUCTION_IMAGE"
645+
RMCP_EVAL_DOCKER_IMAGE="$RMCP_PRODUCTION_IMAGE" \
646+
uv run pytest tests/evals/test_mcp_server_evals.py -v --tb=short
647+
617648
# Container-based tests for production image validation
618649
production-container-tests:
619650
name: Production Container Tests
@@ -695,7 +726,7 @@ jobs:
695726
python-version: ${{ matrix.python-version }}
696727

697728
- name: Install uv
698-
uses: astral-sh/setup-uv@v9.0.0
729+
uses: astral-sh/setup-uv@v10.0.1
699730
with:
700731
enable-cache: true
701732

.github/workflows/deploy.yml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,24 +40,24 @@ jobs:
4040

4141
- name: Configure Docker for Artifact Registry
4242
run: |
43-
gcloud auth configure-docker $AR_REGION-docker.pkg.dev
43+
gcloud auth configure-docker "$AR_REGION-docker.pkg.dev"
4444
4545
- name: Pull image from GitHub Container Registry
4646
run: |
47-
docker pull $GHCR_IMAGE
47+
docker pull "$GHCR_IMAGE"
4848
4949
- name: Tag and push to Artifact Registry
5050
run: |
51-
docker tag $GHCR_IMAGE $AR_IMAGE
52-
docker push $AR_IMAGE
51+
docker tag "$GHCR_IMAGE" "$AR_IMAGE"
52+
docker push "$AR_IMAGE"
5353
5454
- name: Deploy to Cloud Run
5555
env:
5656
RMCP_API_KEY: ${{ secrets.RMCP_API_KEY }}
5757
run: |
58-
gcloud run deploy $SERVICE_NAME \
59-
--image $AR_IMAGE \
60-
--region $REGION \
58+
gcloud run deploy "$SERVICE_NAME" \
59+
--image "$AR_IMAGE" \
60+
--region "$REGION" \
6161
--platform managed \
6262
--allow-unauthenticated \
6363
--command rmcp \

.github/workflows/docs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ jobs:
3333
uses: actions/checkout@v7
3434

3535
- name: Install uv
36-
uses: astral-sh/setup-uv@v9.0.0
36+
uses: astral-sh/setup-uv@v10.0.1
3737
with:
3838
enable-cache: true
3939
cache-dependency-glob: "uv.lock"

.github/workflows/python-publish.yml

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,16 @@ jobs:
2424
timeout-minutes: 15
2525
steps:
2626
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
27-
with:
28-
fetch-depth: 0 # uv-dynamic-versioning derives the version from tags
2927

30-
- uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
28+
- uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
3129

3230
- name: Build distributions
33-
run: uv build
31+
run: uv build --no-sources
3432

3533
- name: Check metadata
3634
run: uvx twine check dist/*
3735

38-
# Cheap guard against a shallow clone or missing tags yielding 0.0.0,
39-
# which would otherwise publish a bogus version under a good tag.
36+
# Prevent a mistagged release from publishing the wrong project version.
4037
- name: Confirm the built version matches the tag
4138
run: |
4239
set -euo pipefail

0 commit comments

Comments
 (0)