Source Promotion
August 28, 2026 ยท View on GitHub
This runbook promotes reviewed development history from
EffortlessMetrics/ripr-swarm into the release-authority repository
EffortlessMetrics/ripr.
The goal is not to copy a final tree. The goal is to preserve the reviewed swarm commits, preserve source-only release and security work, and leave an auditable Git graph.
Non-negotiable history contract
never squash
never rebase
never cherry-pick or reconstruct the swarm range
never merge a source-promotion PR with GitHub's squash option
A source-promotion PR must be merged with Create a merge commit. The copy-safe CLI equivalent is:
gh pr merge <PR> --repo EffortlessMetrics/ripr \
--merge \
--match-head-commit <PROMOTION_HEAD_SHA>
If a promotion PR is squashed accidentally, stop the release. Do not tag or publish. Revert the flattened merge if necessary and repeat the promotion with preserved ancestry.
Inputs
Freeze these values before creating the promotion branch:
SOURCE_PARENT=<exact EffortlessMetrics/ripr main SHA>
SWARM_CANDIDATE=<exact frozen EffortlessMetrics/ripr-swarm SHA>
VERSION=<requested release version>
PREVIOUS_SWARM_PROMOTION=<last swarm SHA promoted to source>
Also record:
- the controlling source and swarm issues;
- the latest required swarm check receipts;
- included and deferred swarm work;
- source-only commits and paths that must survive;
- swarm-only workflows or settings that must not become source authority.
SWARM_REF in the preflight receipt is not a floating branch or a local-only
namespace. It must be the exact fully-qualified protected candidate tag
refs/tags/ripr-release-${VERSION}-${SWARM_CANDIDATE} from the active swarm tag
ruleset, and it must resolve in the actual EffortlessMetrics/ripr-swarm
remote to SWARM_CANDIDATE. The source promotion contract also verifies fixed
public ruleset 20661783 has the exact refs/tags/ripr-release-* singleton
pattern with active update and deletion protections. Missing, wrong-target,
missing-ruleset, or mismatched-ruleset inputs fail closed.
The governed candidate tag is expected to be lightweight: its direct ref must
resolve to the candidate commit, rather than relying on an annotated tag object.
Do not use a floating swarm/main ref as the reviewed candidate.
Choose the promotion mode
Fast-forward promotion
Use fast-forward mode only when source is already an ancestor of the frozen swarm candidate and there is no source-only divergence to preserve:
git merge-base --is-ancestor "$SOURCE_PARENT" "$SWARM_CANDIDATE"
A zero exit status makes fast-forward technically available. The release owner must still confirm that no source-only release, security, workflow, metadata, or history commit would be lost.
Two-parent history-preserving join
Use a two-parent join whenever source and swarm have diverged. This is the
required mode for the 0.11.0 release train.
The join commit must have exactly these ordered parents:
parent 1: exact source main at branch creation
parent 2: exact frozen swarm candidate
The source parent remains first so first-parent release history stays readable. The swarm candidate remains second so every reviewed swarm commit stays reachable.
Preflight
Use a fresh clone or disposable worktree:
git clone git@github.com:EffortlessMetrics/ripr.git ripr-promote
cd ripr-promote
git remote add swarm git@github.com:EffortlessMetrics/ripr-swarm.git
git fetch origin --prune --tags
git fetch swarm --prune --tags
git cat-file -e "$SOURCE_PARENT^{commit}"
git cat-file -e "$SWARM_CANDIDATE^{commit}"
git merge-base "$SOURCE_PARENT" "$SWARM_CANDIDATE"
git rev-list --count "$PREVIOUS_SWARM_PROMOTION..$SWARM_CANDIDATE"
Confirm the candidate is still reachable from swarm main:
git merge-base --is-ancestor "$SWARM_CANDIDATE" swarm/main
Record the merge base, included commit count, expected conflicts, source survivors, and swarm exclusions before creating the join.
Create the promotion branch
git switch -c "promote/${VERSION}-swarm" "$SOURCE_PARENT"
Fast-forward mode
git merge --ff-only "$SWARM_CANDIDATE"
Two-parent join mode
git merge --no-ff --no-commit "$SWARM_CANDIDATE"
Resolve only the conflicts identified by the preflight. Preserve source release and publish authority, source metadata and history, and any named source-only analyzer fixes. Exclude or deliberately resolve swarm-only automation that does not belong in the release-authority repository.
Do not bump crate or extension versions and do not add the new release section
to CHANGELOG.md in this PR. Release metadata is a separate review obligation.
Commit the join:
git commit -m "promote: join frozen ripr-swarm candidate for ${VERSION}"
PROMOTION_HEAD_SHA="$(git rev-parse HEAD)"
Verify the branch history
For two-parent mode:
set -- $(git show -s --format='%P' "$PROMOTION_HEAD_SHA")
test "$#" -eq 2
test "\$1" = "$SOURCE_PARENT"
test "\$2" = "$SWARM_CANDIDATE"
git merge-base --is-ancestor "$SOURCE_PARENT" "$PROMOTION_HEAD_SHA"
git merge-base --is-ancestor "$SWARM_CANDIDATE" "$PROMOTION_HEAD_SHA"
Also verify:
- all named source-only survivor commits and paths remain present;
- every swarm exclusion has an explicit resolution;
- crate and extension versions remain at the pre-metadata value;
CHANGELOG.mdhas no new release section;- generated and golden changes are understood;
- the full source proof suite passes.
Open the PR
Push the exact reviewed head:
git push --set-upstream origin "promote/${VERSION}-swarm"
Open the source-promotion template:
https://github.com/EffortlessMetrics/ripr/compare/main...promote/<VERSION>-swarm?expand=1&template=source-promotion.md
The PR body must record:
- source parent, swarm candidate, merge base, and promotion head;
- ordered parent proof;
- included swarm range and count;
- conflict resolutions;
- source-only survivors;
- swarm-only exclusions;
- version/changelog no-change proof;
- current checks and artifacts;
- post-merge verification command.
Review the integration boundary. The swarm commits were reviewed on the development trunk; the source PR review should focus on identity, conflicts, survivors, exclusions, and current source proof rather than treating the entire swarm range as one opaque new patch.
Merge
Before merging, fetch the current PR head and confirm it still equals the reviewed promotion head. Then use merge commit mode only:
gh pr merge <PR> --repo EffortlessMetrics/ripr \
--merge \
--match-head-commit "$PROMOTION_HEAD_SHA"
Do not use --squash or --rebase.
Post-merge verification
git fetch origin --prune
git merge-base --is-ancestor "$PROMOTION_HEAD_SHA" origin/main
git merge-base --is-ancestor "$SWARM_CANDIDATE" origin/main
git show -s --format='join %H%nparents %P' "$PROMOTION_HEAD_SHA"
The release packet must retain the promotion head and both ordered join parents.
Release readiness must fail when the join is not reachable from source main,
even if the flattened tree contents happen to match.
Recovery
Source main moved before the promotion PR opened
Re-run preflight from the new exact source parent. Do not silently rebase the join commit and reuse old receipts.
The promotion branch has unexpected conflicts
Stop and update the preflight/conflict policy. Do not resolve new conflicts ad hoc inside an already-reviewed plan.
The PR was squashed or rebased
Stop the release and do not tag. The reviewed history was flattened. Revert or otherwise neutralize the incorrect integration, then repeat the source promotion with a preserved join and fresh current-head proof.
A source-only survivor is missing
Treat that as a failed integration, not a documentation discrepancy. Repair the join before the metadata/version PR starts.
Claim boundary
A successful source promotion proves that reviewed swarm history and required
source-only work coexist on source main with auditable ancestry. It does not
prove release metadata, downstream compatibility, or publication.
Repeatable exact-J verification
Retain the final ripr.source_promotion_preflight.v1 receipt as immutable
input, then author and review one deterministic
ripr.source_promotion_resolution.v1 manifest. The manifest binds the
receipt's byte digest, exact parents and merge base, reviewed final tree, and
one disposition with rationale and evidence reference for every conflict,
source-survivor candidate, and swarm-exclusion candidate.
Before merging the direct join, run:
cargo xtask source-promotion verify \
--preflight <exact-preflight.json> \
--resolution-manifest <exact-resolution.json> \
--join-head <exact-40-character-J-SHA> \
--source-main <exact-40-character-held-source-main-SHA> \
--out target/ripr/source-promotion
Merge with a merge commit only, guarded by the expected exact head. Do not
squash, rebase, cherry-pick, or append repair commits to J. After the protected
merge, rerun the command with --main-head <exact-40-character-merged-source-main-SHA> to prove exact J reaches merged
source main. Keep the separate 0.11.0 metadata/version lane out of J.
This rule applies to the intentional repository-sync commits J (source
promotion) and the later K (ancestry-preserving back-sync). Ordinary swarm
feature PRs may be squash-merged within ripr-swarm; those PR commits are
already part of the selected parent-2 history and must remain reachable through
J rather than being reconstructed as source commits.
The verifier is read-only and does not construct J, resolve conflicts, publish, or perform the later ancestry-preserving K back-sync; K verification is a separate follow-up contract.
Preconstruction reviewed-tree validation
Before constructing or publishing a direct two-parent join, validate the exact reviewed tree with the source-parent-selected governance implementation:
cargo xtask source-promotion validate-resolved-tree \
--source-parent <exact-source-parent> \
--swarm-parent <exact-frozen-W7> \
--reviewed-tree <exact-reviewed-tree> \
--preflight <exact-preflight.json> \
--preflight-sha256 <exact-preflight-digest> \
--resolution-manifest <exact-resolution.json> \
--resolution-sha256 <exact-resolution-digest> \
--out target/ripr/source-promotion/resolved-tree
The command requires exact lowercase object identities, canonical non-symlink
sidecars inside the source checkout, and matching sidecar digests. It records
the running source-checkout SHA and executable digest, materializes the reviewed
tree only through an unreferenced disposable object/worktree, runs the ordered
source-owned governance catalog with bounded process-tree termination and
bounded logs, then removes the temporary worktree. A missing, failed,
not_run, or unavailable command, source/checker mismatch, sidecar movement,
ref movement, dirty materialization, or cleanup residue produces rejected.
The canonical JSON and Markdown receipts use schema
ripr.source_promotion_resolved_tree_validation.v1. They omit observed
wall-clock duration so identical semantic states remain byte-stable; each
command instead records the fixed timeout bound. The receipt proves only the
named repository-governance commands on one exact reviewed tree. It does not
construct J, qualify product/editor behavior, or authorize merge or
publication.
Parent-comparative semantic policy decisions remain reviewer-owned inputs. For 0.11.0, #1572 produces the exact network-ledger reconciliation and #1478 binds that receipt into the complete resolution manifest before this command runs the source-trusted final-tree checks.
Source-owned admission, construction, and candidate-ref controller
Issue #1609 adds four typed control-plane subcommands under
cargo xtask source-promotion:
write-trusted-builder-receiptrecords the exact clean source/workflow SHA, Rust toolchain,Cargo.lock, isolated locked target directory, and running executable digest.admit-resolved-treebinds that producer to the exact validated-tree packet, preflight and resolution bytes, and command-catalog and network-policy integration receipts. The integration-index bytes must match the caller-bound lowercase--integration-index-sha256before JSON parsing and again during the final identity snapshot.construct-exact-joinconsumes the admitted packet and a terminal qualification receipt, rechecks the same caller-bound integration-index digest, and requires the qualification bytes to match the caller-bound--qualification-receipt-sha256to create one deterministic, unreferenced, direct two-parent commit object without moving a ref.publish-candidate-refcreates the construction-bound local candidate ref and publishes it only to the bound source repository behind an exact old-or-absent lease.
The typed integration receipts must identify the admitted SOURCE_PARENT as
producer_source_sha and the trusted-builder executable as
producer_executable_sha256; matching schemas and status strings alone are not
producer authority. Admission and construction final snapshots reread every
indexed packet member and typed integration receipt; unchanged index bytes do
not authorize changed member bytes.
The local #1609 controller validates exact content identity and internal consistency only. Its producer fields and digests do not independently prove producer provenance or reviewer acceptance. #1610 owns trusted transport from fixed producer repository, commit/ref, path, and digest authority; #1478 owns reviewer acceptance of integration evidence, and #1507 owns qualification-lane execution and evidence. No local receipt grants merge, publication, or release authority.
The tree-qualification receipt has this exact ordered denominator:
editor_package_linux
editor_package_windows
rust_product
source_governance
source_survivors
trusted_product_journeys
untrusted_workspace_contract
w7_product
Every lane must be terminal passed with a lowercase 64-character evidence
SHA-256. Missing, extra, reordered, renamed, failed, or evidence-free lanes
reject. The qualification also binds the admission packet and receipt,
resolved-tree validation receipt, and admitted network-policy receipt.
Construction compares the complete qualification receipt bytes with the
caller-bound digest before parsing and again during the final preconstruction
reread; a substituted receipt rejects with zero commit-tree attempts.
Controller receipts use numeric commit_tree_attempts, local_ref_attempts,
remote_push_attempts, and merge_command_attempts. Admission performs none
of those operations. Construction may attempt commit-tree once only after
admission and qualification, and never attempts a ref, push, or merge command.
Publication never constructs a commit or attempts a merge command; a local-ref
rollback is a second local-ref attempt and remains visible in the receipt.
The --out packet destination is exclusively reserved before any commit-tree,
local-ref, or remote-push operation. The reservation creates and syncs the
contents of control-attempt.json; a missing packet-index.json means final state is
unknown and must be reconciled before retry. The journal binds the protected
commit/tree and packet identities, refs, fixed remotes, expected state, and
maximum operation counters needed for that
reconciliation. Completed packets retain the
attempt journal and publish the complete index last. An existing output path or
unsafe/non-directory parent fails closed without overwriting the earlier packet
and without advancing a Git-mutation attempt counter. Outputs beneath either
Git administration directory or a consumed packet/indexed-sidecar root reject
before any output path is created, so a receipt cannot corrupt its own input.
Malformed-command rejection paths protect every supplied known input, and the
comparison resolves filesystem aliases before testing containment.
This ordering detects process-visible interruption; it does not claim
power-loss durability for directory entries on every supported filesystem.
Construction performs its complete live reread of refs, tree and sidecar
digests, indexed packet members, typed integration receipts, and qualification
bytes immediately before commit-tree. A changed or unreadable value stops
with zero commit-tree attempts.
Construction and publication require --source-main-ref refs/heads/main;
caller-selected aliases never stand in for source authority. Publication
requires --target-ref to equal the construction receipt's
candidate_ref, requires exact matching old-or-absent state locally and
remotely, and uses
--force-with-lease=<target-ref>:<expected-old-or-empty>. Fetch and push URLs
must both equal https://github.com/EffortlessMetrics/ripr.git; the protected
W7 ref is reread from
https://github.com/EffortlessMetrics/ripr-swarm.git.
Before local mutation, after creating the local candidate ref but before push,
and after the push attempt, the controller rereads local and remote source,
local and remote W7, the complete indexed construction packet, the exact join
object, and source fetch/push URLs. A stale pre-push reread attempts to roll the
local ref back to its exact expected old-or-absent state without pushing. If
the guarded push process fails, or the remote is observed not to have published
the exact join, the controller likewise attempts to restore only the local
candidate ref behind an exact-state guard and records whether that rollback
succeeded. This local-only rollback also applies when a failed push is followed
by an observed join: the remote ref is never rolled back, the join remains
unattributed, and the receipt records publication_state_unknown.
An unavailable final remote observation immediately rolls back only the local
candidate ref behind an exact-state guard, then records every mandatory
post-push authority reread before returning publication_state_unknown;
remote state remains unknown and is never rolled back.
Publication receipts state what was observed:
The receipt keeps push_process_succeeded separate from
target_ref_updated; an exit-zero no-op records true and false respectively.
Exit-zero malformed or unparseable porcelain records true process success and
null target-update attribution, remains fail closed, and cannot publish.
The atomic_push and expected_state_guard_passed fields describe that
guarded operation, not the later publication status: both are true for an
attributed target update, null after an attempted push without attribution,
and false when no push was attempted. If another actor moves the remote before
the final reread, the receipt can consequently be rejected while retaining
true operation facts for the controller's attributed update.
publishedmeans the guarded push's machine-readable status reported an actual update of the exact target ref, the remote candidate ref was observed at the exact join, and every post-push authority reread remained valid;published_but_invalidatedmeans the exact join reached the remote candidate ref but a bound source, W7, packet, object, or URL identity invalidated during publication, or the post-push local candidate-ref observation was unavailable;publication_state_unknownmeans the final remote state could not be read, or it equals the join without an actual target-update attribution; an exit-zero up-to-date/no-op push is not publication attribution; andrejectedmeans the controller observed no authoritative publication.
Only published is a successful candidate-ref transport result, and even it is
not source integration or release authority. The other states are stop states;
do not retry blindly or infer rollback of an observed remote mutation.
The #1609 controller never emits or executes a merge command. Every controller
receipt keeps merge_command: null and merge_command_attempts: 0. Issue #1508
owns the later authoritative promotion candidate and merge-command decision
after accepted-tree and qualification evidence exist.
Source Promotion Admission workflow
.github/workflows/source-promotion-admission.yml is the permanent,
source-owned transport for resolved-tree admission. It supports
workflow_call and workflow_dispatch, uses only contents: read, and keeps
all mutable checkouts, isolated repositories, logs, and packets under the
runner-owned temporary root. It does not use pull_request_target, a
privileged environment, write permissions, identity tokens, release secrets,
or caller-selected commands and runners.
The workflow has three closed execution profiles:
liveconsumes exact externally produced source-promotion evidence;positive_syntheticexercises terminal admission through a deterministic source-owned fixture; andj5_negativeexercises the retained combined-tree network-policy under-description and must finish with a complete rejected packet.
The operation mode is admit_only or constructor_dry_run. Live inputs bind
the full source repository/parent/controller identity, the closed
source-owned-xtask@<workflow-source-SHA> trusted-checker identity, swarm
repository, protected W7 ref and peeled commit, reviewed tree, and every
sidecar by producer repository, immutable commit/ref, mode-100644 path, and
lowercase SHA-256.
The live reviewed tree is transported by an exact source-repository carrier
commit. The harness fetches that commit into a runner-owned clone and requires
its tree and ordered parents to equal the requested reviewed tree, source
parent, and W7 parent before admission begins.
Exact-J-free synthetic profiles instead require the closed
reviewed_tree_carrier_sha=not_required sentinel and do not claim live carrier
materialization.
The normalized admission artifact recursively indexes the controller receipts
and every materialized locator sibling under evidence/. Finalization verifies
and consumes that downloaded closure; only the runner-owned Git controller
checkout is read again for the construction command itself.
Rejected finalization packets may declare the construction receipt unavailable,
but still recursively index every partial construction log or sidecar that was
created before rejection. No output directory is represented by no invented
construction evidence; either rejected packet remains self-verifying and red.
Artifact names, floating branches, abbreviated SHAs, candidate-checkout paths,
and caller-supplied success booleans are not authority. Synthetic profiles use
source-owned fixture identities; they are not general path or command inputs.
The checker identity must equal the workflow/controller SHA. The workflow
produces the trusted-builder packet internally from that exact identity, the
pinned toolchain, lockfile, isolated external target, and executable digest.
The workflow runs the production source-promotion run-admission-workflow
harness and preserves its exit status. It finalizes and uploads every available
member of the immutable admission packet with if: always(), downloads that
artifact into a fresh runner-owned path, and independently verifies the indexed
bytes and exact requested identities before enforcing admission. Only then can
finalize-admission-workflow perform admit-only normalization or the optional
constructor dry-run and produce the final normalized
workflow-disposition.json, workflow-disposition.md, and
packet-index.json; the available final packet is uploaded separately with
if: always(). enforce-admission-workflow --expected-status admitted makes
missing, malformed, unsupported, rejected, not_run, unavailable, non-zero,
stale, or contradictory evidence terminal red.
The runner captures the exact ripr.source_promotion_admission_request.v1
bytes and digest before the producer runs. The producer checks that request
against every command input before materialization, and both downloaded and
final verification compare the embedded request to that original authority.
For an admitted result, verification also replays each indexed controller
receipt and reconstructs the normalized attempt counts; outer digest
rebinding alone cannot bless altered nested semantics. A rejected constructor
without a parseable receipt reports the affected attempt counters as unknown,
never as observed zero.
The normalized schema is
ripr.source_promotion_admission_workflow.v1; its only statuses are admitted
and rejected. The indexed packet schema is
ripr.source_promotion_admission_workflow_packet.v1. An admit_only result
requires zero constructor, local-ref, remote-push, merge-command, and
release/publication attempts. A constructor_dry_run result is reachable only
after terminal admission and may create at most one unreferenced object in the
isolated synthetic repository. It still cannot move a ref, push, emit a merge
command, or invoke publication/release behavior.
The PR that adds or changes this workflow can establish only exact-head static and contract proof. Trusted hosted behavior requires a later dispatch of the committed default-branch workflow using the exact merged source SHA, workflow blob SHA, controller/schema versions, fixture identities, and dispatch inputs. A terminal-green admission packet is necessary transport evidence, not product/editor qualification, J6 publication, merge authority, or release authority.
Source Promotion Contract workflow
Promotion PRs opt into the source-side contract check with this exact body marker:
<!-- source-promotion: true -->
The promotion PR must name one immutable source-repository control commit in
its body. The workflow requires exactly one lowercase source-promotion-control
marker; it is one HTML comment with a full lowercase SHA:
<!-- source-promotion-control: <exact-control-commit-SHA> -->
That control commit is a durable sidecar, not an ancestor or child of J and
not a commit in J's tree. It is fetched only from the fixed source repository
https://github.com/EffortlessMetrics/ripr.git. It must contain these tracked
regular files at these fixed paths:
docs/release/source-promotion/contract-inputs.jsondocs/release/source-promotion/preflight.jsondocs/release/source-promotion/resolution-manifest.json
The sidecar's contract-inputs.json has this shape:
{
"schema": "ripr.source_promotion_ci_inputs.v2",
"source_main": "<exact-40-character-source-parent-SHA>",
"join_head": "<exact-40-character-J-SHA>",
"preflight": "docs/release/source-promotion/preflight.json",
"resolution_manifest": "docs/release/source-promotion/resolution-manifest.json",
"preflight_sha256": "<64-character-lowercase-SHA-256>",
"resolution_manifest_sha256": "<64-character-lowercase-SHA-256>"
}
The Source Promotion Contract workflow checks out the exact PR head with
full history, fetches the sidecar control commit from source origin, verifies
that each fixed path is a mode-100644 regular file, rejects a control commit
that is an ancestor or descendant of J, extracts the files by object id,
verifies their digests, and requires both source_main and
join_head to match the live base/head. It then runs the read-only verifier
and uploads JSON/Markdown receipts under an artifact name containing the
PR-head SHA. No candidate-provided path is read, and no input file is required
in J's tree; this avoids the impossible fixed-point construction where an
input file contains J while also contributing bytes to J^{tree}.
The PR body must also contain the live-head guarded merge command and these warnings:
Use Create a merge commit.
Do not use Squash and merge.
Do not use Rebase and merge.
PRs without the marker skip this job; a skipped job is not a promotion pass. The workflow has read-only contents permission and never executes the printed merge command or changes refs, settings, branch protection, tags, releases, publication channels, or secrets.
A workflow path may appear in more than one reviewed resolution category because
the manifest is keyed by kind:key. A promotion PR may change that workflow
only when the authenticated immutable resolution manifest contains at least one
row for the exact path and every row for that path has disposition
swarm_blob or integrated. Missing rows, any source_blob, mixed
source/non-source authority, and unknown or other dispositions fail closed.
Duplicate rows for the same kind:key remain invalid under the resolution
verifier. This reviewed-resolution rule is the only workflow-import authority;
there is no hardcoded workflow-name exception.
After the protected merge, manually dispatch the same workflow from main with
the exact control_commit, J, the original source parent, the trusted
verifier source parent, and the merged source-main SHA. The dispatch lane
requires source_parent == source_main from the immutable sidecar before it
builds the trusted verifier, fetches the same fixed-path sidecar, records the
exact control commit in its normalized post-merge workflow receipt, passes the existing exact-J
arguments and --main-head to the trusted verifier, and fails when an
equivalent flattened tree is present without the exact join object remaining
reachable.