Skip to content

fix: keep the data branch present through promotion - #4032

Merged
marcusrbrown merged 20 commits into
mainfrom
fix/keep-data-branch-present
Oct 21, 2026
Merged

marcusrbrown merged 20 commits into
mainfrom
fix/keep-data-branch-present

Conversation

@marcusrbrown

Copy link
Copy Markdown
Collaborator

Closes #4018

Every data → main promotion squash-merges a PR whose head is data, and delete_branch_on_merge deletes data at the merge. Nothing recreates it until the next writer bootstraps it. The gateway and dashboard read metadata/repos.yaml from data, and during a 54-minute gap on 10-19 the gateway denied every repo for about 40 minutes.

Changes

  • Deletion ruleset. .github/settings.yml adds a second ruleset on refs/heads/data that has only the deletion rule and no bypass actors. The App-only write ruleset is unchanged, so the App's always bypass does not cover deletion. delete_branch_on_merge stays on for every other branch.
  • Merge-back (scripts/merge-back-data.ts, first step of merge-data.yaml). When data does not already contain main, the App writes one commit with parents [main, data]. The commit's tree is main's tree, with every data-owned path taken from data, including paths that data deleted. Data-owned paths are the authority guard's guarded set, shared through isDataOwnedPath.
    • Because each path has one owner, a promoted line edited again on data no longer conflicts the next promotion.
    • The run fails closed and files an alert when an unowned path on data differs from both the merge base and main. Landing the same content on main clears it.
    • It retries only on an updateRef 409/422 (3 attempts, re-checking each time) and never forces.
    • If data is missing, the bootstrap recreates it.
    • A failure stops the run before the privacy gates and the promotion.
  • Promotion (merge-data-pr.ts):
    • The "no PR" decision uses only the changed-file list.
    • The stale-divergence alert dates from the newest one-parent commit, so merge-back commits don't reset it.
    • The existing-PR lookup ignores closed PRs.
    • An updateBranch merge conflict goes to the conflict alert, which no longer asks for a push to data.
  • Wiki writes (@fro.bot/wiki-write-core, patch release):
    • commitWikiChanges takes an optional baseCommitSha, the data commit its files were derived from. On every attempt it refuses to write if any write-set path changed since that base.
    • A ref race (409, or an updateRef 422) is replayed only when the write set is unchanged. The race with the merge-back is therefore retried, while concurrent wiki writes still fail loudly.
    • sync-wiki outputs the data commit it restored, and the ingest steps and wiki-repair pass it as the base.
  • Docs: metadata/README.md describes the persistent lifecycle (deleting data on purpose means removing the deletion ruleset first, and owned-path reverts go through data). The bootstrap solution doc and the autonomous-commit rules are updated to match.

The remaining overwrite cases between wiki writers predate this change. They are tracked in #4031.

Verification

  • A probe showed that an App token with only contents: write can move a branch through Git Data updateRef to a [main, branch] commit that changes 24 workflow files relative to the old tip.
  • check-types, lint, test (5790 passed), check:wiki-write-core-dist, check:mutation-guards (clean; merge-back-data.ts 275/275 killed) and actionlint all pass.

After merge

  1. Read back the rulesets and confirm that data branch: no deletion has an empty bypass list.
  2. On the first promotion, confirm data keeps the same tip SHA across the squash merge. Then dispatch merge-data and confirm it writes one merge-back commit and that the next promotion diff contains only new writes.

@marcusrbrown
marcusrbrown requested a review from fro-bot as a code owner October 21, 2026 04:47
@fro-bot fro-bot added agent: working Agent is currently working on this and removed agent: working Agent is currently working on this labels Oct 21, 2026

@fro-bot fro-bot left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: CONDITIONAL

Blocking issues

  • P1 — Moving retry baseline permits lost writes. packages/wiki-write-core/src/wiki-ingest.ts:368–370 resolves baseCommitSha: null against main on every attempt. Reproduced: a concurrent writer changes a write-set path, that change is squash-promoted, and the retry adopts the newer main tree as its baseline. It succeeds on attempt 2 and overwrites the concurrent content with stale output. Passing the original derivation SHA correctly rejects the same sequence. Require the immutable derivation commit and retain its baseline across retries; regenerate dist after fixing this.

Non-blocking concerns

  • P2 — Legitimate concurrent merge-back produces a false ownership alert. scripts/merge-back-data.ts:239–255 can read main at M0, then read data after another run has merged M1. It mistakes inherited M1 changes for unauthorized writes and blocks before reaching the ref-race retry. Independently reproduced; a fresh invocation returns noop. Before alerting, confirm main still matches the captured SHA; retry with fresh snapshots if it moved.
  • P2 — Alert failure hides the ownership block. scripts/merge-back-data.ts:252–255 awaits alert delivery before returning blocked. If delivery fails, the CLI reports only API_ERROR, losing the ownership classification and path count when no issue was delivered. Preserve a privacy-safe BLOCKED diagnostic alongside the alert-delivery failure and retain the nonzero exit.

Missing tests

  • Add a null-base regression with a concurrent write and promotion between attempts; assert rejection and preservation of the concurrent content.
  • Add the overlapping M0/M1 merge-back scenario and an alert-delivery rejection case.

Risk assessment (HIGH) + rationale

The new public null-base mode can silently overwrite concurrent wiki content. The ownership snapshot race also interrupts valid promotions. 700 targeted tests passed across 9 files, and check:wiki-write-core-dist passed; those checks do not cover the reproduced races. Live ruleset enforcement remains an after-merge verification.


Run Summary
Field Value
Event pull_request
Repository fro-bot/.github
Run ID 39546002242
Cache hit
Session ses_ea335a09bffemFedqSA9NC2tNr
Verification 700 tests passed; committed dist current; two races reproduced in memory
Coverage limitation TypeScript specialist return rejected for invalid schema; reported defects independently validated

@fro-bot fro-bot added the agent: working Agent is currently working on this label Oct 21, 2026

@fro-bot fro-bot left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: PASS

The follow-up commits resolve all three prior findings: the derivation baseline stays immutable, overlapping merge-backs retry against fresh snapshots, and alert-delivery failures preserve the ownership-block diagnostic.

Blocking issues

None

Non-blocking concerns

None

Missing tests

None

Risk assessment (MED) + rationale

This changes control-plane branch writes, but the reviewed fixes preserve fail-closed behavior. 705 targeted tests passed across 9 files, and the committed dist check passed. An independent promotion-between-attempts probe rejected the stale write and preserved concurrent content. Live deletion-ruleset enforcement remains the documented after-merge check.


Run Summary
Field Value
Event pull_request
Repository fro-bot/.github
Run ID 39548328359
Cache hit
Session ses_ea335a09bffemFedqSA9NC2tNr
Scope Follow-up commits efa8d7a and f4160b6; all prior findings resolved
Verification 705 tests passed; dist current; stale-write regression independently verified

@fro-bot fro-bot removed the agent: working Agent is currently working on this label Oct 21, 2026
@marcusrbrown
marcusrbrown merged commit d3d0eb2 into main Oct 21, 2026
21 checks passed
@marcusrbrown
marcusrbrown deleted the fix/keep-data-branch-present branch October 21, 2026 15:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Keep the data branch present through promotion

2 participants