Skip to content
Merged
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
32 changes: 31 additions & 1 deletion .github/workflows/node-repair.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ name: Validate selected Node repairs
required: true
outputs:
candidate-artifact-id:
description: Immutable preparation artifact ID in this run; validation never replaces it. No publication occurred.
description: Immutable preparation artifact ID in this run; validation and publication never replace it.
value: ${{ jobs.prepare.outputs.candidate-artifact-id }}
permissions:
contents: read
Expand Down Expand Up @@ -102,3 +102,33 @@ jobs:
set -euo pipefail
[[ "$ARTIFACT_ID" =~ ^[1-9][0-9]*$ && "$ARCHIVE_SHA256" =~ ^[a-f0-9]{64}$ && "$CONTENT_SHA256" =~ ^[a-f0-9]{64}$ ]]
printf '::notice title=Node repair evidence v1::v1:%s:%s:%s\n' "$ARTIFACT_ID" "$ARCHIVE_SHA256" "$CONTENT_SHA256"
publish:
needs: [prepare, validate]
runs-on: ubuntu-latest
timeout-minutes: 10
concurrency:
group: node-dependency-proposal-${{ github.repository }}
cancel-in-progress: false
permissions:
contents: write
pull-requests: write
actions: read
checks: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
ref: ${{ inputs.expected-head }}
persist-credentials: false
fetch-depth: 0
- name: Publish the validated sealed candidate
env:
TRUSTED_WORKFLOW_SHA: ${{ job.workflow_sha }}
uses: $/node/repair
with:
operation: publish
candidate-artifact-id: ${{ needs.prepare.outputs.candidate-artifact-id }}
request-id: ${{ inputs.request-id }}
pull-request: ${{ inputs.pull-request }}
expected-head: ${{ inputs.expected-head }}
expected-base: ${{ inputs.expected-base }}
selections: ${{ inputs.selections }}
45 changes: 41 additions & 4 deletions docs/node-maintenance-operator.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,10 +173,9 @@ The separate `node-repair-validation-<request-id>` artifact contains human
evidence only: install/check logs, the fresh audit and a validation result.
It never replaces the preparation artifact. Project code runs with the
repository write maintainer as its trust principal; validation cannot attest
intentionally dishonest tests. A later publisher must use the original
preparation artifact and actual validation job success, never validation-local
patches, reports or logs. Publication and normal-audit retention remain later
slices, followed by the isolated live repair gate and general rollout.
intentionally dishonest tests. The publisher uses the original preparation artifact and actual validation job
success, never validation-local patches, reports or logs. Normal-audit retention
and the full isolated live repair gate remain required before general rollout.

## Check the receiver locally

Expand Down Expand Up @@ -319,3 +318,41 @@ This checkpoint prepares cumulative evidence. It does not publish dependency
changes or authorize merging. The later publisher must retain the validated
repair marker and exact native `Node-Repair-*` locator trailers for another
read-only attempt; producer job outputs alone do not persist across runs.

## Publish the validated selected repair

The reusable repair workflow now has a fresh publisher after successful
preparation and validation. Only that job receives Contents/Pull requests write
permission; its caller must grant those scopes plus Actions/Checks read. It
downloads the original three-member preparation artifact by its exact ID and
verifies the producer job, Check seal, immutable archive/content digests, source,
request, project and exact candidate dependency state. Missing cumulative history
or stale evidence stops before a branch push. Project scripts and package-manager
commands never run on this writer.

Immediately before pushing, it rechecks the canonical PR, selected batch and
head/base. The native push uses an explicit expected-head lease. Its commit
contains `Node-Repair: validated-selected-pins-v1` plus the original preparation
run/job/Check/artifact IDs, archive/content digests and trusted workflow SHA.
These trailers locate the immutable cumulative report for the next attempt;
editable PR prose is never historical audit input.

The PR body uses the sealed cumulative rows and verified candidate choices,
adds the actual successful-validation run link and binds choices to the new
head/base. It preserves the existing recognized platform section byte-for-byte,
including CRLF and per-release CVE links. A missing or ambiguous platform section
or an oversized body stops before push; inspect or replace an old proposal
through the normal audit flow rather than dropping evidence.

The push and PR-body edit are separate operations. A successful push followed
by an edit failure leaves the repair commit published and reports the incomplete
body update with its commit ID. After push, the writer rereads only an exact
unchanged pre-push PR view up to four times, with waits of one, two and four
seconds. It never repeats the push. An unexpected head or changed base, body,
open state, branch or repository stops immediately; an old view at the limit also
leaves an explicit partial result. The report is edited only after the expected
published head and unchanged remaining PR state are observed.
An uncertain push reports the attempted commit
and requires branch inspection before another attempt. Nothing merges
automatically. Normal-audit preservation and full live repair acceptance remain
required before general rollout.
32 changes: 22 additions & 10 deletions node/repair/action.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
name: Node repair candidate
description: Guard, prepare or validate a selected pnpm candidate without publication.
description: Guard, prepare, validate and publish the exact selected pnpm candidate.
inputs:
operation:
description: guard, prepare or validate.
description: guard, prepare, validate or publish.
required: true
request-id:
required: true
Expand All @@ -21,7 +21,7 @@ inputs:
description: Canonical selected package/version triples.
candidate-artifact-id:
required: false
description: Single immutable preparation artifact ID from this run, required for validate.
description: Single immutable preparation artifact ID from this run, required for validate and publish.
outputs:
candidate-artifact-id:
description: Immutable preparation artifact ID from this run.
Expand All @@ -41,18 +41,18 @@ runs:
OPERATION: ${{ inputs.operation }}
run: |
case "$OPERATION" in
guard|prepare|validate) ;;
*) echo '::error::Expected guard, prepare or validate'; exit 1 ;;
guard|prepare|validate|publish) ;;
*) echo '::error::Expected guard, prepare, validate or publish'; exit 1 ;;
esac
- name: Validate the artifact ID
if: ${{ inputs.operation == 'validate' }}
if: ${{ inputs.operation == 'validate' || inputs.operation == 'publish' }}
shell: bash
env:
ARTIFACT_ID: ${{ inputs.candidate-artifact-id }}
run: |
[[ "$ARTIFACT_ID" =~ ^[1-9][0-9]*$ ]] || { echo '::error::Expected one preparation artifact ID'; exit 1; }
- name: Download only the preparation artifact from this run
if: ${{ inputs.operation == 'validate' }}
if: ${{ inputs.operation == 'validate' || inputs.operation == 'publish' }}
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333
with:
artifact-ids: ${{ inputs.candidate-artifact-id }}
Expand All @@ -76,18 +76,18 @@ runs:
REPAIR_SELECTIONS: ${{ inputs.selections }}
run: python "$GITHUB_ACTION_PATH/../runtime/repair.py" guard
- name: Resolve the declared preparation toolchain
if: ${{ inputs.operation != 'guard' }}
if: ${{ inputs.operation == 'prepare' || inputs.operation == 'validate' }}
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 != 'guard' }}
if: ${{ inputs.operation == 'prepare' || inputs.operation == 'validate' }}
with:
node-version: ${{ env.NODE_AUDIT_NODE_VERSION }}
check-latest: true
- name: Enable the declared package manager
if: ${{ inputs.operation != 'guard' }}
if: ${{ inputs.operation == 'prepare' || inputs.operation == 'validate' }}
shell: bash
run: corepack enable
- name: Prepare and seal the selected candidate
Expand Down Expand Up @@ -137,3 +137,15 @@ runs:
name: node-repair-validation-${{ inputs.request-id }}
if-no-files-found: warn
path: ${{ runner.temp }}/node-repair-validation/
- name: Publish only the original validated candidate
if: ${{ inputs.operation == 'publish' }}
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 }}
REPAIR_ARTIFACT_ID: ${{ inputs.candidate-artifact-id }}
run: python "$GITHUB_ACTION_PATH/../runtime/repair.py" publish --artifact "$RUNNER_TEMP/node-repair-candidate"
Loading
Loading