Skip to content

Commit 1831970

Browse files
kim-emclaude
andauthored
feat: add nanoda external type checker support (#145)
This PR adds support for verifying Lean projects with [nanoda](https://github.com/ammkrn/nanoda_lib), an independent Lean 4 type checker written in Rust. ## New Inputs | Input | Default | Description | |-------|---------|-------------| | `nanoda` | `"false"` | Enable nanoda verification | | `nanoda-allow-sorry` | `"true"` | Permit sorryAx axiom | | `nanoda-on-main-only` | `"true"` | Only run on push to main, not PRs | ## New Output - `nanoda-status`: `"SUCCESS"` | `"FAILURE"` | `""` ## Reusable Daily Workflow Also adds `.github/workflows/nanoda-daily.yml` - a reusable workflow for scheduled daily verification with configurable notifications: ```yaml jobs: verify: uses: leanprover/lean-action/.github/workflows/nanoda-daily.yml@v1 # Notifications: issue (default), webhook, zulip, or none ``` ## Implementation Notes - Rust toolchain is installed automatically if not present - Uses `kim-em/lean4export` fork with fix for nonDep normalization (pending leanprover/lean4export#11) - Uses `ammkrn/nanoda_lib` debug branch with kernel compatibility fixes ## Testing Successfully tested on: - sphere-eversion: 352,154 declarations (~6 min) - Carleson: 353,613 declarations (~6 min) - FLT: 420,742 declarations (~11 min) - Mathlib: 627,356 declarations (~17 min) 🤖 Prepared with Claude Code --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 5df9cab commit 1831970

5 files changed

Lines changed: 387 additions & 1 deletion

File tree

.github/workflows/nanoda-daily.yml

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
# Reusable workflow for daily nanoda verification
2+
# Users can call this workflow from their repository:
3+
#
4+
# name: Daily nanoda verification
5+
# on:
6+
# schedule:
7+
# - cron: '0 0 * * *'
8+
# workflow_dispatch:
9+
# jobs:
10+
# verify:
11+
# uses: leanprover/lean-action/.github/workflows/nanoda-daily.yml@v1
12+
# secrets:
13+
# webhook-url: ${{ secrets.WEBHOOK_URL }} # optional
14+
# zulip-api-key: ${{ secrets.ZULIP_API_KEY }} # optional
15+
16+
name: nanoda Daily Verification
17+
18+
on:
19+
workflow_call:
20+
inputs:
21+
allow-sorry:
22+
description: 'Permit sorryAx axiom'
23+
type: boolean
24+
default: true
25+
notify:
26+
description: 'Notification method: issue, webhook, zulip, none'
27+
type: string
28+
default: 'issue'
29+
zulip-stream:
30+
description: 'Zulip stream name (if using zulip notify)'
31+
type: string
32+
default: 'ci'
33+
zulip-topic:
34+
description: 'Zulip topic (if using zulip notify)'
35+
type: string
36+
default: 'nanoda'
37+
zulip-org-url:
38+
description: 'Zulip organization URL (required if using zulip notify)'
39+
type: string
40+
default: ''
41+
secrets:
42+
webhook-url:
43+
description: 'Slack/Discord webhook URL (optional)'
44+
required: false
45+
zulip-api-key:
46+
description: 'Zulip bot API key (optional)'
47+
required: false
48+
49+
jobs:
50+
nanoda-verify:
51+
runs-on: ubuntu-latest
52+
steps:
53+
- name: Checkout repository
54+
uses: actions/checkout@v4
55+
56+
- name: Run lean-action with nanoda
57+
id: lean-action
58+
uses: leanprover/lean-action@v1
59+
with:
60+
nanoda: true
61+
nanoda-allow-sorry: ${{ inputs.allow-sorry }}
62+
63+
- name: Create GitHub issue on failure
64+
if: failure() && inputs.notify == 'issue'
65+
uses: actions/github-script@v7
66+
with:
67+
script: |
68+
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
69+
await github.rest.issues.create({
70+
owner: context.repo.owner,
71+
repo: context.repo.repo,
72+
title: '❌ Daily nanoda verification failed',
73+
body: `The daily nanoda type checker verification failed.\n\n**Commit:** ${context.sha}\n**Run:** [View workflow run](${runUrl})\n\nPlease investigate and fix any type checking issues.`,
74+
labels: ['nanoda', 'automated', 'ci-failure']
75+
});
76+
77+
- name: Send webhook notification
78+
if: always() && inputs.notify == 'webhook'
79+
env:
80+
WEBHOOK_URL: ${{ secrets.webhook-url }}
81+
run: |
82+
if [ -z "$WEBHOOK_URL" ]; then
83+
echo "::error::webhook-url secret is required when notify is set to 'webhook'"
84+
exit 1
85+
fi
86+
STATUS="${{ job.status }}"
87+
EMOJI=$([[ "$STATUS" == "success" ]] && echo "✅" || echo "❌")
88+
RUN_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
89+
90+
curl --fail -X POST "$WEBHOOK_URL" \
91+
-H "Content-Type: application/json" \
92+
-d "{
93+
\"text\": \"$EMOJI nanoda daily verification: $STATUS\",
94+
\"blocks\": [
95+
{
96+
\"type\": \"section\",
97+
\"text\": {
98+
\"type\": \"mrkdwn\",
99+
\"text\": \"$EMOJI *nanoda daily verification*: $STATUS\n<$RUN_URL|View run> | Commit: \`${{ github.sha }}\`\"
100+
}
101+
}
102+
]
103+
}"
104+
shell: bash
105+
106+
- name: Validate Zulip configuration
107+
id: validate-zulip
108+
if: (success() || failure()) && inputs.notify == 'zulip'
109+
continue-on-error: true
110+
env:
111+
ZULIP_ORG_URL: ${{ inputs.zulip-org-url }}
112+
ZULIP_API_KEY: ${{ secrets.zulip-api-key }}
113+
run: |
114+
if [ -z "$ZULIP_ORG_URL" ]; then
115+
echo "::error::zulip-org-url input is required when notify is set to 'zulip'"
116+
exit 1
117+
fi
118+
if [ -z "$ZULIP_API_KEY" ]; then
119+
echo "::error::zulip-api-key secret is required when notify is set to 'zulip'"
120+
exit 1
121+
fi
122+
123+
- name: Send Zulip notification on success
124+
if: success() && inputs.notify == 'zulip' && steps.validate-zulip.outcome == 'success'
125+
uses: zulip/github-actions-zulip/send-message@e4c8f27c732ba9bd98ac6be0583096dea82feea5
126+
with:
127+
api-key: ${{ secrets.zulip-api-key }}
128+
email: 'github-bot@${{ inputs.zulip-org-url }}'
129+
organization-url: 'https://${{ inputs.zulip-org-url }}'
130+
to: ${{ inputs.zulip-stream }}
131+
type: 'stream'
132+
topic: ${{ inputs.zulip-topic }}
133+
content: |
134+
✅ nanoda daily verification [succeeded](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) on `${{ github.sha }}`
135+
136+
- name: Send Zulip notification on failure
137+
if: steps.lean-action.outcome == 'failure' && inputs.notify == 'zulip' && steps.validate-zulip.outcome == 'success'
138+
uses: zulip/github-actions-zulip/send-message@e4c8f27c732ba9bd98ac6be0583096dea82feea5
139+
with:
140+
api-key: ${{ secrets.zulip-api-key }}
141+
email: 'github-bot@${{ inputs.zulip-org-url }}'
142+
organization-url: 'https://${{ inputs.zulip-org-url }}'
143+
to: ${{ inputs.zulip-stream }}
144+
type: 'stream'
145+
topic: '${{ inputs.zulip-topic }} failure'
146+
content: |
147+
❌ nanoda daily verification [failed](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) on `${{ github.sha }}`

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## Unreleased
99

10+
### Added
11+
12+
- new `nanoda` input to check environment with [nanoda](https://github.com/ammkrn/nanoda_lib) external type checker
13+
- new `nanoda-allow-sorry` input to permit sorryAx axiom when running nanoda (default: true)
14+
- new `nanoda-status` output parameter
15+
- new reusable workflow `nanoda-daily.yml` for scheduled daily verification with notifications
16+
1017
### Changed
1118

1219
- rename the `lean4checker` input to `leanchecker`, while keeping `lean4checker` as a deprecated alias

README.md

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ If `lean-action` is unable to successfully run the step, `lean-action` will fail
9090
- `mk_all-check`
9191
- `check-reservoir-eligibility`
9292
- `leanchecker`
93+
- `nanoda`
9394

9495
### Automatic configuration
9596

@@ -204,7 +205,20 @@ To be certain `lean-action` runs a step, specify the desire feature with a featu
204205

205206
# Deprecated alias for `leanchecker`.
206207
lean4checker: ""
207-
208+
209+
# Check environment with nanoda external type checker.
210+
# nanoda is an independent Lean 4 type checker written in Rust.
211+
# Requires Rust toolchain (will be installed automatically if not present).
212+
# Allowed values: "true" | "false".
213+
# Default: "false"
214+
nanoda: ""
215+
216+
# When running nanoda, permit the sorryAx axiom.
217+
# Set to "false" if your project should have no sorry placeholders.
218+
# Allowed values: "true" | "false".
219+
# Default: "true"
220+
nanoda-allow-sorry: ""
221+
208222
# Enable GitHub caching.
209223
# Allowed values: "true" or "false".
210224
# If use-github-cache input is not provided, the action will use GitHub caching by default.
@@ -238,6 +252,8 @@ To be certain `lean-action` runs a step, specify the desire feature with a featu
238252
- Values: "SUCCESS" | "FAILURE" | ""
239253
- `mk_all-status`
240254
- Values: "SUCCESS" | "FAILURE" | ""
255+
- `nanoda-status`
256+
- Values: "SUCCESS" | "FAILURE" | ""
241257

242258
Note, a value of empty string indicates `lean-action` did not run the corresponding feature.
243259

@@ -302,6 +318,60 @@ steps:
302318
rm import_graph.dot
303319
```
304320
321+
## External Type Checking with nanoda
322+
323+
[nanoda](https://github.com/ammkrn/nanoda_lib) is an independent Lean 4 type checker written in Rust. It provides additional assurance that your project's declarations are well-typed by verifying them with a completely separate implementation.
324+
325+
### Enable nanoda verification
326+
327+
```yaml
328+
- uses: leanprover/lean-action@v1
329+
with:
330+
nanoda: true
331+
```
332+
333+
### Require no sorry placeholders
334+
335+
By default, nanoda permits the `sorryAx` axiom for projects with incomplete proofs. To require all proofs be complete:
336+
337+
```yaml
338+
- uses: leanprover/lean-action@v1
339+
with:
340+
nanoda: true
341+
nanoda-allow-sorry: false
342+
```
343+
344+
### Daily nanoda verification with notifications
345+
346+
For daily verification runs with automatic failure notifications, use the reusable workflow:
347+
348+
```yaml
349+
# .github/workflows/nanoda-daily.yml
350+
name: Daily nanoda verification
351+
on:
352+
schedule:
353+
- cron: '0 0 * * *'
354+
workflow_dispatch:
355+
356+
jobs:
357+
verify:
358+
uses: leanprover/lean-action/.github/workflows/nanoda-daily.yml@v1
359+
# Optional: configure notification method
360+
# with:
361+
# notify: 'issue' # default: creates GitHub issue on failure
362+
# For webhook (Slack/Discord):
363+
# with:
364+
# notify: 'webhook'
365+
# secrets:
366+
# webhook-url: ${{ secrets.WEBHOOK_URL }}
367+
# For Zulip:
368+
# with:
369+
# notify: 'zulip'
370+
# zulip-org-url: 'leanprover.zulipchat.com'
371+
# secrets:
372+
# zulip-api-key: ${{ secrets.ZULIP_API_KEY }}
373+
```
374+
305375
## Projects which use `lean-action`
306376

307377
- [leanprover-community/aesop](https://github.com/leanprover-community/aesop/blob/master/.github/workflows/build.yml#L16)

action.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,21 @@ inputs:
9191
Allowed values: "true" | "false".
9292
required: false
9393
default: "false"
94+
nanoda:
95+
description: |
96+
Check environment with nanoda external type checker.
97+
nanoda is an independent Lean 4 type checker written in Rust.
98+
Requires Rust toolchain (will be installed automatically if not present).
99+
Allowed values: "true" | "false".
100+
required: false
101+
default: "false"
102+
nanoda-allow-sorry:
103+
description: |
104+
When running nanoda, permit the sorryAx axiom.
105+
Set to "false" if your project should have no sorry placeholders.
106+
Allowed values: "true" | "false".
107+
required: false
108+
default: "true"
94109
use-github-cache:
95110
description: |
96111
Enable GitHub caching.
@@ -138,6 +153,11 @@ outputs:
138153
If lean-action detected a mathlib dependency equals "true"
139154
otherwise equals "false".
140155
value: ${{ steps.detect-mathlib.outputs.detected-mathlib }}
156+
nanoda-status:
157+
description: |
158+
The status of the nanoda check step.
159+
Allowed values: "SUCCESS" | "FAILURE" | "".
160+
value: ${{ steps.nanoda.outputs.nanoda-status }}
141161

142162
runs:
143163
using: "composite"
@@ -271,3 +291,14 @@ runs:
271291
${GITHUB_ACTION_PATH}/scripts/run_leanchecker.sh
272292
shell: bash
273293
working-directory: ${{ inputs.lake-package-directory }}
294+
295+
- name: check environment with nanoda
296+
id: nanoda
297+
if: ${{ inputs.nanoda == 'true' }}
298+
env:
299+
NANODA_ALLOW_SORRY: ${{ inputs.nanoda-allow-sorry }}
300+
run: |
301+
: Check Environment with nanoda
302+
${GITHUB_ACTION_PATH}/scripts/run_nanoda.sh
303+
shell: bash
304+
working-directory: ${{ inputs.lake-package-directory }}

0 commit comments

Comments
 (0)