Skip to content

Commit 0006ce6

Browse files
feat: converge template identity, runtime contracts, and release state (#400)
* docs: plan focused ecosystem convergence * feat: make package selectors exact * feat: make template selection exact * feat: make project scaffolding transactional * feat: make runtime selection root-local * feat: validate Linux runtime closure * feat: carry provider-neutral runtime provenance * feat: publish immutable release manifests * feat: reconcile mcpp-bin releases * fix: reconcile runtime bindings with SubOS views * chore: prepare 2026.8.9.1 release * test: recognize Python E2E capability * test: keep ELF parser fixtures on Linux * fix(pm): inherit declared index namespaces exactly * fix(pm): retain index ownership in dependency selection * fix(runtime): model ELF SONAME reuse * test(e2e): align fixtures with exact identities * test(e2e): preserve runtime contracts in fake xlings * fix(index): retain floor cause on exact misses * fix(pm): surface malformed exact descriptors * fix(pm): derive mangling from authored modules * test(e2e): scope BMI settling to its edge * test(scaffold): close fixtures before cleanup * docs: make Draft PR trace privacy-safe * test(e2e): honor isolated mcpp home in libc poison * fix(workspace): anchor inherited indices lexically * docs: add Chinese PR handoff * docs: refresh PR validation handoff * fix(aur): use privacy-safe release identity * docs: record privacy-safe AUR checkpoint * fix(pm): explain exact index route misses * docs: record Windows index route evidence * test(e2e): write fixture paths in host spelling, not shell spelling The Windows E2E 2/2 failure is not the workspace index inheritance the last three commits were aimed at. It is step (15) of 12_add_command.sh, added by this branch, which writes an MSYS path into a manifest: TMP=$(mktemp -d) # Git Bash -> /tmp/tmp.XXXXXXXX acme = { path = "$TMP/myapp/index" } # written as FILE CONTENT MSYS converts POSIX paths on the way into argv and the environment; it does not convert file content. A native mcpp.exe reads the literal leading `/` as "root of the current drive", so the fixture index resolves to C:\tmp\... and is absent. That is exactly what the route diagnostic reported: `local index 'acme': root absent, pkgs absent`. Four independent signals place the failure at step (15), not (14): exit code 2 is mcpp's own status forwarded by `set -e`, while step (14) is wrapped in `|| { …; exit 1; }`; step (14)'s failure banner never appears; the last uncaptured output before it belongs to step (12); and step (14) predates this branch, on which the Windows suite was green. The Wine replay that "reproduced it as passing" was a false negative: Wine maps Z: to /, so \tmp\... lands back on the real /tmp. Wine can show a PE runs. It cannot show anything about path semantics. Fixed as a class rather than a line. `_host_path.sh` provides `host_path` (cygpath -m on MSYS, identity elsewhere, relative paths untouched), 00_fixture_path_hygiene.sh fails any fixture that interpolates a non-`*_HOST` variable into a TOML `path =` value, and the 40 pre-existing sites across 24 files are migrated. Most were masked only because those tests skip on Windows for a missing capability. * test(pm): pin why an inherited index is anchored lexically `inherit_workspace_indices` was switched from `weakly_canonical` to `lexically_normal` in ed4cf64 to fix the red Windows suite. It did not: the same failure appeared identically before (run 31318089536) and after (run 31321961040), and the actual cause was a fixture writing an MSYS path into mcpp.toml. So the change was landed against a misdiagnosis, against a behaviour that was green on main, with no test able to tell the two spellings apart. It turns out to be right for a different reason, and now it says so. Anchoring answers "which directory did the workspace author mean", and the answer must stay inside the tree the author addressed. `weakly_canonical` answers "what is this path once every symlink is resolved", so a workspace reached through a symlinked parent gets relocated into a tree the author never wrote — observable, because prepare reports a missing descriptor as "not found in local index at '<path>'". Verified as a real RED: restoring `weakly_canonical` fails `unit/test_pm_index_route` (71 passed, 1 failed). The existing test also stops asserting a path SPELLING. Comparing the stored path against `(root / "index").lexically_normal()` passes for any rule that happens to produce that string and says nothing about whether the index can be read — which is how it stayed green through a red Windows suite. It now asserts the capability: route description, then a descriptor lookup that actually resolves. * feat(pm): give namespace omission a one-release exit ramp Namespace omission means exactly `mcpplibs`. But every published `compat.*` package and every manifest written before exact identity spells its dependency bare — `gtest = "1.15.2"`, `ftxui = "6.1.9"` — so turning that into an immediate hard error makes an mcpp upgrade break builds against data that is already published and cannot be edited retroactively. Four CI jobs on this branch are that breakage, and this repository's own mcpp.toml had to move gtest under [dev-dependencies.compat] to keep bootstrapping: a change that breaks its own project is breaking everyone's. It is the mirror of the rule that keeps a raised index floor from bricking older clients. Published data must not break the program; a published program must not break existing data either. Both degrade. The #278 defect was the SILENCE, not the reach: mcpp continued with a namespace the user never wrote and never said so. So the rungs come back, and cannot be quiet. After the exact coordinate misses, and only for a selector whose namespace was OMITTED, `(compat, name)` and the namespace-less discovery rung are tried; a hit is announced with the canonical selector and the exact manifest edit, and the canonical identity — not the ambiguous spelling — is what reaches the lock, the install layer and the cache label. `mcpp add` goes further and performs the edit, so touching a dependency migrates it. `mcpplibs.gtest` states an identity and still misses. A third-party namespace is still unreachable by a bare name. Removed in 2026.9, named once as kBareNameFallbackRemovedIn so the warning, the docs and the removal cannot drift apart. Test 162 now asserts both branches and that an already-exact selector is not nagged, and it serves the compat fixture from its own index instead of whatever gtest happened to be installed. * fix(runtime): keep the private libc out of the process environment (#401) `mcpp run` put the private glibc directory on LD_LIBRARY_PATH. That variable is inherited by every process the program ever spawns, and a child like /bin/sh is loaded by the HOST loader — PT_INTERP is baked in and no environment variable overrides it. glibc's libc.so.6 and its ld.so are version-locked through GLIBC_PRIVATE, so the child dies during relocation, before main: sh: symbol lookup error: …/xim-x-glibc/2.44/lib64/libc.so.6: undefined symbol: __pointer_chk_guard, version GLIBC_PRIVATE The reporter hit it through an application probing the desktop theme with gsettings: popen() returned nothing, the app silently used the wrong theme, and mcpp said nothing. Verified locally — the 2.44 payload kills /bin/sh, the 2.39 payload does not, which is why this survived as long as it did and why "it works here" proved nothing. The directory was published to serve a dlopen() the executable's DT_NEEDED closure does not cover, and the artifact's RUNPATH already covers exactly that: the link model emits -Wl,-rpath,<glibc> next to --dynamic-linker, and the built binary's RUNPATH is byte-identical before and after this change. So the environment entry bought nothing and cost every child process. It is gone. Measured against the released mcpp with the same fixture: before LDLP=[…/runtime:…/xim-x-glibc/2.39/lib64] after LDLP=[…/runtime] The decision lives in mcpp.platform.runtime_env_contract as a scope, not a condition: no build-level predicate can make an inherited variable safe for a process mcpp did not launch and cannot see. mcpp#291 was the same mistake one hop closer in. 208 pins all three halves — the dir is in the artifact RUNPATH, it is not in the program's environment, and both the /bin/sh child and the dlopen still work. The RUNPATH assertion deliberately checks coverage mcpp does not itself emit, so losing it is a red test rather than a silent loss of resolution. * test(runtime): a proven mismatch must not go green on a re-run The Linux closure verdict is cached against the artifact stat and the RuntimeBinding contract hash, and caching a FAILURE is the half worth pinning: an unchanged artifact that was proven wrong is still wrong, and reporting success the second time tells the user a problem went away when nothing about the program changed. It is easy to lose by accident, because the project fast path exists to skip work when nothing changed — including, unless something stops it, the check that would have failed. `validated_artifact_snapshot` stops it by refusing to engage unless every stored verdict is a PASS; that rule had no test. 209 poisons one stored verdict without touching a source file and requires the next build to fail with the recorded diagnostics, requires `mcpp self doctor` to explain it from storage, and then requires a rebuilt artifact to be re-validated — a mismatch is cached, not a sentence, or the first real failure would wedge the project until someone deleted target/. * ci(aur): require arming before an unattended publish `schedule` fires every six hours off the default branch and set publish=true unconditionally. Merging this workflow was therefore enough to make mcpp start writing to the AUR on its own, within six hours, on a path that had never completed a real push — only a makepkg --verifysource dry-run. Merging is a decision about code; publishing to a third-party service is a decision about the outside world, and they should not be the same act. Both automatic triggers now plan and report but withhold the push unless the repository variable AUR_AUTOPUBLISH is exactly "true", so a typo fails closed. Dry runs still validate payloads, render .SRCINFO, query the AUR and print the diff — only the push is withheld, so nothing is lost but the surprise. workflow_dispatch keeps its explicit per-run switch, which is how the first watched publish is meant to happen, and unsetting the variable is a kill switch that needs no revert. The contract test runs the workflow's own decision shell rather than a paraphrase of it, so the guarantee cannot drift from the YAML. * test(e2e): assert local-index addressing on every host Every test covering local-index addressing needed `gcc` or `fresh-sandbox`, and Windows has neither capability. So the one platform where path semantics actually differ was the one platform never asserting them — which is why a fixture could write an MSYS path into mcpp.toml and the failure surfaced four steps away, as "package not found in any configured index", and was chased through the workspace inheritance code that was not involved. 210 needs no compiler, no sandbox bootstrap and no network: it asserts at the resolution layer, which is the layer that was broken. An absolute [indices] path resolves; a workspace member inherits a root-relative one and reads it; a miss in both reports the index as a READABLE root, since "root absent" is the fingerprint of a path written in the wrong spelling; and the route diagnostic that makes any of this findable stays free of filesystem paths. * chore(release): 2026.8.10.1 on xlings 2026.8.10.1 Two independent bumps that happen to land on the same date string. The xlings pin moves from 2026.8.9.2 to the current stable 2026.8.10.1 across all 16 pin sites; check_version_pins.sh verifies them against src/xlings.cppm rather than trusting a comment. Note that 2026.8.10.1 still declares a bare `ftxui`, so the four cross-repo CI failures on this branch are resolved by the bare-name migration window in this PR, not by the pin — openxlings/xlings#521 remains the correct upstream fix and is no longer on the critical path. The release version moves from 2026.8.9.1 to 2026.8.10.1 because the version is a DATE and the date changed while this branch was in review. 2026.8.9.1 was never tagged, so nothing depends on the old string. The release-manifest and AUR fixtures derive "some other version" from VERSION; both halves move so they stay distinct instead of collapsing onto the release version and silently asserting nothing. Bootstrap pin stays at the released 2026.8.8.4 — it is the self-hosting starting point, not a function of what this release will be. * docs: the bare-name window, the runtime env scope, and AUR arming Three things a user or a maintainer cannot discover from the code: - The one-release exit ramp for namespace-omitted selectors: what still resolves, what the warning says, that the canonical identity is what reaches the lock, that `mcpp add` performs the migration, and that a stated namespace is not eligible. Written with the reason attached — every published `compat.*` package spells its dependency bare, so failing them outright would make a program release invalidate data that is already published and cannot be edited. - Why the private libc directory is binary-scoped and never environment-scoped, as a table of blast radius rather than a rule to memorize: DT_RUNPATH reaches one object, LD_LIBRARY_PATH reaches every descendant process forever, and a GLIBC_PRIVATE-coupled libc handed to the host loader kills /bin/sh before main (#401). - How to arm the AUR automatic triggers, including that the kill switch is unsetting a repository variable rather than reverting a commit. English and Chinese both, plus the changelog entry for 2026.8.10.1. * chore(xlings): hold the pin at 2026.8.9.2 until openxlings/xlings#524 Pinning to the current stable 2026.8.10.1 turned every Linux job red at the same place: a COLD home cannot install xim:gcc@16.1.0 at all. Downloading xim:glibc@2.44 … integrity: cleaning incomplete install: …/xpkgs/xim-x-gcc/16.1.0 [gcc] failed: config hook failed [xim:xpkg]: glibc payload not found, but gcc needs it to rewrite its ELF interpreter away from the build machine's path. install it first: xlings install xim:glibc@2.39 The version it installs and the version the hook asks for disagree, and gcc.lua declares `xim:glibc@>=2.39` so resolving 2.44 is legal — the failure is that the consumer's config hook cannot find the dependency payload that was just fetched. A/B on the same workflow, same runner image, both with a confirmed cache miss, so this is not a long-standing cold-start bug that warm caches were hiding: 2026.8.9.2 cold → gcc installs (run 31317627461) 2026.8.10.1 cold → the failure above (run 31335075557, 4/4 Linux jobs) Nothing in mcpp can route around it: it is upstream install ordering or store-root lookup, and it breaks every new machine, every cold CI cache and every fresh install. Reported with the evidence as openxlings/xlings#524; the pin moves once that ships. The release version stays 2026.8.10.1 — it is a date, and this is a different project's version that happens to share the string. * docs: record the completion design and what executing it actually found The design that drove this round of work, plus an appendix of outcomes written after the fact — including the two places reality diverged from the plan. D2 resolved as "proven, keep", but for a different reason than the commit it was defending claimed: not a Windows short-name alias, but that anchoring must not relocate an index out of the tree the workspace author addressed. D5's invariant turned out to already hold; it simply had no test. Two things arrived unplanned: #401, and the discovery that every identity/index-routing e2e was gated behind a capability Windows does not have — so the platform where path semantics differ was the one platform never asserting them. And main line C's bottleneck moved. The migration window took openxlings/xlings#521 off the critical path as intended, but pinning to the newest xlings put a harder one in its place: a cold home cannot install gcc at all (openxlings/xlings#524). Recorded with the A/B, because the lesson generalizes — "pin to latest" is an action that needs verifying, not a text substitution, and it only surfaced here because changing the pin also changed the CI cache key. * test(e2e): 166 asserted the mechanism, not the capability 166 exists for mcpp#291 and had two halves. The negative half — a plain binary must not be handed the private glibc — is still exactly right and is unchanged. The positive half required the opposite in the presence of a dlopen-reachable dependency: the payload dir HAD to be on LD_LIBRARY_PATH, "because dlopen'd libraries do not consult the executable's RUNPATH". The guard was right to exist. It stopped anyone from "fixing" #291 by deleting the entry and quietly breaking dlopen. But it pinned the mechanism rather than the capability, and the mechanism's premise was false: a dlopen() performed by the executable does consult the executable's DT_RUNPATH, and the link model already puts the payload glibc there — the artifact's RUNPATH is byte-identical with and without the environment entry. So the entry bought nothing while reaching every descendant process, which is how it became mcpp#401. The capability it was protecting now lives in 208, which asserts the dlopen actually resolves. 166 keeps the rule it is named for and now applies it to both shapes, plus one new assertion: the project's own [runtime] library_dirs entry must still be there. Only the private libc is binary-scoped; dropping ordinary runtime dirs too would be a different regression that the negative assertion alone would not catch. Full local Linux e2e on this HEAD: 202 passed, 0 failed, 8 skipped. --------- Co-authored-by: speak-agent <x.d2learn.org@gmail.com> Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com>
1 parent 80291ca commit 0006ce6

139 files changed

Lines changed: 17055 additions & 1765 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agents/docs/2026-08-09-mcpp-template-runtime-graphics-aur-focused-design.md

Lines changed: 1034 additions & 0 deletions
Large diffs are not rendered by default.

.agents/docs/2026-08-09-mcpp-template-runtime-graphics-aur-implementation-plan.md

Lines changed: 408 additions & 0 deletions
Large diffs are not rendered by default.

.agents/docs/2026-08-09-mcpp-template-runtime-graphics-aur-validation.md

Lines changed: 408 additions & 0 deletions
Large diffs are not rendered by default.

.agents/docs/2026-08-09-pr400-handoff-zh.md

Lines changed: 422 additions & 0 deletions
Large diffs are not rendered by default.

.agents/docs/2026-08-09-xlings-mcpp-ecosystem-convergence-design.md

Lines changed: 985 additions & 0 deletions
Large diffs are not rendered by default.

.agents/docs/2026-08-10-pr400-completion-design.md

Lines changed: 613 additions & 0 deletions
Large diffs are not rendered by default.

.github/actions/bootstrap-mcpp/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ inputs:
2525
# `package.name`, so one of the two was simply unreachable — and which one
2626
# depended on the machine, which is why CI failed on `compat:lua` on
2727
# Windows and `mcpplibs.capi:lua` on Linux. Never pin below that.
28-
default: '2026.8.8.1'
28+
default: '2026.8.9.2'
2929
cache-target:
3030
description: also restore/save target/ (build artifacts + BMIs)
3131
required: false

.github/actions/setup-macos-llvm/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ inputs:
1515
# Floor imposed by the index, not a routine bump — see
1616
# .github/actions/bootstrap-mcpp/action.yml for why 0.4.69 is required
1717
# (two packages named `lua` in one repo need openxlings/xlings#381).
18-
default: '2026.8.8.1'
18+
default: '2026.8.9.2'
1919

2020
runs:
2121
using: composite

.github/workflows/aur-publish.yml

Lines changed: 109 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,138 @@
11
name: aur-publish
22

3-
# Publish the `mcpp-bin` and `mcpp` AUR packages after a release.
4-
#
5-
# Triggers on COMPLETION of the `release` workflow (not on `release:
6-
# published`): release.yml creates the GitHub Release in its first job but
7-
# uploads the aarch64 / macOS / Windows assets in LATER jobs, so the aarch64
8-
# .sha256 that mcpp-bin needs only exists once the whole workflow finishes.
9-
#
10-
# Requires one repository secret:
11-
# AUR_SSH_PRIVATE_KEY — private key whose public half is registered on the
12-
# AUR account that owns mcpp / mcpp-bin.
13-
# See scripts/aur/README.md → "Automated publishing" for the full setup.
3+
# Reconcile only mcpp-bin. This workflow is downstream of `release`, so its
4+
# failure is visible without changing the already-terminal release conclusion.
145
on:
156
workflow_run:
167
workflows: [release]
178
types: [completed]
9+
schedule:
10+
- cron: '17 */6 * * *'
1811
workflow_dispatch:
1912
inputs:
20-
version:
21-
description: "Version to publish (default: [package].version in mcpp.toml)"
13+
publish:
14+
description: 'Publish the validated diff (false performs a dry-run only)'
15+
type: boolean
16+
required: true
17+
default: false
18+
tag:
19+
description: 'Optional exact latest complete stable tag (no downgrade override)'
20+
type: string
2221
required: false
2322

2423
concurrency:
25-
group: aur-publish
24+
group: aur-mcpp-bin-reconcile
2625
cancel-in-progress: false
2726

27+
permissions:
28+
contents: read
29+
2830
jobs:
29-
publish:
30-
runs-on: ubuntu-latest
31-
# On the workflow_run trigger, only proceed if the release actually
32-
# succeeded (skip failed/cancelled release runs).
31+
reconcile:
32+
name: reconcile mcpp-bin
3333
if: >-
34-
github.event_name == 'workflow_dispatch' ||
34+
github.event_name != 'workflow_run' ||
3535
github.event.workflow_run.conclusion == 'success'
36+
runs-on: ubuntu-24.04
37+
timeout-minutes: 60
38+
env:
39+
GH_TOKEN: ${{ github.token }}
40+
PYTHONDONTWRITEBYTECODE: '1'
41+
REQUESTED_TAG: ${{ inputs.tag }}
3642
steps:
37-
- name: Checkout released commit
43+
- name: Checkout reconciler source
3844
uses: actions/checkout@v4
3945
with:
40-
# workflow_run: the exact commit the release was built from.
41-
# workflow_dispatch: default ref (HEAD of the branch).
4246
ref: ${{ github.event.workflow_run.head_sha || github.ref }}
4347

44-
- name: Refresh both PKGBUILDs to the release version
45-
id: refresh
48+
- name: Reconciler contract tests
49+
run: python3 tests/scripts/test_aur_reconcile.py
50+
51+
# This phase has no SSH private key in its environment. It downloads the
52+
# immutable manifest and both Linux payload/sidecars, recomputes hashes,
53+
# runs makepkg as non-root in Arch, inspects RPC + HTTPS git, and emits the
54+
# exact diff before any publishing secret is loaded.
55+
- name: Inspect and validate desired state
56+
id: plan
4657
env:
47-
# CI runs as root; force update.sh's template .SRCINFO path.
48-
MCPP_AUR_NO_MAKEPKG: "1"
58+
TRIGGER: ${{ github.event_name }}
59+
MANUAL_PUBLISH: ${{ inputs.publish }}
60+
# Repository variable, absent until a human has watched one publish
61+
# succeed. See "Arming the automatic triggers" in scripts/aur/README.md.
62+
AUTOPUBLISH: ${{ vars.AUR_AUTOPUBLISH }}
4963
run: |
50-
VER="${{ github.event.inputs.version }}"
51-
if [ -z "$VER" ]; then
52-
# mcpp.toml at the released commit carries the right version.
53-
VER=$(grep -m1 -E '^\s*version\s*=' mcpp.toml | sed -E 's/.*"([^"]+)".*/\1/')
54-
fi
55-
echo "version=$VER" >> "$GITHUB_OUTPUT"
56-
./scripts/aur/update.sh "$VER"
64+
args=(
65+
--trigger "$TRIGGER"
66+
--report-json "$RUNNER_TEMP/aur-plan.json"
67+
--summary "$GITHUB_STEP_SUMMARY"
68+
)
69+
[[ -z "$REQUESTED_TAG" ]] || args+=(--tag "$REQUESTED_TAG")
70+
python3 scripts/aur/reconcile_mcpp_bin.py "${args[@]}"
5771
58-
- name: Configure AUR SSH
59-
run: |
60-
install -dm700 ~/.ssh
61-
printf '%s\n' "${{ secrets.AUR_SSH_PRIVATE_KEY }}" > ~/.ssh/aur
62-
chmod 600 ~/.ssh/aur
63-
ssh-keyscan -t rsa,ed25519 aur.archlinux.org >> ~/.ssh/known_hosts 2>/dev/null
64-
cat > ~/.ssh/config <<'EOF'
65-
Host aur.archlinux.org
66-
User aur
67-
IdentityFile ~/.ssh/aur
68-
IdentitiesOnly yes
69-
EOF
72+
# An unattended push to a third-party service must be ARMED, not
73+
# inherited from a merge. `schedule` fires every six hours off the
74+
# default branch, so merging this workflow used to be enough to make
75+
# mcpp start writing to the AUR on its own — before anyone had seen
76+
# the reconciler complete a real push even once. Both automatic
77+
# triggers therefore plan-and-report until AUR_AUTOPUBLISH is set;
78+
# `workflow_dispatch` keeps its explicit per-run switch, which is how
79+
# that first push is meant to happen.
80+
case "$TRIGGER" in
81+
workflow_run | schedule)
82+
if [[ "${AUTOPUBLISH:-}" == "true" ]]; then
83+
publish=true
84+
else
85+
publish=false
86+
echo "::notice::AUR_AUTOPUBLISH is not set — reporting the desired state without publishing."
87+
fi
88+
;;
89+
*)
90+
publish=${MANUAL_PUBLISH:-false}
91+
;;
92+
esac
93+
echo "needs_publish=$(jq -r '.needs_publish' "$RUNNER_TEMP/aur-plan.json")" >> "$GITHUB_OUTPUT"
94+
echo "publish=$publish" >> "$GITHUB_OUTPUT"
7095
71-
- name: Push to the AUR
96+
- name: Configure pinned AUR SSH identity
97+
if: steps.plan.outputs.needs_publish == 'true' && steps.plan.outputs.publish == 'true'
7298
env:
73-
VER: ${{ steps.refresh.outputs.version }}
99+
AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
74100
run: |
75-
set -eu
76-
git config --global user.name "mcpp-ci"
77-
git config --global user.email "x.d2learn.org@gmail.com"
101+
test -n "$AUR_SSH_PRIVATE_KEY" || { echo 'AUR_SSH_PRIVATE_KEY is empty'; exit 1; }
102+
install -dm700 "$HOME/.ssh"
103+
install -m600 /dev/null "$HOME/.ssh/aur"
104+
printf '%s\n' "$AUR_SSH_PRIVATE_KEY" > "$HOME/.ssh/aur"
105+
install -m600 scripts/aur/aur.archlinux.org.known_hosts "$HOME/.ssh/known_hosts"
106+
ssh-keygen -lf "$HOME/.ssh/known_hosts" -E sha256 \
107+
| grep -F 'SHA256:RFzBCUItH9LZS0cKB5UE6ceAYhBD5C8GeOBip8Z11+4'
108+
install -m600 /dev/null "$HOME/.ssh/config"
109+
printf '%s\n' \
110+
'Host aur.archlinux.org' \
111+
' User aur' \
112+
' IdentityFile ~/.ssh/aur' \
113+
' IdentitiesOnly yes' \
114+
' StrictHostKeyChecking yes' \
115+
' UserKnownHostsFile ~/.ssh/known_hosts' \
116+
> "$HOME/.ssh/config"
78117
79-
publish() { # $1 = package name (= dir under scripts/aur/)
80-
pkg="$1"; src="scripts/aur/${pkg}"; work="/tmp/aur-${pkg}"
81-
# Clone the existing AUR repo; if the package doesn't exist yet
82-
# (first publish), start an empty repo — AUR creates it on push.
83-
if git clone "ssh://aur@aur.archlinux.org/${pkg}.git" "$work" 2>/dev/null \
84-
&& [ -e "$work/.git" ]; then :; else
85-
rm -rf "$work"; mkdir -p "$work"
86-
git -C "$work" init -q
87-
git -C "$work" remote add origin "ssh://aur@aur.archlinux.org/${pkg}.git"
88-
fi
89-
# AUR repos contain only PKGBUILD, .SRCINFO and local sources.
90-
cp "$src/PKGBUILD" "$src/.SRCINFO" "$src/mcpp.sh" "$work/"
91-
git -C "$work" add -A
92-
if git -C "$work" diff --cached --quiet; then
93-
echo ":: ${pkg}: no changes, skipping"
94-
return 0
95-
fi
96-
git -C "$work" commit -q -m "${pkg} ${VER}"
97-
git -C "$work" push origin HEAD:master
98-
echo ":: ${pkg}: published ${VER}"
99-
}
118+
- name: Fast-forward publish and verify convergence
119+
if: steps.plan.outputs.needs_publish == 'true' && steps.plan.outputs.publish == 'true'
120+
env:
121+
TRIGGER: ${{ github.event_name }}
122+
run: |
123+
args=(
124+
--publish
125+
--trigger "$TRIGGER"
126+
--report-json "$RUNNER_TEMP/aur-final.json"
127+
--summary "$GITHUB_STEP_SUMMARY"
128+
)
129+
[[ -z "$REQUESTED_TAG" ]] || args+=(--tag "$REQUESTED_TAG")
130+
python3 scripts/aur/reconcile_mcpp_bin.py "${args[@]}"
100131
101-
publish mcpp-bin
102-
publish mcpp-m
132+
- name: Preserve reconciliation reports
133+
if: always()
134+
uses: actions/upload-artifact@v4
135+
with:
136+
name: aur-mcpp-bin-reconciliation
137+
path: ${{ runner.temp }}/aur-*.json
138+
if-no-files-found: error

.github/workflows/bootstrap-macos.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
# Dormant (workflow_dispatch only), but kept in step with the rest —
1818
# check_version_pins.sh holds it there. Floor: 0.4.69, below which the
1919
# index cannot resolve two packages that share a short name.
20-
XLINGS_VERSION: '2026.8.8.1'
20+
XLINGS_VERSION: '2026.8.9.2'
2121
steps:
2222
- uses: actions/checkout@v4
2323

0 commit comments

Comments
 (0)