Skip to content

nuxt-cloudflare: a reuse proof counts only from a Required job the callable wrote - #227

Merged
narduk-lane-automation[bot] merged 2 commits into
mainfrom
proof-callable-binding
Oct 30, 2026
Merged

narduk-lane-automation[bot] merged 2 commits into
mainfrom
proof-callable-binding

Conversation

@narduk-lane-automation

@narduk-lane-automation narduk-lane-automation Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

nuxt-cloudflare.yml's findProof honoured a Required or full-E2E proof whenever a same-repo pull_request run had a job ending in Required with the step Publish Required proof <key>. Any workflow can write those names. A pull_request run also executes the workflow from its merge ref, which a writer controls through the PR's base branch. So a writer could open a PR from an unchanged branch into a branch whose ci.yml fakes that job, and the default-branch push would reuse the forged proof.

The Opus verify on workflows#226 found this class in taskgraph.yml, and #226 fixed it there. This PR applies the same rule to the store client that nuxt-cloudflare.yml and taskgraph.yml share.

Change

  • Callable binding. The attesting job's log is read, ranged to 64 KiB. The runner's setup header, before Complete job name:, must say Uses: narduk-enterprises/workflows/.github/workflows/nuxt-cloudflare.yml@<sha>, and no step can write there. <sha> must equal the SHA this run itself uses, taken from its own referenced_workflows. This needs only actions: read, which reuse-plan and e2e-plan already have.
  • Job name. The job must be <caller job> / Required.
  • Lag retry. While a green Required job lists no proof step yet, the jobs record is re-read after 2 s and 5 s. That lag is the cause fleet#1180 proposed for main pushes missing reuse about half the time, despite a present, green proof step.
  • Copy. taskgraph.yml's byte-equal copy of the store client follows.

Proof

  • Tests: test_e2e_reuse.py, test_fast_path.py and test_ci_artifact_store.py now cover:
    • a top-level Required job;
    • a job with no callable header;
    • a Uses: line printed by a step after Complete job name:;
    • another SHA;
    • another callable;
    • a run that names no SHA;
    • a lagging jobs record, read twice and then reused.
  • Real log: the shipped headerNamesCallable was run on a real familystat-us ci / Required log (run 41105845419, first 64 KiB). It returns true for that run's referenced_workflows SHA (17b2490) and false for any other.
  • Live ranged download: the same ranged log download and parser are live in taskgraph.yml, confirmed on gonogo#370 (run 41134850603).
  • Not measured: a default-branch push reusing through this path. That needs a merge into a caller, and callers pick it up on their next pin bump.

Review fix: only the first Uses: line counts

The independent review (Sonnet 5.5) found one blocker. Between its own Uses: line and Complete job name:, the runner echoes every caller-written with: input (##[group] Inputs). An input value could carry a forged Uses: ...nuxt-cloudflare.yml@<sha> line. The check now accepts only the first Uses: line, which is always the runner's.

  • The same fix applies to taskgraph.yml's engine, which shipped the looser check in taskgraph: targeted FAST_BASE_REF tasks and store-free whole-run reuse #226.
  • test_e2e_reuse.py and test_taskgraph.py now include a log whose first Uses: names another workflow and whose echoed inputs carry the right line. Both are refused.
  • The real familystat-us Required log still matches its own SHA (true) and refuses another (false).

Gates

  • scripts/precheck on 4698e20: exit 0, 40/40 commands passed. Its timed lines:

    PASS 11.85s bash scripts/install-actionlint-native-alerts.sh actionlint
    PASS 0.85s actionlint -no-color -oneline .github/workflows/*.yml
    PASS 0.31s python3 scripts/lint_callables.py
    PASS 0.76s python3 scripts/test_actionlint_native_permissions.py
    PASS 20.04s python3 scripts/test_apple_gate.py
    PASS 1.22s python3 scripts/test_artifact_budget.py
    PASS 0.77s python3 scripts/test_cache_paths.py
    PASS 4.99s python3 scripts/test_ci_admission.py
    PASS 5.13s python3 scripts/test_ci_artifact_store.py
    PASS 6.69s python3 scripts/test_dependency_audit.py
    PASS 1.49s python3 scripts/test_design_ledger.py
    PASS 0.28s python3 scripts/test_design_publish.py
    PASS 2.40s python3 scripts/test_e2e_build_artifact.py
    PASS 4.78s python3 scripts/test_e2e_mode.py
    PASS 5.72s python3 scripts/test_e2e_pr_subset.py
    PASS 1.00s python3 scripts/test_e2e_quarantine.py
    PASS 11.85s python3 scripts/test_e2e_reuse.py
    PASS 17.38s python3 scripts/test_e2e_skip_paths.py
    PASS 1.17s python3 scripts/test_eslint_cache.py
    PASS 0.50s python3 scripts/test_exact_candidate.py
    PASS 0.41s python3 scripts/test_extra_env.py
    PASS 27.67s python3 scripts/test_fast_path.py
    PASS 0.60s python3 scripts/test_flake_digest.py
    PASS 4.18s python3 scripts/test_foundation_check.py
    PASS 5.26s python3 scripts/test_package_auth_cleanup.py
    PASS 0.76s python3 scripts/test_package_batch_metadata.py
    PASS 0.89s python3 scripts/test_permission_ceiling.py
    PASS 1.44s python3 scripts/test_playwright_toolchain.py
    PASS 13.78s python3 scripts/test_preview_gate.py
    PASS 1.49s python3 scripts/test_production_upload.py
    PASS 0.09s python3 scripts/test_python_static_gate.py
    PASS 1.08s python3 scripts/test_quality_gates.py
    PASS 2.13s python3 scripts/test_red_main_listener.py
    PASS 0.09s python3 scripts/test_required_gate.py
    PASS 0.75s python3 scripts/test_reusable_browser_tests.py
    PASS 7.98s python3 scripts/test_runner_default.py
    PASS 16.46s python3 scripts/test_script_gates.py
    PASS 1.22s python3 scripts/test_store_placement.py
    PASS 17.81s python3 scripts/test_taskgraph.py
    PASS 0.05s python3 scripts/test_tee_pipefail.py

Refs fleet#1180

🤖 Generated with Claude Code

…llable wrote

findProof honoured a Required or E2E proof when a same-repository
pull_request run had a job named `... Required` with the publish step name.
Any workflow can write those names, and a pull_request run executes the
merge ref's workflow, which a writer controls through the PR's base branch.
A main push could therefore reuse a forged proof.

- The attesting job's runner setup header (`Uses: ...nuxt-cloudflare.yml@<sha>`
  before `Complete job name:`, which no step can write) must name the SHA this
  run itself uses (its own referenced_workflows). It is read from a ranged
  job-log download that needs only actions: read.
- The job must be `<caller job> / Required`.
- The jobs record is re-read after 2 s and 5 s while a green Required job lists
  no proof step yet (fleet#1180: about half of main pushes missed reuse).

taskgraph.yml's byte-equal copy of the store client follows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@narduk-lane-automation narduk-lane-automation Bot added the bug Something isn't working label Oct 30, 2026
…s caller-written

Review of #227: the runner logs every caller-supplied input between its own
Uses: line and Complete job name:, so an input value could carry a forged
Uses: line. Both the store client and taskgraph's engine (shipped in #226)
now take the first Uses: line only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@narduk-lane-automation
narduk-lane-automation Bot merged commit e608a09 into main Oct 30, 2026
4 checks passed
@loganrenz
loganrenz deleted the proof-callable-binding branch October 30, 2026 22:32
narduk-lane-automation Bot added a commit that referenced this pull request Oct 30, 2026
…tHub's record (#229)

* nuxt-cloudflare: Required and E2E proofs reuse with no store, from GitHub's record

36 of the 43 nuxt-cloudflare callers that do not reuse a PR's Required on
main pass no CI_ARTIFACTS_R2_* secrets. Some are in the clients org, which
never receives them. Without a pointer, a main push ran the full gate.

The PR's `Publish ... proof <key>` step already runs, and passes, without
credentials (it just records nothing), and #227 bound it to the callable's
own runner header. So on a push with no pointer, Reuse plan and E2E plan now
find the merged pull request's run on GitHub. The PR comes from the push's
subject (a merge commit names its branch; a squash subject is the PR title,
i.e. the run's display_title), or from a PR head that already held the
pushed tree. The run must pass the same rules as a pointer's run
(attestedRun). It needs only actions: read.

Opt-in is unchanged (required-reuse-pr-results / e2e-reuse-pr-results).
taskgraph.yml's byte-equal store-client copy follows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* store-free reuse: a Dependabot run never vouches; correct the no-store texts

Opus verify (no blockers) on this branch:
- A Dependabot pull request gets no NVAULT_TOKEN or store secrets, so it
  never vouches in findProofOnGitHub (actor or triggering actor).
- Texts and log messages that said a run without the store publishes no proof
  and that the push runs in full now say what happens. A store error still
  throws and runs the full gate; the comment no longer says it falls back.
- e2e-reuse-pr-results (default true) now reuses E2E on a store-less caller's
  push too; its description says so.
- Tests: Dependabot actor and triggering actor, a tree the run did not test,
  and attempt 2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Logan Renz <narduk@mac.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant