Add provisional registration for did:me method #47
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: AI Spec Review DID Methods | |
| on: | |
| # Automatic: run on PRs that add/modify method entries. | |
| # | |
| # NOTE: pull_request_target runs in the *base* repository's context, so it has | |
| # access to repository secrets (ANTHROPIC_API_KEY) and a write-scoped token | |
| # even for pull requests opened from forks. The plain `pull_request` event does | |
| # NOT — GitHub gives fork PRs no secrets and a read-only token — which is why | |
| # the review could neither run nor post a comment on fork PRs. | |
| # | |
| # SECURITY: because this event is privileged, this workflow MUST NOT execute | |
| # any code from the pull request. It installs the tooling from the trusted base | |
| # repository and only ever reads the PR's changed `methods/*.json` files as | |
| # data (JSON.parse). It never runs `npm install`, build steps, or scripts from | |
| # the PR checkout. Do not add steps that execute PR-authored code here. | |
| pull_request_target: | |
| paths: ["methods/**"] | |
| # Manual: re-run against any PR (including PRs opened before this workflow | |
| # existed) from the Actions tab, by supplying the PR number. | |
| workflow_dispatch: | |
| inputs: | |
| pr_number: | |
| description: "Pull request number to review" | |
| required: true | |
| type: string | |
| # Comment-triggered: re-run by commenting `/ai-review` on a pull request. | |
| issue_comment: | |
| types: [created] | |
| # Least-privilege: read the code, write PR comments. No write access to the repo | |
| # contents. | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| # One in-flight review per PR; cancel superseded runs when new commits land. | |
| concurrency: | |
| group: ai-spec-review-${{ github.event.pull_request.number || github.event.issue.number || inputs.pr_number }} | |
| cancel-in-progress: true | |
| jobs: | |
| ai-spec-review: | |
| # Skip issue_comment events unless they are a `/ai-review` command on a PR. | |
| if: >- | |
| github.event_name != 'issue_comment' || | |
| (github.event.issue.pull_request != null && | |
| startsWith(github.event.comment.body, '/ai-review')) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Resolve pull request... | |
| id: pr | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| let prNumber; | |
| if (context.eventName === 'pull_request_target') { | |
| prNumber = context.payload.pull_request.number; | |
| } else if (context.eventName === 'issue_comment') { | |
| prNumber = context.payload.issue.number; | |
| } else { // workflow_dispatch | |
| prNumber = parseInt('${{ inputs.pr_number }}', 10); | |
| } | |
| const {owner, repo} = context.repo; | |
| const {data: pr} = await github.rest.pulls.get( | |
| {owner, repo, pull_number: prNumber}); | |
| core.setOutput('number', String(prNumber)); | |
| core.setOutput('base_sha', pr.base.sha); | |
| core.setOutput('head_sha', pr.head.sha); | |
| // Full "owner:branch" head ref for the gh CLI checkout below. | |
| core.setOutput('head_repo', pr.head.repo.full_name); | |
| # 1) Check out the TRUSTED base repository (the branch this workflow runs | |
| # from). This is where the tooling and review script come from — never | |
| # from the pull request. | |
| - name: Checkout base repository (trusted)... | |
| uses: actions/checkout@v4 | |
| - name: Setup Node 22... | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22.x | |
| # 2) Install the tooling from the TRUSTED base checkout, before any PR | |
| # content is present. This guarantees `npm install` never runs a | |
| # package.json / lifecycle script authored by the pull request. | |
| - name: Install did-extensions tooling (from base)... | |
| working-directory: tooling | |
| run: npm ci || npm i | |
| # 3) Fetch ONLY the changed method JSON files from the pull request head | |
| # into a scratch directory, using the GitHub CLI (read-only). We do not | |
| # check out or trust the rest of the fork. These files are pure data and | |
| # are only ever read via JSON.parse by the review script. | |
| - name: Fetch changed method files from the PR (data only)... | |
| id: changed | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| REPO: ${{ github.repository }} | |
| PR_NUMBER: ${{ steps.pr.outputs.number }} | |
| HEAD_SHA: ${{ steps.pr.outputs.head_sha }} | |
| run: | | |
| set -euo pipefail | |
| mkdir -p pr-methods | |
| # List files changed in the PR, restricted to methods/*.json additions, | |
| # copies, modifications and renames (deletions need no spec review). | |
| mapfile -t FILES < <(gh pr diff "$PR_NUMBER" --name-only \ | |
| | grep -E '^methods/.*\.json$' || true) | |
| KEPT=() | |
| for f in "${FILES[@]:-}"; do | |
| [ -z "$f" ] && continue | |
| base="$(basename "$f")" | |
| case "$base" in | |
| index.html|index.json|example.json) continue ;; | |
| esac | |
| # Download the file's content at the PR head commit. This is a raw | |
| # data blob — never executed. | |
| if gh api "repos/${REPO}/contents/${f}?ref=${HEAD_SHA}" \ | |
| --jq '.content' 2>/dev/null | base64 -d > "pr-methods/${base}"; then | |
| # Overlay the PR's version of the file onto the base checkout so the | |
| # review script (which reads from ../methods) sees the submission. | |
| cp "pr-methods/${base}" "methods/${base}" | |
| KEPT+=("$f") | |
| echo "Fetched $f" | |
| else | |
| echo "Could not fetch $f from PR head; skipping." | |
| fi | |
| done | |
| { | |
| echo "files<<EOF" | |
| for f in "${KEPT[@]:-}"; do [ -n "$f" ] && echo "$f"; done | |
| echo "EOF" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Run AI preliminary specification review... | |
| id: review | |
| working-directory: tooling | |
| env: | |
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | |
| CHANGED_FILES: ${{ steps.changed.outputs.files }} | |
| REVIEW_OUTPUT: ${{ github.workspace }}/ai-spec-review.md | |
| # Do not fail the step here; we always want to post the comment first. | |
| # The gating decision is made in the "Enforce" step below using the | |
| # exit code captured here. | |
| run: | | |
| set +e | |
| npm run --silent spec-review | |
| echo "exit_code=$?" >> "$GITHUB_OUTPUT" | |
| set -e | |
| - name: Post/update review comment... | |
| if: always() | |
| continue-on-error: true | |
| uses: actions/github-script@v7 | |
| env: | |
| PR_NUMBER: ${{ steps.pr.outputs.number }} | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const marker = '<!-- ai-spec-review -->'; | |
| let body; | |
| try { | |
| body = fs.readFileSync( | |
| `${process.env.GITHUB_WORKSPACE}/ai-spec-review.md`, 'utf8'); | |
| } catch (e) { | |
| body = '## 🤖 AI Preliminary Specification Review\n\n' + | |
| '> ⚠️ The review did not produce a report. A registry editor ' + | |
| 'should review the specification(s) manually.'; | |
| } | |
| body = `${marker}\n${body}`; | |
| const {owner, repo} = context.repo; | |
| const issue_number = parseInt(process.env.PR_NUMBER, 10); | |
| try { | |
| const {data: comments} = await github.rest.issues.listComments( | |
| {owner, repo, issue_number, per_page: 100}); | |
| const existing = comments.find( | |
| (c) => c.body && c.body.includes(marker)); | |
| if (existing) { | |
| await github.rest.issues.updateComment( | |
| {owner, repo, comment_id: existing.id, body}); | |
| } else { | |
| await github.rest.issues.createComment( | |
| {owner, repo, issue_number, body}); | |
| } | |
| } catch (e) { | |
| // Commenting can fail (e.g. token permissions); do not fail the | |
| // whole run over it — the verdict is still enforced below and the | |
| // report is available in the job summary. | |
| core.warning(`Could not post the review comment: ${e.message}`); | |
| await core.summary | |
| .addRaw(body) | |
| .write(); | |
| } | |
| - name: Enforce preliminary review result... | |
| if: always() | |
| run: | | |
| CODE="${{ steps.review.outputs.exit_code }}" | |
| if [ "$CODE" = "1" ]; then | |
| echo "::error::One or more submissions failed the AI preliminary" \ | |
| "specification review. See the PR comment for details." | |
| exit 1 | |
| fi | |
| echo "AI preliminary specification review passed (exit code $CODE)." |