Skip to content

[do-not-mirror][do-not-merge] chore(fork): add merge gate before stacking on integration/3.0 - #4

Closed
jdsika wants to merge 3 commits into
integration/3.0from
chore/merge-gate-and-review
Closed

jdsika wants to merge 3 commits into
integration/3.0from
chore/merge-gate-and-review

Conversation

@jdsika

@jdsika jdsika commented Oct 23, 2026 •

Copy link
Copy Markdown

What

Hardens the fork process docs so that another agent can execute them unattended, and encodes the
merge gate: nothing is stacked on integration/3.0 without green CI, an adversarial review, and
proven upstream-readiness.

Scope grew after two exercises were run against the docs (a reproducibility audit and an
adversarial review of #3). Both are folded in below.

Why

The process was readable but not executable. The audit found five blockers that would have
stopped or silently corrupted an agent run, two of which were repo-settings bugs rather than doc
bugs.

Blockers fixed

# Problem Fix
1 Rebase-and-merge was disabled on the repo, so the documented merge method was impossible repo settings: rebase on, squash + merge-commit off
2 §3.3 used git rebase -i, which is a silent no-op headless — and the next command force-pushes the default branch replaced with git commit --fixup + GIT_SEQUENCE_EDITOR=: git rebase -i --autosquash, plus a hard invariant check before the push
3 §5 said to apply upstream AGENTS.md "verbatim", which mandates adding an AGENTIC_PR French-poem file to every mirror PR new §6.1: we disclose AI assistance and say we chose disclosure over the poem file
4 Gate 1 ("CI green") had no referent — nothing defined which checks branch protection now requires all 12; §4.8 points at gh pr checks
5 Smoke-golden regeneration was undocumented, making any observation/dynamics change undoable §4.3 documents update_goldens.sh, the git add -f for the gitignored rollout golden, and base-dependence across Gate 3

Correctness fixes

  • git pull --rebase in §2 silently resurrects commits dropped during a rebase → replaced with
    fetch + reset --hard.
  • §7's rebase recipe had no reset --hard first, risking rewriting history on top of local drift.
  • §6's git branch -D mirror/<slug> fails when that branch is checked out → detach first.
  • git diff upstream/3.0..HEAD → ...HEAD in Gate 3 and the template.
  • grep -c exits non-zero on zero matches, aborting a set -e script → || true.
  • Gate 3 left the agent stranded on mirror/<slug> → added git switch -.
  • §4.5 and the template were unsatisfiable for chore/ branches (their whole job is to touch
    fork-only files) → explicit exemption.
  • make test was described as "everything"; it is test-unit test-eval test-c test-notebooks and
    excludes docker-smoke and perf → corrected in all three files.
  • upstream remote and gh repo set-default were documented nowhere, yet Gate 3 fails without the
    first and bare gh pr can resolve to upstream without the second → §4.1 setup block.
  • Documented the update-smoke-golden.yml hazard: it ends in git push origin HEAD:<ref> and
    triggers on 3.0, so it can commit to the mirror. Added the detector and recovery.
  • "Never open a PR targeting 3.0" made explicit.
  • Normative example no longer hardcodes a PR number.

How tested

  • §3.3's recipe rehearsed end-to-end on throwaway branches with GIT_EDITOR=false, so any
    attempt to open an editor would hard-fail. Result: cherry-pick → fixup → autosquash rebase
    completed, stack collapsed to exactly 1 chore(fork) commit, no stray fixup!, and the
    change was confirmed present inside the base commit.
  • All 21 section N / §N cross-references resolve to a heading that exists.
  • LF endings, single trailing newline, git diff --check clean.
  • make test target read from the Makefile to confirm what it actually runs.
  • update_goldens.sh read to confirm the documented git add -f path.

Docs-only change; no code touched, so the suites are unaffected.

Stack hygiene

exempt: chore(fork) — this PR deliberately changes the files in §3.1.

  • exactly one commit

Merge gate

  • Gate 1 — CI green on the final commit
  • Gate 2 — adversarial review
  • Gate 3 — exempt (chore(fork), can never be upstream-ready)

Upstream disposition

  • Never upstream — fork-only chore(fork): change, Gate 3 exempt

Applied by fixup into the base commit per §3.3, not with the merge button. This PR ends up
closed, not merged; the resulting integration/3.0 SHA gets posted here.

Risk / notes

The one behavioural change outside the docs is the repo settings (rebase-merge enabled, squash and
merge-commit disabled, 12 required checks on integration/3.0, do-not-mirror label). Those are
already live.

Open follow-up: #3 is green and Gate 3 clean, but its body was rewritten after review found the
stated root cause was wrong. It needs a human read before it goes upstream.

ASCS-eV/PufferDrive is consumed as a submodule internally while feeding
features back to Emerge-Lab/PufferDrive. Document the model that keeps
both possible: 3.0 as a verbatim upstream mirror and default branch,
integration/3.0 as a linear stack of single-commit features, and
mirror/<slug> branches cherry-picked onto pure 3.0 for upstream PRs.

- .github/copilot-instructions.md: normative process for coding agents
- CONTRIBUTING.md: human orientation, same rules
- .github/pull_request_template.md: testing and stack-hygiene gates
- .github/workflows/sync-mirror.yml: fast-forward-only mirror sync that
  refuses any non-fast-forward instead of rewriting the upstream base
- AGENTS.md / CLAUDE.md: fenced pointer so agents find the fork process
  without colliding with upstream-owned content
- ci.yml / install.yml: run CI for PRs targeting integration/3.0, which
  upstream's branch filters would otherwise skip

A standing draft PR from integration/3.0 into 3.0 provides a continuous
CI verdict on the combined stack; it must never be merged.

Everything here is fork-only and must never be cherry-picked upstream.
@jdsika jdsika added the do-not-mirror Fork-only change; must never be sent to Emerge-Lab/PufferDrive label Oct 23, 2026
@jdsika
jdsika force-pushed the chore/merge-gate-and-review branch 3 times, most recently from 35c9ef5 to 7661a01 Compare October 23, 2026 11:11
@jdsika

jdsika commented Oct 23, 2026

Copy link
Copy Markdown
Author

Gate 2 — adversarial review record

Reviewed in a fresh context by an agent that did not author the change (required by §5, since I wrote it).

Verdict returned: do not stack it — 9 blockers + 6 should-fix, each with a demonstrated failure. All have now been resolved or explicitly deferred with a reason.

Blockers — resolved

# Finding Resolution
B1 Gate 1 red on this PR Expected. This PR touches only 3 .md files; the red is the pre-existing [Errno 122] breakage fixed by #3. Ordering dependency is real and is exactly what Gate 1 prescribes: #3 must land first.
B2 integration/3.0 could be force-pushed behind a stale check strict=true (require branches up to date) enabled on integration/3.0.
B3 §3.3/§7 publish the default branch after only make test Documented as a known limitation; make test corrected to list what it actually runs (test-unit test-eval test-c test-notebooks — not docker-smoke or perf). Candidate-branch CI before force-push remains an open question.
B4 §4.5 / Gate 3 guards printed a warning and exited 0 Rewritten as fail-fast guards that exit 1. Proven by execution — see the evidence block below.
B5 git rebase -i in executable blocks hangs headless All executable -i removed. §3.3 uses cherry-pick --no-commit + commit --fixup + GIT_SEQUENCE_EDITOR=: rebase -i --autosquash; §4.4 uses rebase + reset --soft + commit -F. Recipe verified headless under GIT_EDITOR=false.
B6 git switch - fails after git switch --detach Real defect, found by dogfooding Gate 3 (fatal: a branch is expected, got commit). Branches now named explicitly in §4.8 and §6.
B7 Gate 3 not retry-safe; second run hit an existing branch Always detach, delete, recreate the mirror branch.
B8 Gate 3 vs goldens for a middle stacked commit Documented as a known dead end; no mechanical procedure yet.
B9 §6 could mirror a stale integration state §6 rewritten to unconditionally rebuild the mirror from a recorded integration/3.0 SHA.

Should-fix — applied

upstream remote + gh repo set-default + venv bootstrap added to §4.1 (bare gh pr was silently resolving to upstream); both smoke goldens are gitignored and need git add -f; update-smoke-golden.yml hazard; "never open a PR targeting 3.0"; chore-exemption scoping in §4.8/§8/template; git pull --rebase replaced; clean-tree assertions + captured SHAs + explicit --force-with-lease=<ref>:<sha> in §3.3 and §7.

B4 evidence — guards actually abort

Run in a throwaway git worktree so the main checkout could not be touched:

=== CASE 1: clean feature (real CI fix 652e0806) — expect PASS, exit 0 ===
   -> PASS: upstream-clean                              exit=0
=== CASE 2: contaminated with fork-only FILE — expect ABORT, exit 1 ===
   -> ABORT: fork-only FILE in mirror diff              exit=1
=== CASE 3: contaminated with fork-only HUNK — expect ABORT, exit 1 ===
   hunk added: 2 line(s)
   -> ABORT: fork-only HUNK in mirror diff              exit=1
=== CASE 4: fork:begin block leak — expect ABORT, exit 1 ===
   -> ABORT: fork-only HUNK in mirror diff              exit=1

The first attempt at this test was itself invalid — the script omitted a clean-tree assertion, so with uncommitted edits present every git switch failed and the test committed onto the working branch. That is the precise failure mode §3.3/§7 now assert against, and it is why this re-run is isolated in a worktree.

Also added this round

  • §6.1 — disclose, don't comply for the upstream AGENTS.md:41 AGENTIC_PR canary. The "no AI attribution" rule is scoped so it cannot be read as licence to omit that disclosure.
  • §2 — documents the branch-protection model and why the admin bypass is deliberate (enforce_admins=true makes §3.3/§7 impossible — verified: HTTP 422 on direct ref update). The in-recipe invariant checks are the compensating control.

Remaining blocker

B1 only. This PR cannot go green until #3 lands. No further work here until then.

@jdsika
jdsika force-pushed the chore/merge-gate-and-review branch from 7661a01 to 157adbe Compare October 23, 2026 11:20
@jdsika

jdsika commented Oct 23, 2026

Copy link
Copy Markdown
Author

Added §6.2 — After upstream responds

Closes a gap found while answering "does #3 need to be stacked and upstreamed?" (both: yes, independently).

The post-acceptance lifecycle was previously one sentence in §7, plus a stray duplicate paragraph buried at the end of §6.1 — a section about the AGENTS.md canary, which is an unrelated topic. Neither gave a procedure. §6.2 now covers all three endings:

Ending Mechanic
A — merged as written Next sync drops our copy as empty. Verify with rev-list --count, then delete mirror/<slug> locally and on origin.
B — merged with modifications Rebase stops on a conflict instead of dropping. git rebase --skip — upstream owns the change now. Any behaviour we still need is a new feature branch, never a resurrected commit.
C — rejected or stalled Needs a decision, not a command: either reclassify as fork-only content (§3.2, rename to chore(fork): so the §4.5 and Gate 3 guards start treating it as never-leaves), or drop it from the stack. Leaving it looking like a pending contribution means a future agent re-offers it to upstream forever.

Detection is by patch-identity, not the PR's merge badge — upstream may squash, rename or amend.

Verified by execution, not assertion

Claim Result
Mirror not yet upstream → + ✅ + 8458cb430
Whole-stack audit git cherry upstream/3.0 origin/integration/3.0 ✅ lists stack
Distinct commit, equivalent patch → - ✅ - 8458cb430; patch-ids identical (1315dfd7…) under different SHAs
Upstream amended the diff → + ✅ + 8458cb430 — correctly reports case B

The test found a defect in the section I had just written. My first draft documented only - and +. There is a third outcome: if upstream merges in a way that preserves our commit object, it becomes an ancestor of upstream/3.0, falls outside the range git cherry examines, and the output is empty — which an agent would read as "no result" rather than "landed". §6.2 now documents silence as a result and gives git merge-base --is-ancestor to disambiguate.

(My first attempt at the verification script was also wrong — it used the mirror commit as the fake upstream, making the range empty, so the - case silently proved nothing. Rewritten and re-run.)

Process note

Both scripts ran in a throwaway git worktree. The first draft of the second script called git checkout directly on a dirty tree — the precise mistake that produced three junk commits earlier in this branch's history. Caught before running it. This is why §3.3/§7 open with clean-tree assertions.

Status

Unchanged: 1 commit, 3 markdown files, no code touched. Still red on Linux for the known [Errno 122] reason, still blocked on #3 landing first per Gate 1.

@jdsika
jdsika force-pushed the chore/merge-gate-and-review branch from 157adbe to c1c1a8f Compare October 23, 2026 11:36
@jdsika

jdsika commented Oct 23, 2026

Copy link
Copy Markdown
Author

Added §6.2 — While the mirror PR is open (former §6.2 → §6.3)

Answers: how do feature PRs stack on integration/3.0 while staying open upstream, until upstream merges and we rebase?

The overlap needs no special handling — it is structural. mirror/<slug> is parented on upstream/3.0, never on integration/3.0, so rebasing the stack cannot touch it; the upstream PR (ASCS-eV:mirror/<slug> → Emerge-Lab:3.0) is unaffected by the fork PR merging. Verified: our fork has delete_branch_on_merge=true (removes feature/<slug> only), upstream has it unset, and GitHub cannot delete branches in our fork. Default action during the open period is nothing.

But four real gaps existed around that period:

1. Deleting the mirror branch closes the upstream PR 🔴

§6.2 case A (now §6.3) ended in a bare git push origin --delete mirror/<slug>. Run while the PR is open — e.g. after misreading git cherry's empty output — GitHub closes the PR, discarding the review and reading to upstream as a silent withdrawal. Now routed through a terminal-state guard.

2. The recorded SHA rots

§6 rebuilt the mirror from "the integration/3.0 SHA recorded on the feature PR". Every sync rebase rewrites the stack, so that SHA stops resolving once 3.0 advances — and the doc simultaneously warned that the SHA disappears, while telling you to use it. Replaced with subject-based lookup plus an ambiguity guard.

3. No refresh procedure while open

Two triggers, now both documented: upstream/3.0 advances (PR goes conflicted in their UI), or a sync rebase alters our commit via conflict resolution. The second means the open PR offers code we no longer run — a correctness and honesty problem, not untidiness. Rebuild + --force-with-lease updates the PR in place; never close and reopen, which discards the review thread. Rebuild must re-run both Gate 3 guards, since conflict resolution is exactly where fork-only content gets dragged in.

4. Nothing enumerated what is in flight

Once the fork PR closes, no link remains from a stacked commit to its open upstream PR. The slug is the only key; inventory commands added for both sides.

Verified by execution

Check Result
Subject lookup finds the stacked commit ✅ 19346687d, n=1
Ambiguity guard fires on a bad subject ✅ aborts, n=0
pid() staleness — in sync must match ✅ both 1315dfd7…
pid() staleness — drifted must not match ✅ 83a6c262… vs 1315dfd7…
merge-base --is-ancestor base check ✅ reports current
Delete guard across MERGED/CLOSED/NONE/OPEN ✅ aborts only on OPEN

The staleness detector is patch-id based, so it needs no checkout and correctly ignores that the two commits have different parents.

Status

Unchanged: 1 commit, 3 markdown files, no code. Still blocked on #3 per Gate 1.

@jdsika jdsika added the do-not-merge Never use the merge button; applied by fixup (3.3) or never merged at all label Oct 23, 2026
@jdsika jdsika changed the title [do-not-mirror] chore(fork): add merge gate before stacking on integration/3.0 [do-not-mirror][do-not-merge] chore(fork): add merge gate before stacking on integration/3.0 Oct 23, 2026
@jdsika
jdsika force-pushed the chore/merge-gate-and-review branch 2 times, most recently from 45354f0 to c74ed71 Compare October 23, 2026 12:01
The Linux CI jobs fail while installing the package:

    [Errno 122] Disk quota exceeded

Errno 122 is EDQUOT, hit while unpacking the CUDA torch stack that
`pip install -e .` resolves by default. torch alone is 554.6 MB and the
accompanying nvidia-* wheels add another ~2.4 GB, which no longer fits
the runner's quota. The failure occurs during "Installing build
dependencies", i.e. the isolated PEP 517 build environment is already
enough to exceed it on its own.

The trigger is a runner image change, not a code change:

  * upstream 11_bernhard_dev, 2026-10-20, Image: ubuntu-24.04 -> success
  * same workflows, 2026-10-23, Image: ubuntu-26.04 -> failure

Both resolve an identical wheel set (torch 2.14.1, 554.6 MB). The
`ubuntu-latest` label began migrating to Ubuntu 26 on 2026-10-19
(actions/runner-images#14748), between those two runs. Both workflows
already carry "Free up disk space" steps, so these jobs were running
close to the limit before the migration.

Fix: install CPU-only torch from download.pytorch.org, then build with
--no-build-isolation so the build reuses it instead of resolving torch
a second time. --no-build-isolation needs the build dependencies present
up front, hence the explicit setuptools/wheel/numpy install; the
`setuptools<81` bound matches the pin already in install_requires.

This loses no coverage: BUILD_CUDA_EXT keys off CUDA_HOME/ROCM_HOME,
which is unset on these runners, so the CUDA sources were never compiled
here. tests/smoke_tests/Dockerfile already installs torch this way.

macOS keeps the plain `pip install -e .` unchanged: its PyPI wheels are
already CPU-only, and leaving it alone keeps the default PEP 517
build-isolation path under test somewhere in the matrix.
@jdsika
jdsika force-pushed the chore/merge-gate-and-review branch from c74ed71 to 2881c22 Compare October 23, 2026 12:07
`integration/3.0` is both what BMW pins and the base every later feature
rebases onto, so a defect merged there is multiplied across every consumer
and every subsequent PR. Opening a PR was previously enough to get a change
onto the stack; now it is not.

A feature may be stacked only after three gates:

  1. CI green on the final commit.
  2. An adversarial review by someone other than the author. Section 4.7
     defines what that means concretely -- revert the fix and prove the new
     test fails, hunt edge cases, determinism, trust boundaries, observation
     and config layout, hot-path cost, scope creep, and likely upstream
     objections -- and requires the findings to be written down, including
     the probes that found nothing.
  3. Upstream-readiness proven rather than asserted: the commit is
     cherry-picked onto a branch cut from pure `upstream/3.0` *before*
     merging, and must apply without conflict and produce an upstream-clean
     diff. We stack only commits we could contribute today.

`chore(fork):` commits are exempt from gate 3 -- fork-only infrastructure
can never be upstream-ready -- and gain their own flow (section 3.3). Two
orthogonal axes must both be marked, because they fail in different
directions: `do-not-mirror` (must never reach upstream; marked four times
over -- title prefix, label, `chore(fork):` commit type, and the template's
"Never upstream" box) and `do-not-merge` (the merge button must never be
pressed on it; marked by title prefix and label). Merging such a PR would
put a *second* `chore(fork):` commit on the stack, and section 7 asserts
there is exactly one, so every future sync would abort until someone
unpicked it by hand. It is folded into the base commit by fixup instead, so
the stack keeps exactly one fork commit and the PR ends closed, not merged.

Also fixes a real ambiguity in the old text. Section 3 listed fork-only
*files* and fork-only *hunks inside shared files* as one flat list, and
section 4.5 then checked them with a file-name diff. That check cannot see
hunks, and it falsely flags any PR that legitimately edits a shared file --
which is exactly what the CI fix in #3 does to `ci.yml` and `install.yml`.
Split into 3.1 (whole files) and 3.2 (hunks in shared files), each with a
check that actually detects it.

Smaller changes:
- the default branch *is* the process. A clone checks out `integration/3.0`,
  so a rule that lives only in an open PR has no effect on any agent or on
  the BMW submodule consumers. Section 2 states this, 4.1 now opens by
  listing in-flight process PRs (the two labels AND-ed, which excludes the
  standing draft PR), and 3.3 requires the fold-in window to be kept short
  and the output of agents that ran during it to be re-checked.
- document `chore/<slug>` branch naming, previously undefined
- agents may not review their own work, nor merge to `integration/3.0` on
  their own initiative
- section 6 no longer rebuilds the mirror branch from scratch, since gate 3
  already built and verified it, and now requires re-running the suites on
  the mirror branch because it has a different base than CI tested
@jdsika
jdsika force-pushed the chore/merge-gate-and-review branch from 2881c22 to b510f1e Compare October 23, 2026 12:12
@jdsika

jdsika commented Oct 23, 2026

Copy link
Copy Markdown
Author

Applied by fixup — closed, not merged (§3.3)

Folded into the base chore(fork): commit and force-pushed to integration/3.0.

Merging would have created a second chore(fork): commit on the stack. §7's invariant
asserts there is exactly one, so every future sync would have aborted until someone unpicked
it by hand. That is what the do-not-merge marker exists to prevent — it is about the button,
not the content. The content landed.

Resulting integration/3.0

fork base 5efda076b — chore(fork): define the ASCS-eV fork process
tip d692385b2 — fix(ci): install CPU-only torch wheels in GitHub Actions
previous tip (lease target) 7ae6c839f

Gate 1 — CI green

12/12 on b510f1e7f, the exact SHA that was folded in — including the full install matrix
(3.11–3.14 on ubuntu-latest, 3.11/3.12 on macos-latest), which was red before #3 merged.

Gate 2 — adversarial review

Posted earlier in this thread: 9 blockers + 6 should-fix findings, each fixed or explicitly
accepted, including the probes that found nothing.

Gate 3 — exempt

Fork-only infrastructure can never be upstream-ready.

Post-fold verification

PASS: exactly 1 chore(fork) commit
PASS: no stray fixup!
PASS: oldest stack commit is the fork base
PASS: tip tree 62ceccd3… identical to the tree CI proved 12/12 green
PASS: ci-fix patch-id 1315dfd7… unchanged → upstream PR #602 linkage intact

The tree-equality check is deliberately stronger than re-running make test: the stacked
result is byte-for-byte the tree CI validated, so the verdict transfers exactly rather than
by inference.

Added in the final revision

The default branch is the process. A clone checks out integration/3.0, so a rule that
lives only in an open PR has no effect on any agent or on submodule consumers — this PR sat
open while the default branch served a 262-line version of a process that is now 930 lines.

  • §2 states that the agent instructions, not just the PR template and scheduled workflows,
    are read from the default branch.
  • §4.1 now opens by listing in-flight process PRs. The two labels AND-ed
    (--label do-not-mirror --label do-not-merge) match fork-only process PRs and nothing else;
    the standing draft PR DO NOT MERGE - integration stack CI #1 carries do-not-merge alone and is correctly excluded. Verified.
  • §3.3 requires the fold-in window to be kept short, and the output of agents that ran
    during it to be re-checked rather than assumed compliant.

@jdsika jdsika closed this Oct 23, 2026
@jdsika
jdsika deleted the chore/merge-gate-and-review branch October 23, 2026 17:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

do-not-merge Never use the merge button; applied by fixup (3.3) or never merged at all do-not-mirror Fork-only change; must never be sent to Emerge-Lab/PufferDrive

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant