Skip to content

[do-not-mirror][do-not-merge] chore(fork): publish fork-only content as a standing view PR - #5

Closed
jdsika wants to merge 3 commits into
integration/3.0from
chore/fork-only-view
Closed

jdsika wants to merge 3 commits into
integration/3.0from
chore/fork-only-view

Conversation

@jdsika

@jdsika jdsika commented Oct 23, 2026

Copy link
Copy Markdown

What

Publish the fork-only chore(fork): commit as a derived branch fork-only/3.0, so the fork-only
content can be read in isolation through a standing draft PR into 3.0.

Why

"What does this fork add that never goes upstream?" had no cheap answer. The content is a single
chore(fork): commit on integration/3.0, but reading it meant resolving a SHA by hand, and the
standing integration draft PR cannot show it in isolation — its diff necessarily carries the whole
feature stack too.

This is a view, not storage. Fork-only content must stay on the default branch, because:

  • a clone checks out integration/3.0, so that copy of the instructions is the process;
  • GitHub runs scheduled workflows only from the default branch, so sync-mirror.yml parked
    elsewhere would silently stop fast-forwarding the mirror;
  • the pull_request branch filter in ci.yml must be there or feature PRs targeting
    integration/3.0 get no checks at all, making Gate 1 unprovable.

How tested

  • YAML validity -> YAML OK, 4 steps, permissions: {contents: write, pull-requests: read}
  • All 8 workflow guards replayed against the live stack: ancestor OK; 0 merge commits;
    0 fixup!; exactly 1 chore(fork): ; base 5efda076b is oldest, has exactly one parent,
    and base^ == origin/3.0 == 74b44c2f3
  • git diff --check clean; LF-only, single trailing newline on all three files
  • One commit: git rev-list --count origin/integration/3.0..HEAD -> 1
  • Guard-regex coverage verified at all three call sites (4.5 L402, Gate 3 L497, section 6 L590)
  • Blocker evidence captured from the live repo, not assumed:
    actions/permissions/workflow -> can_approve_pull_request_reviews: false;
    PR DO NOT MERGE - integration stack CI #1 labels -> do-not-merge alone

Not run, and why: pre-commit and the make suites assume a POSIX shell and .venv/bin/python
(section 4.3); this change was authored on Windows. The CI pre-commit job covers the former. No
.c/.h, Python, notebook, dynamics, reward, observation or training-loop code is touched, so
make rebuild, test-unit, test-eval, test-c, test-notebooks, test-docker-smoke and the
perf target are not applicable.

New behaviour covered by a test: n/a — fork-only CI/doc infrastructure, no sim behaviour. The
workflow's own guards are its test; they are replayed above and were probed in Gate 2.

Stack hygiene

  • exactly one commit (1)
  • Conventional Commits subject, rebased on current integration/3.0
  • no fork-only file in the diff — exempt: chore(fork) (adding one is the point)
  • no fork-only hunk in a shared file — exempt: chore(fork)

Merge gate

  • Gate 1 — CI green on the final commit
  • Gate 2 — adversarial review completed by a separate agent with no authoring context.
    Verdict FAIL: 2 blockers, 6 should-fix. Both blockers were independently reproduced
    against the live repository and fixed; see the review comment on this PR for the disposition
    of every finding.
  • Gate 3 — exempt: chore(fork)

Gate 3 result: exempt: chore(fork) — fork-only infrastructure can never be upstream-ready.

Upstream disposition

  • Mirror PR requested now
  • Contributable, but held
  • Never upstream — fork-only chore(fork): change, Gate 3 exempt

Risk / notes

  • The workflow does not open the standing PR. GITHUB_TOKEN is denied PR creation in this
    repository (can_approve_pull_request_reviews: false), and section 5 reserves that decision for a
    human anyway. A human opens it once; the workflow thereafter only checks it — warning when it is
    missing and failing loudly if one was ever merged, which would mean the mirror is
    contaminated. This also removed the need for pull-requests: write and label self-creation.
  • The view PR carries do-not-merge alone, never do-not-mirror. Both labels together are the
    in-flight-process-change signal that section 4.1 queries; a permanent PR wearing both would tell
    every agent, forever, that the rules are mid-change. Generalised into a stated rule, and it is
    already how standing PR DO NOT MERGE - integration stack CI #1 is labelled.
  • The workflow has contents: write and force-pushes fork-only/3.0. Its push has a SHA source
    and a literal destination, so it cannot write to 3.0 or integration/3.0.
  • It uses a push: trigger on integration/3.0, which section 3.2 warns against for the test
    suites
    . That warning is about runner cost; this workflow runs a few git commands and must react
    to the branch moving. Section 3.2 records the distinction so it is not "fixed" later.
  • Accepted cost: the view PR targets 3.0, so each refresh re-runs the pull_request
    workflows. Suppressing that would mean adding more fork-only hunks to ci.yml, install.yml and
    pre-commit.yml, and every extra hunk in a shared file is another contamination path for feature
    commits. The fork base changes rarely. Documented rather than hidden.
  • Known limitation, not introduced here and deliberately out of scope: section 4.5 greps changed
    diff lines for fork:begin/fork:end, so an edit in the middle of the marker block in
    AGENTS.md/CLAUDE.md can produce a three-line-context diff containing neither marker and slip
    past the cheap preflight. Gate 3 still catches it. Fixing it means comparing extracted blocks
    rather than diff lines — a separate reviewable idea (section 4.2).
  • Section 5 previously permitted exactly one PR targeting 3.0. There are now two, both
    standing, both never merged. Amended.
  • The three contamination guards now also match fork-only-view; without that, the new workflow
    would have been treated as an ordinary file and could have ridden into a mirror PR.
  • CONTRIBUTING.md still described the superseded single-axis [do-not-mirror] marker scheme
    ("four redundant markers"); corrected to the two independent axes, and its fork-only file list now
    includes the new workflow.
  • Follow-up: this PR is applied by fixup into the base commit and closed unmerged (section 3.3),
    after which the workflow fires for the first time and publishes fork-only/3.0. A human then
    opens the standing view PR once.

ASCS-eV/PufferDrive is consumed as a git submodule internally while feeding
features back to Emerge-Lab/PufferDrive. This commit carries everything that
exists only in the fork, and is the single place fork-only content lives.

Branch model:
  3.0              verbatim, fast-forward-only mirror of upstream/3.0
  integration/3.0  default branch; this commit plus a linear stack of
                   single-commit features; what internal consumers pin
  feature/<slug>   one feature, exactly one commit
  mirror/<slug>    cherry-picked onto pure upstream/3.0 for upstream PRs

integration/3.0 is the default branch because GitHub reads the PR template,
the contributing guidelines, the agent instructions and scheduled workflows
only from the default branch, and none of those files may exist on 3.0
without destroying the mirror. A clone checks out the default branch, so the
copy of the instructions here is the process as far as every agent and every
submodule consumer is concerned; a rule that lives only in an open PR has no
effect.

Nothing reaches integration/3.0 until three gates pass:
  1. CI green on the final commit.
  2. Adversarial review by someone other than the author, with the findings
     written down -- including the probes that found nothing.
  3. Upstream-readiness proven rather than asserted: the commit cherry-picks
     onto a branch cut from pure upstream/3.0 and produces an upstream-clean
     diff. We stack only commits we could contribute today.

chore(fork): commits are exempt from gate 3, since fork-only infrastructure
can never be upstream-ready. They carry two orthogonal markers: do-not-mirror
(must never reach upstream) and do-not-merge (the merge button is never
pressed -- merging would create a second chore(fork): commit and break the
single-commit invariant that every sync asserts). Such a PR is folded into
this commit by fixup and closed unmerged.

Contents:
- .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 gives 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.
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 added do-not-mirror Fork-only change; must never be sent to Emerge-Lab/PufferDrive do-not-merge Never use the merge button; applied by fixup (3.3) or never merged at all labels Oct 23, 2026
@jdsika

jdsika commented Oct 23, 2026

Copy link
Copy Markdown
Author

Gate 2 — adversarial review

Run as a separate task with no authoring context (section 5: an agent may not review a change it
authored). Verdict: FAIL — 2 blockers, 6 should-fix. Both blockers were independently reproduced
against the live repository before any fix was written.

Probes that found nothing (recorded so the next reader knows the coverage)

Probe Outcome
Can the force-push reach 3.0 or integration/3.0? No — SHA source, literal destination
Empty stack / 0 or >1 fork commits / wrong oldest commit / root commit All abort before pushing
Shell guard semantics under GitHub's bash -e -o pipefail grep ... && { exit 1; } is the left side of an && list, so a healthy no-match does not abort
grep -c ... || true zero-match bug Correctly avoided
Documented guards replayed (4.5 / Gate 3 / section 6) ABORT on this contaminated diff, pre-flight OK on a clean one
Fork-only file coverage vs section 3.1 Complete; fork-only-view catches the new workflow
Divergence report / mirror/* inventories polluted by fork-only/3.0? No — explicit origin/3.0..origin/integration/3.0 and mirror/* globs
git diff --check, PyYAML parse Pass
Concurrency group + cancel-in-progress: false Appropriate; avoids cancelling between branch update and PR maintenance
Unrelated source changes, formatting churn, debug content None

(actionlint was unavailable, so no semantic action linting was performed.)

Findings and disposition

# Finding Disposition
B1 can_approve_pull_request_reviews: false — GITHUB_TOKEN cannot create PRs, so the final step always fails Fixed. Workflow no longer creates the PR; a human opens it once (section 5 required this anyway) and the workflow only checks it. pull-requests: write -> read.
B2 The permanent view PR carrying both labels permanently trips section 4.1's in-flight-process detector, contradicting the doc's own claim Fixed, generally. Standing PR #1 already carries do-not-merge alone; that is now a stated rule — standing artifact = one label; both labels = actively rewriting the process. Preferred over a headRefName exclusion because it removes the class of bug, not the instance.
S1 PR state not repaired / not idempotent Mostly dissolved by B1's fix. Retained the valuable parts: fail on >1 open PR, and fail loudly if a view PR was ever merged (that would mean the mirror is contaminated).
S2 "$base^" checks only the first parent; no merge-commit, fixup!, or exact-prefix checks Fixed. Added mirror-ancestry assertion, --min-parents=2 rejection across the stack, exactly-one-parent on the base, ^fixup! check, and exact ^chore(fork): prefix. All 8 guards replayed against the live stack: base 5efda076b, one parent, base^ == 74b44c2f3.
S3 The view PR generates extra pull_request runs Accepted, documented. Suppressing it means adding fork-only hunks to ci.yml, install.yml and pre-commit.yml — every extra hunk in a shared file is another contamination path for feature commits. The fork base changes rarely. Reasoning written into section 2 so it is not silently "fixed" later.
S4 CONTRIBUTING.md fork-only file list stale Fixed.
S5 Label self-creation lacked issues: write Dissolved — label self-creation removed entirely with B1's fix.
S6 Section 4.5 can miss edits in the middle of a fork:begin/end block Accepted, not fixed here. Pre-existing, not introduced by this change, and Gate 3 still catches it. The fix (compare extracted blocks, not diff lines) is a separate reviewable idea per section 4.2. Recorded in the PR body as a follow-up.

Also corrected while in here: section 5 still said the two standing PRs "already exist, are
maintained automatically" — the view PR does not exist yet and is opened by a human.

@jdsika
jdsika force-pushed the chore/fork-only-view branch from a140448 to b67c40f Compare October 23, 2026 16:05
@jdsika

jdsika commented Oct 23, 2026

Copy link
Copy Markdown
Author

Scope correction (maintainer direction): the human-approval gate is for upstream PRs only. Section 5 had over-reached to any PR — fork or upstream; narrowed so agents open fork-internal PRs themselves and report gate status, with the human gate on fork PRs being the merge button, not the open button. CONTRIBUTING.md and the PR template were already correctly upstream-scoped.

Amended: a1404484a -> b67c40f5a (still exactly 1 commit). Gate 1 re-running on the new tip.

"What does this fork add that never goes upstream?" had no cheap answer.
The content is a single `chore(fork):` commit on `integration/3.0`, but
reading it meant resolving a SHA by hand, and the standing integration
draft PR cannot show it in isolation because its diff necessarily carries
the whole feature stack as well.

Add `fork-only/3.0`: a derived branch holding exactly that one commit,
republished on top of the `3.0` mirror, plus a standing draft PR into `3.0`
whose "Files changed" tab is therefore precisely the fork-only content.

It is a view, not storage. Fork-only content has to stay on the default
branch: a clone checks out `integration/3.0`, GitHub runs scheduled
workflows only from the default branch (so `sync-mirror.yml` parked
elsewhere would stop firing), and the `pull_request` branch filter in
`ci.yml` has to be there or feature PRs targeting `integration/3.0` get no
checks at all -- which would make gate 1 unprovable. The view exists to be
read, never to be the source of truth.

`.github/workflows/fork-only-view.yml` rebuilds the branch on every push to
`integration/3.0`, so it cannot drift. It refuses to publish a stack that
violates the section 7 invariants -- not exactly one `chore(fork):` commit,
or a base that is not parented on the mirror tip -- so a malformed stack
surfaces as a failed run rather than as a misleading diff.

Supporting changes:
- the three contamination guards (4.5, gate 3, and section 6) now also
  match `fork-only-view`, which they would otherwise have let through into
  a mirror PR as an ordinary workflow file
- section 5 allowed exactly one PR to target `3.0`; there are now two, both
  standing, both auto-maintained, neither ever merged
- section 3.2 warns against `push:` triggers on `integration/3.0` for the
  test suites; note that this workflow uses one deliberately, so it is not
  "fixed" to match later
- CONTRIBUTING.md still described the single-axis `[do-not-mirror]` marker
  scheme that was replaced by the two independent axes
@jdsika

jdsika commented Oct 23, 2026

Copy link
Copy Markdown
Author

Landed — closing unmerged (section 3.3)

Gate 1: 12/12 green on 21ecfe5.
Gate 2: completed, FAIL -> all findings fixed or explicitly accepted (above).
Gate 3: exempt (chore(fork)).

Folded into the fork base commit by fixup, not merged:

before after
fork base 5efda076b b9b276a3d
integration/3.0 d692385b2 fad5d9586

Invariants verified before the force-push — all PASS:

  1. chore(fork): commits on the stack = 1
  2. fixup! commits = 0
  3. oldest stack commit = chore(fork): define the ASCS-eV fork process
  4. fork base parent == origin/3.0 (74b44c2f3) = true
  5. merge commits on the stack = 0
  6. tip tree == the tree CI proved green (8ea57c45a) = true — the 12/12 verdict transfers exactly
  7. ci-fix patch-id preserved (1315dfd78...) — so mirror PR Install CPU-only torch wheels in CI to fit ubuntu-26.04 disk limits Emerge-Lab/PufferDrive#602 keeps its section 6.2 linkage despite the SHA change

GitHub logged Bypassed rule violations ... 12 of 12 required status checks are expected — the deliberate hole documented in section 2.

Follow-up: this design turned out to be wrong

The first real run of the workflow it added, 39986186116, failed on the push:

\
! [remote rejected] b9b276a -> fork-only/3.0 (refusing to allow a GitHub App to create or
update workflow .github/workflows/ci.yml\ without \workflows\ permission)
\\

GitHub blocks GITHUB_TOKEN from pushing any commit that touches .github/workflows/, and no permissions: block lifts it. The fork base is mostly workflow files, so publish-by-workflow can never work. Remediated in #6, which makes the workflow a read-only drift detector and moves publishing into the two procedures that already force-push by hand.

Worth recording: this design cleared Gate 1 and Gate 2 and was still unworkable, because neither gate executes the workflow.

@jdsika jdsika closed this Oct 23, 2026
@jdsika
jdsika deleted the chore/fork-only-view 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