apache / apache/maka

Forward-roll baseline is not fenced to an ancestor of the change under test

Open
#4,447 4 comments 1 reaction 0 assignees View on GitHub
bug effort/S
Dominant language
TypeScript
Stars
5.4k
Forks
502
Avg merge
1d 2h
Merged PRs (30d)
716

Description

The released-artifact forward roll qualifies durable state by writing it with a published build and reading it with the build under test. Which published build it picks is resolved at run time, and nothing establishes that build as an ancestor of the change being tested.

`resolveRegistryNightlyPredecessor` (`scripts/release-cli-publication.mjs:221`) reads `dist-tags.nightly` from the registry and validates only that the version metadata's identity matches the tag it came from:

```js
const version = packageMetadata?.['dist-tags']?.nightly;
parseProductNightlyVersion(version);
// ...
if (versionMetadata.name !== PACKAGE_NAME || versionMetadata.version !== version) {
throw new Error('Registry Nightly identity does not match its dist-tag');
}
```

There is no check that the commit this Nightly was published from is reachable from the checkout under test.

## How it goes wrong

A branch based on `main@X` enters CI. While that run is in flight — or simply because the branch has not been rebased — `main` advances to `Y` and publishes `nightly(Y)`. The forward roll then writes state with `nightly(Y)` and reads it with a workspace whose code predates it. The roll runs backwards.

Two outcomes, and the quieter one is worse:

- **False failure.** `nightly(Y)` writes a record the older workspace cannot decode. The job fails for a reason that has nothing to do with the change under review.
- **Silent no-op.** `nightly(Y)` already contains the very migration the change is meant to exercise, so both sides of the roll speak the same vocabulary. The job passes without having crossed a version boundary at all. Nothing distinguishes this from a real pass.

The second is the reason to fence it: a guard that quietly stops guarding reports the same green as one that works.

## Reachability

Ordinary path — no adversary and no unusual timing required. Any pull request whose branch point predates the current Nightly is exposed, which over a day of merges is most of them.

## Material already available for a fence

- npm version metadata carries `gitHead` for packages published from a repository.
- The CI checkout already uses `fetch-depth: 0` (`.github/workflows/ci.yml`), so `git merge-base --is-ancestor HEAD` can be evaluated in the job without an extra fetch.
- `assertRegistryNightlyPredecessor` in the same module already demonstrates the shape of a fence — it refuses a predecessor that is no longer current — but it is used by the release flow, not by the forward roll.

A fence would establish the baseline as an ancestor and fail loudly when it is not, rather than qualifying in whichever direction the registry happens to point.

## Origin

Raised during review of the durable-state forward roll in #4427. That pull request moves the forward roll onto the main CI lane, which widens how often this baseline is resolved; it does not introduce the gap. Fixing it is a separate change with its own verification, so it is tracked here rather than folded in.

Contributor guide

Open the contributing guide

Research direction

Read scripts/release-cli-publication.mjs at resolveRegistryNightlyPredecessor and compare it with assertRegistryNightlyPredecessor; then inspect .github/workflows/ci.yml to confirm the checkout provides the needed history. Verify that the forward roll rejects a registry Nightly whose gitHead is not an ancestor of the tested checkout, and add focused verification for that failure.

Written by the indexing model from the issue text.

Assessment

Tech stack
git, github-actions, javascript
Domain
ci-cd, release
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.