docs/UPSTREAM-SYNC.md — the recurring upstream sync, its standing resolutions, and a guard on where the forge delta lives #200

Closed
opened 2026-08-05 10:21:16 +00:00 by claude-bot-andresmgsl · 2 comments

Context

Part of #197. Blocked by #198, which performs the first sync and is the
worked example this document describes.

#197 decision 3 makes the upstream sync recurring: GitHub stays the source
of new ceremony features and is read-only; this forge carries the
forge-compatibility delta on top and never writes back. That arrangement has a
cost that grows silently — every upstream release lands on files this tree has
already modified — and right now the whole procedure exists only as the
reasoning in #197 and #198, which is to say only in two issue bodies.

The first sync measured the cost precisely: 157 upstream commits produced
18 conflict hunks, of which 13 were mechanical and 5 needed a decision.
lib/forge*.sh produced none. That ratio is the thing worth preserving, and
it is preserved by where the delta lives, not by luck.

This repo's own convention is that a decision living only in prose is a
decision waiting to rot (CONTRIBUTING, "Every piece of logic is a file of its
own so a test can drive it"). The sync is not logic and gets no script here —
it gets a runbook and one guard.

Spec

1. docs/UPSTREAM-SYNC.md — the procedure. Written as a runbook an agent
or operator follows without prior context:

  • the two trees, their roles, and that upstream is read-only — no issue,
    PR, comment or release is ever created there;
  • adding the upstream remote and fetching read-only;
  • confirming the merge-base before merging anything, and what it means if the
    merge-base has moved;
  • git merge, not rebase, and why (#197 decision 1);
  • the standing resolutions that recur every sync, so they are not re-decided
    each time: .github/labels.conf keeps this forge's roster (#195);
    drills/*.md keep this forge's records; VERSION and CEREMONY_SELF_REF
    take upstream's numbers (#197 decision 2);
  • the post-merge audit: enumerate every gh call site the merge introduced
    and either port it or declare CEREMONY_FORGE_CLIENT=gh so it refuses
    loudly (#197 decision 4);
  • the CHANGELOG provenance line naming the upstream commit carried.

2. docs/CONSUMERS.md gains a short section stating that this tree's
version numbers track upstream's, that two trees therefore answer to the same
number, and how a consumer names which one it pinned. Consumers read that file
already; the fact belongs where they look.

3. One guard, not a suite: the delta inventory. docs/UPSTREAM-SYNC.md
carries the list of files that hold forge-specific behaviour — today
lib/forge.sh, lib/forge-github.sh, lib/forge-forgejo.sh,
lib/closes_references.sh, .github/labels.conf, drills/. A test asserts
the inventory names every file that differs from upstream's tree in a
forge-specific way, so a future sync that scatters forge branching into a
seventh file fails on the PR that does it rather than being discovered by the
sync after next.

The guard compares against a recorded upstream ref, not a live fetch:
tests must not require the network, and a guard that silently passes when
offline is the blind-sweep shape this repo keeps writing issues about. The ref
is a file in the repo, updated by each sync.

4. What this issue does not do. No automation, no scheduled job, no
stoke-side tooling. The sync is infrequent, needs judgement at 5 of 18
hunks, and an automated merge that resolves those by rule would produce
exactly the confidently-wrong result the runbook exists to prevent.

Tasks

  • Write docs/UPSTREAM-SYNC.md covering spec 1, using #198 as the worked
    example with its real numbers.
  • Add the version-tracking section to docs/CONSUMERS.md (spec 2).
  • Record the upstream ref this tree last merged, in a file.
  • Add the delta-inventory guard and its test (spec 3).
  • Add a changelog.d/ fragment.

Acceptance criteria

  • docs/UPSTREAM-SYNC.md exists and names, for each of the six standing
    resolutions, which side wins and the issue that decided it.
  • A reader following only that document can reproduce #198's merge —
    no step depends on knowing #197 or #198's discussion.
  • docs/CONSUMERS.md states that this tree tracks upstream version
    numbers and that two trees answer to the same number.
  • The recorded upstream ref matches what main actually carries.
  • The delta-inventory guard fails when a forge-specific change is made to
    a file the inventory does not name.
  • The guard runs offline — unplugging the network does not turn it green.
  • test/run.sh ends failed 0.

Test plan

  • Must fail: add a forge_detect branch to a file outside the inventory;
    the guard goes red naming the file.
  • Must fail: the recorded upstream ref disagrees with what main
    contains.
  • Must not pass by absence: run the guard with no network and with the
    recorded ref removed — it must refuse, not report success.
  • Documentation criteria are checked by reading, and by the reviewer
    confirming each of the six standing resolutions traces to an issue number.

Dependencies

Part of #197. Blocked by #198.

Dependency discharged 2026-08-05 (triage). Blocked by #198 was about the MERGE, and it landed: !204 merged as 790c4d2, and upstream 0.6.0 is now an ancestor of main. #198 itself stays open only for its post-merge evidence (#5667), which this issue does not wait on. The blocked label may lag: on this forge the sweep cannot remove labels at all until #192 lands, which is the whole of that issue.

## Context Part of #197. `Blocked by #198`, which performs the first sync and is the worked example this document describes. #197 decision 3 makes the upstream sync **recurring**: GitHub stays the source of new ceremony features and is read-only; this forge carries the forge-compatibility delta on top and never writes back. That arrangement has a cost that grows silently — every upstream release lands on files this tree has already modified — and right now the whole procedure exists only as the reasoning in #197 and #198, which is to say only in two issue bodies. The first sync measured the cost precisely: 157 upstream commits produced **18 conflict hunks**, of which 13 were mechanical and 5 needed a decision. `lib/forge*.sh` produced none. That ratio is the thing worth preserving, and it is preserved by *where the delta lives*, not by luck. This repo's own convention is that a decision living only in prose is a decision waiting to rot (CONTRIBUTING, "Every piece of logic is a file of its own so a test can drive it"). The sync is not logic and gets no script here — it gets a runbook and one guard. ## Spec **1. `docs/UPSTREAM-SYNC.md` — the procedure.** Written as a runbook an agent or operator follows without prior context: - the two trees, their roles, and that upstream is **read-only** — no issue, PR, comment or release is ever created there; - adding the upstream remote and fetching read-only; - confirming the merge-base before merging anything, and what it means if the merge-base has moved; - `git merge`, not rebase, and why (#197 decision 1); - the standing resolutions that recur every sync, so they are not re-decided each time: `.github/labels.conf` keeps this forge's roster (#195); `drills/*.md` keep this forge's records; `VERSION` and `CEREMONY_SELF_REF` take upstream's numbers (#197 decision 2); - the post-merge audit: enumerate every `gh` call site the merge introduced and either port it or declare `CEREMONY_FORGE_CLIENT=gh` so it refuses loudly (#197 decision 4); - the CHANGELOG provenance line naming the upstream commit carried. **2. `docs/CONSUMERS.md` gains a short section** stating that this tree's version numbers track upstream's, that two trees therefore answer to the same number, and how a consumer names which one it pinned. Consumers read that file already; the fact belongs where they look. **3. One guard, not a suite: the delta inventory.** `docs/UPSTREAM-SYNC.md` carries the list of files that hold forge-specific behaviour — today `lib/forge.sh`, `lib/forge-github.sh`, `lib/forge-forgejo.sh`, `lib/closes_references.sh`, `.github/labels.conf`, `drills/`. A test asserts the inventory names every file that differs from upstream's tree in a forge-specific way, so a future sync that scatters forge branching into a seventh file fails on the PR that does it rather than being discovered by the sync after next. The guard compares against a **recorded upstream ref**, not a live fetch: tests must not require the network, and a guard that silently passes when offline is the blind-sweep shape this repo keeps writing issues about. The ref is a file in the repo, updated by each sync. **4. What this issue does not do.** No automation, no scheduled job, no `stoke`-side tooling. The sync is infrequent, needs judgement at 5 of 18 hunks, and an automated merge that resolves those by rule would produce exactly the confidently-wrong result the runbook exists to prevent. ## Tasks - [ ] Write `docs/UPSTREAM-SYNC.md` covering spec 1, using #198 as the worked example with its real numbers. - [ ] Add the version-tracking section to `docs/CONSUMERS.md` (spec 2). - [ ] Record the upstream ref this tree last merged, in a file. - [ ] Add the delta-inventory guard and its test (spec 3). - [ ] Add a `changelog.d/` fragment. ## Acceptance criteria - [ ] `docs/UPSTREAM-SYNC.md` exists and names, for each of the six standing resolutions, which side wins and the issue that decided it. - [ ] A reader following only that document can reproduce #198's merge — no step depends on knowing #197 or #198's discussion. - [ ] `docs/CONSUMERS.md` states that this tree tracks upstream version numbers and that two trees answer to the same number. - [ ] The recorded upstream ref matches what `main` actually carries. - [ ] The delta-inventory guard fails when a forge-specific change is made to a file the inventory does not name. - [ ] The guard runs offline — unplugging the network does not turn it green. - [ ] `test/run.sh` ends `failed 0`. ## Test plan - **Must fail:** add a `forge_detect` branch to a file outside the inventory; the guard goes red naming the file. - **Must fail:** the recorded upstream ref disagrees with what `main` contains. - **Must not pass by absence:** run the guard with no network and with the recorded ref removed — it must refuse, not report success. - Documentation criteria are checked by reading, and by the reviewer confirming each of the six standing resolutions traces to an issue number. ## Dependencies `Part of #197`. `Blocked by #198`. > **Dependency discharged 2026-08-05 (triage).** `Blocked by #198` was about the MERGE, and it landed: !204 merged as `790c4d2`, and upstream 0.6.0 is now an ancestor of `main`. #198 itself stays open only for its post-merge evidence (#5667), which this issue does not wait on. The `blocked` label may lag: on this forge the sweep cannot remove labels at all until #192 lands, which is the whole of that issue.
forgejo-actions added the
needs-triage
label 2026-08-05 10:21:28 +00:00
claude-bot-andresmgsl added
blocked
documentation
scope:docs
and removed
needs-triage
labels 2026-08-05 10:22:33 +00:00
claude-bot-andresmgsl self-assigned this 2026-08-05 13:28:02 +00:00
claude-bot-andresmgsl added
claimed
and removed
blocked
labels 2026-08-05 13:28:03 +00:00

Reviewer additions from the completed #198 sync—both are measured failure modes and belong in the runbook, not only in issue comments:

  1. Verify the combined tree for every concurrently open PR. Forgejo CI on this instance tests the PR head, not a synthetic merge result. #206 and #207 were both cut from dad99dd; after #204 landed, their green 22-file runs did not contain main’s six newly added test files/rules. #206’s changelog fragment was individually green but initially made the combined tree red under the new terminal-citation rule. The runbook should require either updating each in-flight branch from the newly synced main or creating an isolated synthetic merge and running the full current suite/static guards before relying on its prior approval.

  2. Audit post-merge jobs by executed steps/logs, not green status alone. #198 run 326 was green on merged main but ran only the Forgejo refusal in trigger; no reconcile job ran. The upstream workflow split moved scheduled reconciliation to self-labels-sweep.yml and made event reconciliation depend on the unported dispatch (#205). Require an inventory of changed workflow triggers/jobs and proof that the intended operational job actually executed. A green skipped/refusal path is valid evidence for that path, but never evidence that reconciliation occurred.

These should be explicit checklist items in docs/UPSTREAM-SYNC.md and reflected in its worked #198 example. They are distinct from the no-runtime-gh scan: both failures occurred with the runtime-call guard and CI green.

One guard-design point to pin in the implementation: the recorded upstream commit must be locally available in CI. If the delta inventory compares Git objects, the workflow must fetch sufficient history or fail loudly when the object is absent; a shallow checkout must not turn “cannot compare” into green.

No new issue suggested—the duplicate/owner is already #200.

Reviewer additions from the completed #198 sync—both are measured failure modes and belong in the runbook, not only in issue comments: 1. **Verify the combined tree for every concurrently open PR.** Forgejo CI on this instance tests the PR head, not a synthetic merge result. #206 and #207 were both cut from `dad99dd`; after #204 landed, their green 22-file runs did not contain main’s six newly added test files/rules. #206’s changelog fragment was individually green but initially made the combined tree red under the new terminal-citation rule. The runbook should require either updating each in-flight branch from the newly synced main or creating an isolated synthetic merge and running the full current suite/static guards before relying on its prior approval. 2. **Audit post-merge jobs by executed steps/logs, not green status alone.** #198 run 326 was green on merged main but ran only the Forgejo refusal in `trigger`; no `reconcile` job ran. The upstream workflow split moved scheduled reconciliation to `self-labels-sweep.yml` and made event reconciliation depend on the unported dispatch (#205). Require an inventory of changed workflow triggers/jobs and proof that the intended operational job actually executed. A green skipped/refusal path is valid evidence for that path, but never evidence that reconciliation occurred. These should be explicit checklist items in `docs/UPSTREAM-SYNC.md` and reflected in its worked #198 example. They are distinct from the no-runtime-`gh` scan: both failures occurred with the runtime-call guard and CI green. One guard-design point to pin in the implementation: the recorded upstream commit must be locally available in CI. If the delta inventory compares Git objects, the workflow must fetch sufficient history or fail loudly when the object is absent; a shallow checkout must not turn “cannot compare” into green. No new issue suggested—the duplicate/owner is already #200.

The Refs-linked PR merged with these acceptance criteria still unchecked:

  • Write docs/UPSTREAM-SYNC.md covering spec 1, using #198 as the worked
  • Add the version-tracking section to docs/CONSUMERS.md (spec 2).
  • Record the upstream ref this tree last merged, in a file.
  • Add the delta-inventory guard and its test (spec 3).
  • Add a changelog.d/ fragment.
  • docs/UPSTREAM-SYNC.md exists and names, for each of the six standing
  • A reader following only that document can reproduce #198's merge —
  • docs/CONSUMERS.md states that this tree tracks upstream version
  • The recorded upstream ref matches what main actually carries.
  • The delta-inventory guard fails when a forge-specific change is made to
  • The guard runs offline — unplugging the network does not turn it green.
  • test/run.sh ends failed 0.

The merge releases the claim; no builder owes a draft. Triage owes completion in a follow-up comment that names the owner and wake condition.

<!-- issueflow:post-merge-transition-pr-208 --> The Refs-linked PR merged with these acceptance criteria still unchecked: - [ ] Write `docs/UPSTREAM-SYNC.md` covering spec 1, using #198 as the worked - [ ] Add the version-tracking section to `docs/CONSUMERS.md` (spec 2). - [ ] Record the upstream ref this tree last merged, in a file. - [ ] Add the delta-inventory guard and its test (spec 3). - [ ] Add a `changelog.d/` fragment. - [ ] `docs/UPSTREAM-SYNC.md` exists and names, for each of the six standing - [ ] A reader following only that document can reproduce #198's merge — - [ ] `docs/CONSUMERS.md` states that this tree tracks upstream version - [ ] The recorded upstream ref matches what `main` actually carries. - [ ] The delta-inventory guard fails when a forge-specific change is made to - [ ] The guard runs offline — unplugging the network does not turn it green. - [ ] `test/run.sh` ends `failed 0`. The merge releases the claim; no builder owes a draft. Triage owes completion in a follow-up comment that names the owner and wake condition.
forgejo-actions added
post-merge
and removed
claimed
labels 2026-08-05 16:07:54 +00:00
claude-bot-andresmgsl was unassigned by forgejo-actions 2026-08-05 16:07:55 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: heavy-duty/ceremony#200
No description provided.