Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions .github/workflows/node-repair.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
name: Prepare selected Node repairs
'on':
workflow_call:
inputs:
request-id:
type: string
required: true
pull-request:
type: string
required: true
expected-head:
type: string
required: true
expected-base:
type: string
required: true
selections:
type: string
required: true
outputs:
candidate-artifact-id:
description: Immutable preparation artifact ID in this run; no validation or publication occurred.
value: ${{ jobs.prepare.outputs.candidate-artifact-id }}
permissions:
contents: read
jobs:
guard:
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
pull-requests: read
steps:
- name: Recheck canonical PR eligibility and selections
uses: $/node/repair
with:
operation: guard
request-id: ${{ inputs.request-id }}
pull-request: ${{ inputs.pull-request }}
expected-head: ${{ inputs.expected-head }}
expected-base: ${{ inputs.expected-base }}
selections: ${{ inputs.selections }}
prepare:
needs: guard
runs-on: ubuntu-latest
timeout-minutes: 50
permissions:
contents: read
env:
npm_config_ignore_scripts: 'true'
pnpm_config_ignore_scripts: 'true'
npm_config_ignore_pnpmfile: 'true'
pnpm_config_ignore_pnpmfile: 'true'
outputs:
candidate-artifact-id: ${{ steps.prepare.outputs.candidate-artifact-id }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
ref: ${{ inputs.expected-head }}
persist-credentials: false
- name: Prepare the selected candidate using trusted workflow code
id: prepare
uses: $/node/repair
with:
operation: prepare
request-id: ${{ inputs.request-id }}
pull-request: ${{ inputs.pull-request }}
expected-head: ${{ inputs.expected-head }}
expected-base: ${{ inputs.expected-base }}
selections: ${{ inputs.selections }}
35 changes: 35 additions & 0 deletions docs/node-maintenance-operator.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,41 @@ This change is local and unreleased. Before testing the reviewed revision live:
No live permission, database, deployment or repository change is performed by
these instructions. The local/native tests cannot replace that acceptance.

## Candidate preparation boundary

The reusable `.github/workflows/node-repair.yml` currently enables only `guard`
and `prepare`. It accepts the existing five request inputs and returns
`candidate-artifact-id` for the immutable artifact created in that run.
It does not install a caller or enable repairs in an existing repository.

The read-only guard fetches the canonical repository and managed PR again.
It checks bot authorship, the same repository, maintenance branch, default
branch, exact head/base, revision marker and selected versions. It does not
require the old idle Check identity after the App has admitted the request.

Preparation checks out only the expected head without persisted credentials.
It uses the configured toolchain, existing audit engine, release cooldown and
parent/direct constraint evidence. Lifecycle and local/global pnpmfile hooks
are disabled during metadata reads, audit and lockfile-only resolution.
Unselected versions, unrelated lockfile metadata and changed peer suffixes
fail preparation. No project validation script runs in this job.

The preparation artifact contains exactly three regular files:

- `candidate.patch`: only the selected project's `pnpm-workspace.yaml` and
`pnpm-lock.yaml` changes, exported with external diff and text conversion off.
- `audit.json`: a version-1 envelope containing the exact request, normalized
project directory, structured before audit and candidate audit.
- `result.md`: the shared package report. It states that project validation and
dependency publication have not run.

The envelope rejects missing/extra fields, mismatched request or project,
unsupported versions, symlinked project parents, unconfirmed selected fixes and
new findings. Later consumers must download by the returned artifact ID from
the same run. A preparation success is not proof of a validated or published
repair. Separate validation, publication and normal-audit retention remain
later slices, followed by the isolated live repair gate and general rollout.

## Check the receiver locally

Use Node 24 and run these commands from `repair-app/`:
Expand Down
90 changes: 90 additions & 0 deletions node/repair/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
name: Node repair candidate
description: Guard or prepare a selected pnpm candidate without project scripts or publication.
inputs:
operation:
description: guard or prepare.
required: true
request-id:
required: true
description: App request UUID.
pull-request:
required: true
description: Managed pull request number.
expected-head:
required: true
description: Selected PR head SHA.
expected-base:
required: true
description: Selected PR base SHA.
selections:
required: true
description: Canonical selected package/version triples.
outputs:
candidate-artifact-id:
description: Immutable preparation artifact ID from this run.
value: ${{ steps.candidate.outputs.artifact-id }}
runs:
using: composite
steps:
- name: Validate the requested operation
shell: bash
env:
OPERATION: ${{ inputs.operation }}
run: |
case "$OPERATION" in
guard|prepare) ;;
*) echo '::error::Expected guard or prepare'; exit 1 ;;
esac
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97
with:
python-version: '3.13'
- name: Install configuration and audit parser
shell: bash
run: python -I -m pip install PyYAML==6.0.3
- name: Recheck the selected PR
if: ${{ inputs.operation == 'guard' }}
shell: bash
env:
GH_TOKEN: ${{ github.token }}
REPAIR_REQUEST: ${{ inputs.request-id }}
REPAIR_PR: ${{ inputs.pull-request }}
REPAIR_HEAD: ${{ inputs.expected-head }}
REPAIR_BASE: ${{ inputs.expected-base }}
REPAIR_SELECTIONS: ${{ inputs.selections }}
run: python "$GITHUB_ACTION_PATH/../runtime/repair.py" guard
- name: Resolve the declared preparation toolchain
if: ${{ inputs.operation == 'prepare' }}
shell: bash
run: |
set -euo pipefail
python "$GITHUB_ACTION_PATH/../runtime/project.py" --github-env "$GITHUB_ENV" --resolved-config "$RUNNER_TEMP/node-repair-config.json"
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
if: ${{ inputs.operation == 'prepare' }}
with:
node-version: ${{ env.NODE_AUDIT_NODE_VERSION }}
check-latest: true
- name: Enable the declared package manager
if: ${{ inputs.operation == 'prepare' }}
shell: bash
run: corepack enable
- name: Prepare and seal the selected candidate
if: ${{ inputs.operation == 'prepare' }}
shell: bash
env:
REPAIR_REQUEST: ${{ inputs.request-id }}
REPAIR_PR: ${{ inputs.pull-request }}
REPAIR_HEAD: ${{ inputs.expected-head }}
REPAIR_BASE: ${{ inputs.expected-base }}
REPAIR_SELECTIONS: ${{ inputs.selections }}
run: python "$GITHUB_ACTION_PATH/../runtime/repair.py" prepare --artifact "$RUNNER_TEMP/node-repair-candidate"
- name: Seal the preparation artifact
id: candidate
if: ${{ inputs.operation == 'prepare' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: node-repair-candidate-${{ inputs.request-id }}
if-no-files-found: error
path: |
${{ runner.temp }}/node-repair-candidate/candidate.patch
${{ runner.temp }}/node-repair-candidate/audit.json
${{ runner.temp }}/node-repair-candidate/result.md
Loading
Loading