From b6181ff41654497ef99e36c320b1f17cd080beea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E7=94=9F?= Date: Wed, 26 Aug 2026 09:33:33 +0800 Subject: [PATCH 1/2] fix(tokenless): fail RPM build when spec lacks anolisa-component Provides MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirror the build_cosh_ng component-contract guard for build_tokenless: derive the component name from .anolisa/component.toml.in, verify it matches the RPM package name, and require an exact "Provides: anolisa-component()" line in tokenless.spec.in before running rpmbuild. Spec drift now fails the build instead of shipping an RPM that silently lacks the capability used by `anolisa install tokenless` when the component index is unavailable (GH-2836). Signed-off-by: 林生 --- scripts/rpm-build.sh | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/scripts/rpm-build.sh b/scripts/rpm-build.sh index 7544f05075..58e2fbc849 100755 --- a/scripts/rpm-build.sh +++ b/scripts/rpm-build.sh @@ -463,6 +463,38 @@ build_tokenless() { local pkg_name pkg_name=$(parse_spec_name "$spec_in") + + # Component contract guard (mirrors build_cosh_ng): the RPM must publish + # the anolisa-component() capability so `anolisa install tokenless` + # can resolve the package via Provides when the component index is + # unavailable. Fail the build on spec drift instead of shipping a package + # that silently lacks the capability (GH-2836). + local component_in="${TOKEN_DIR}/.anolisa/component.toml.in" + if [ ! -f "$component_in" ]; then + err "Component contract template not found: $component_in" + return 1 + fi + local component_name + component_name=$(awk ' + $0 == "[component]" { in_component = 1; next } + in_component && /^\[/ { exit } + in_component && /^name = / { + value = $0 + sub(/^name = "/, "", value) + sub(/"$/, "", value) + print value + exit + } + ' "$component_in") + if [ "$component_name" != "$pkg_name" ]; then + err "tokenless identity mismatch: RPM name '${pkg_name}', component name '${component_name}'" + return 1 + fi + if ! grep -Fqx "Provides: anolisa-component(${component_name})" "$spec_in"; then + err "tokenless spec must provide anolisa-component(${component_name})" + return 1 + fi + local tarball_name="${pkg_name}-${version}.tar.gz" # Step 1: Process spec template From f359ab95d291ee50025cc348f10a0d1fcc687626 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E7=94=9F?= Date: Wed, 26 Aug 2026 10:33:50 +0800 Subject: [PATCH 2/2] fix(tokenless): enforce Provides contract guard in nightly RPM workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The checked-in nightly pipeline produces tokenless RPMs through docker-nightly.yaml -> _rpm-build.yaml, which processes the archived spec and calls rpmbuild directly, so the build_tokenless guard added to scripts/rpm-build.sh never runs there: deleting the "Provides: anolisa-component(tokenless)" line would still let the nightly publish an RPM with the original defect (GH-2836). Close the gap with the same component contract assertion, gated on inputs.component == 'tokenless' so other components are unaffected: - New "Verify tokenless component contract" step before rpmbuild: derive the component name from .anolisa/component.toml.in in the source archive, verify it matches the RPM package name, and require an exact "Provides: anolisa-component()" line in the processed spec. - Extend "Collect RPM artifacts" to verify every built RPM actually publishes the anolisa-component() capability via rpm -qp --provides, catching malformed Provides lines that rpmbuild would silently drop. Signed-off-by: 林生 --- .github/workflows/_rpm-build.yaml | 63 +++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/.github/workflows/_rpm-build.yaml b/.github/workflows/_rpm-build.yaml index e7f1986878..5ad5bcfaab 100644 --- a/.github/workflows/_rpm-build.yaml +++ b/.github/workflows/_rpm-build.yaml @@ -249,6 +249,54 @@ jobs: echo "SPEC_BASENAME=$(basename "$SPEC_FILE")" >> $GITHUB_ENV echo "EXTRACT_DIR=${EXTRACT_DIR}" >> $GITHUB_ENV + - name: Verify tokenless component contract + if: inputs.component == 'tokenless' + run: | + # Component contract guard for the nightly RPM path: this workflow + # processes the archived spec and calls rpmbuild directly, so it + # never runs the build_tokenless guard in scripts/rpm-build.sh. + # Mirror that assertion here: derive the component name from + # .anolisa/component.toml.in, verify it matches the RPM package + # name, and require an exact "Provides: anolisa-component()" + # line in the processed spec, so spec drift fails the build + # instead of publishing an RPM that silently lacks the capability + # used by `anolisa install tokenless` (GH-2836). + COMPONENT_IN=$(find "$EXTRACT_DIR" -path '*/.anolisa/component.toml.in' | head -1) + if [ -z "$COMPONENT_IN" ]; then + echo "ERROR: component contract template (.anolisa/component.toml.in) not found in source archive" + exit 1 + fi + + COMPONENT_NAME=$(awk ' + $0 == "[component]" { in_component = 1; next } + in_component && /^\[/ { exit } + in_component && /^name = / { + value = $0 + sub(/^name = "/, "", value) + sub(/"$/, "", value) + print value + exit + } + ' "$COMPONENT_IN") + if [ -z "$COMPONENT_NAME" ]; then + echo "ERROR: could not parse component name from $COMPONENT_IN" + exit 1 + fi + + SPEC_NAME=$(grep -E '^Name:' "$HOME/rpmbuild/SPECS/$SPEC_BASENAME" | awk '{print $2}' | tr -d ' \t') + if [ "$COMPONENT_NAME" != "$SPEC_NAME" ]; then + echo "ERROR: tokenless identity mismatch: RPM name '$SPEC_NAME', component name '$COMPONENT_NAME'" + exit 1 + fi + + if ! grep -Fqx "Provides: anolisa-component($COMPONENT_NAME)" "$HOME/rpmbuild/SPECS/$SPEC_BASENAME"; then + echo "ERROR: tokenless spec must provide anolisa-component($COMPONENT_NAME)" + exit 1 + fi + + echo "TOKENLESS_COMPONENT_NAME=$COMPONENT_NAME" >> "$GITHUB_ENV" + echo "tokenless component contract OK: $SPEC_NAME provides anolisa-component($COMPONENT_NAME)" + - name: Build RPM run: | rpmbuild -bb --nodeps \ @@ -274,6 +322,21 @@ jobs: done < <(find /tmp/rpm-output/ -maxdepth 1 -type f -name "*.rpm" -print) fi + if [ "${{ inputs.component }}" = "tokenless" ]; then + # Verify the built artifact actually publishes the component + # capability; this catches cases where rpmbuild silently drops a + # malformed Provides line that passed the spec text guard. + while IFS= read -r rpm_path; do + echo "Verifying tokenless RPM capabilities: $rpm_path" + if ! rpm -qp --provides "$rpm_path" | grep -Eq "^anolisa-component\(${TOKENLESS_COMPONENT_NAME}\)([[:space:]]|$)"; then + echo "ERROR: $rpm_path does not provide anolisa-component(${TOKENLESS_COMPONENT_NAME})" + echo "Actual provides:" + rpm -qp --provides "$rpm_path" + exit 1 + fi + done < <(find /tmp/rpm-output/ -maxdepth 1 -type f -name "*.rpm" -print) + fi + - name: Upload RPM artifact uses: actions/upload-artifact@v4 with: