From 6acaa87eb422e3ac6d28e990583ecfaee81e661e Mon Sep 17 00:00:00 2001 From: Pawel Winogrodzki Date: Thu, 14 May 2026 16:39:30 +0000 Subject: [PATCH] docs(instructions): document azl component-modification workflow conventions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Captures the conventions developed during the multi-day session of remediating package-signing/scan failures at the component level (no allow-listing) and folds them into permanent Copilot guidance. No new tooling -- this is purely instructional / documentation content to make future agent sessions discover and follow the patterns we now use across the distro. Changes ------- 1. `.github/instructions/comp-toml.instructions.md` (+69 lines). New "replace-upstream source override" section documenting the single-step pattern for serving a locally-modified upstream tarball under the same filename: one `[[components..source-files]]` block with `replace-upstream = true` and a `replace-reason = "..."` string. No separate `file-remove` overlay against `sources` is needed; `azldev`'s render step emits an audit WARN log naming the override and the from/to SHA-512 pair. Covers: field semantics (`filename`, `hash`, `hash-type`, `replace-upstream`, `replace-reason`, `origin.uri`); the lookaside URL pattern; the requirement that `hash` and the `$hash` segment of `origin.uri` stay in sync; the `replace-reason` style rules (single TOML string, no triple-quoted strings, self-contained motivation); and a "Public-content hygiene" subsection establishing that motivations be described technically and neutrally (no specific internal infrastructure names). Also documents the `components.toml` file-organization rule: when moving a component to a dedicated `*.comp.toml`, delete the inline entry; when removing a component, delete the inline entry. No tombstone comments. 2. `.github/skills/skill-modify-source/SKILL.md` (new, 267 lines). New skill documenting the canonical `modify_source.sh` pattern for byte-deterministic Source0 repacks. The lookaside URL embeds the SHA-512 of the served file, so the repack MUST be byte-deterministic: same upstream input ⇒ byte-identical output ⇒ identical SHA-512, across machines and re-runs. Sections: (a) Anatomy of `modify_source.sh` (scratch dir under `base/build/work/scratch//`, `BASH_SOURCE[0]` root resolution, `set -euo pipefail`). (b) The byte-deterministic repack contract -- required `tar` flags (`--sort=name`, `--owner=0 --group=0`, `--numeric-owner`, `--mtime='@'`, `--format=gnu`, `LC_ALL=C`) and `xz` flags (`-T 1`, `-9`), plus the forbidden set. (c) Mandatory upstream SHA-512 verification before extract. (d) Strip-list vs keep-list strategies with list-discipline rules. (e) Output-and-reporting contract (path, hash, ready-to-paste `az storage blob upload` command, comp.toml update reminder). (f) Determinism self-test recipe (two consecutive runs must produce byte-identical output). (g) Anti-patterns (`mktemp` for output filename, `$$` / `$RANDOM` / `$(date)` intermediates, `gzip` without `-n`, multi- threaded `xz` without `--block-size`, `find -newer`, etc.). (h) Canonical ~40-line bash skeleton showing the whole pattern end-to-end. 3. `.github/skills/skill-signing-failure-remediation/SKILL.md` (new, 98 lines). New skill documenting the decision tree for handling pipeline scan failures at the component level: (a) No reverse dependencies → drop the component outright (separate PR). (b) Offending artefact lives in a test-only / optional subpackage → use `spec-remove-subpackage` (or `build.without` if a clean bcond exists). (c) Reverse deps require the component to keep building → strip subtrees from Source0 via the `replace-upstream` pattern with a `modify_source.sh` script (see `skill-modify-source`). Always run a reverse-dependency scan before choosing (a) or (b). 4. `.github/copilot-instructions.md` (+3 lines). Three new repository-wide rules at the bottom of "Repository Hygiene Rules": 6. Commit signing -- every commit MUST be GPG-signed; `git log -1 --show-signature` must show "Good signature". 7. Public-content hygiene -- describe motivations in overlay descriptions, replace-reason strings, comp.toml comments, spec files, modify_source.sh echo lines, commit messages, and PR descriptions *technically and neutrally*. Use neutral phrasing such as "automated package-signing pipeline", "FS-aware deep scanner", "automated malware scan". 8. Component-modification order of operations -- canonical sequence is `edit comp.toml → azldev comp update -p → commit (toml + lock) → azldev comp render -p → amend (rendered spec)`. Keeps the lock fingerprint, %changelog, and Release: in sync within a single commit. 5. `.github/skills/skill-update-component/SKILL.md` (+20/-4 lines). Clarified the commit ordering with a worked example. The canonical sequence is: edit `comp.toml`, `azldev comp update -p ` to refresh the lock, commit the comp.toml + lock, `azldev comp render -p `, amend in the rendered spec. Spelled out *why* the second render-and-amend is needed (`%changelog` / `Release:` are derived from `git log`, not the working tree). 6. `AGENTS.md` (+2 lines). Added two rows to the skill index: | Byte-deterministic Source0 repack (`modify_source.sh` + `replace-upstream`) | `skill-modify-source` | | Remediate component-level signing/scan failures | `skill-signing-failure-remediation` | 7. `base/comps/AGENTS.md` (+4 lines). Mirrored the `components.toml` hygiene rule at the directory-level guide: when moving a component to a dedicated file, delete the inline entry (no tombstone comment); when removing a component, delete the inline entry. 8. `.github/copilot-instructions.md` (+1 line, follow-up). Added rule 11 to "Repository Hygiene Rules": fix render / lock drift locally by re-running `azldev comp render -p ` / `azldev comp update -p ` and amending, never by `gh run download`-ing the `rendered-specs-patch` / `locks-patch` CI artifact and `git apply`-ing it. The artifact is a reviewer convenience; using it as a fix path skips local reproduction and masks any local/CI environment skew. 9. `.github/skills/skill-update-component/SKILL.md` (+2 lines, follow-up). Added a "Drift-fix discipline" paragraph under the existing "CI gotcha" section, cross-referencing rule 11. 10. `.github/copilot-instructions.md` + `.github/instructions/comp-toml.instructions.md` (follow-up). Encode the "descriptions over comments" policy that emerged from the TOML cleanup work in this session: - Rule 1 of "Repository Hygiene Rules" reworded from "Overlay descriptions" to "Descriptions over comments". Establishes that schema-provided fields (`description`, `replace-reason`, `skip_reason`, etc.) are preferred over TOML comments, and that every rationale string MUST be brief -- one short line, motivational (the *why*), not implementation detail. - Rule 9 updated to cite `CONTRIBUTING.md` -- Pull Request Workflow as the authoritative source for the single-commit-per-PR requirement. - `comp-toml.instructions.md` section renamed from "Adding Description Comments" to "Descriptions over comments". Rewritten to lead with the structured-field preference and to give DO / DO NOT examples for both descriptions and (sparing) TOML comments; the "References" subsection is retained. 11. `.github/skills/skill-fix-overlay/SKILL.md` (follow-up). Refresh the `spec-remove-subpackage` / `spec-remove-section` failure-mode list. The engine now balances surrounding `%if` / `%endif` wrappers automatically (microsoft/azure-linux-dev-tools#190), so the "greedy consumption of `%if` guards" entry was dropped along with its manual `spec-append-lines` workaround. The two remaining failure modes — `%define` / `%global` inside the removed body vanishes (tracked in microsoft/azure-linux-dev-tools#203) and stray buildroot files — stay as-is, with a cross-link to the upstream tracking issue for the macro case. --- .github/copilot-instructions.md | 16 +- .../instructions/comp-toml.instructions.md | 129 ++++++++- .github/skills/skill-fix-overlay/SKILL.md | 23 ++ .github/skills/skill-modify-source/SKILL.md | 267 ++++++++++++++++++ .../SKILL.md | 98 +++++++ .../skills/skill-update-component/SKILL.md | 27 +- AGENTS.md | 2 + base/comps/AGENTS.md | 4 + 8 files changed, 546 insertions(+), 20 deletions(-) create mode 100644 .github/skills/skill-modify-source/SKILL.md create mode 100644 .github/skills/skill-signing-failure-remediation/SKILL.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 3c056b50f73..b3458a607b6 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -71,6 +71,14 @@ When making a PR, the lock and rendered specs must be updated and self-consisten Run all commands from the repo root (where `azldev.toml` lives). If the terminal's cwd has drifted, use `azldev -C /path/to/repo `. Use `azldev --help` and `azldev --help` for current syntax — the tool is under active development. +**Installing / updating `azldev`:** the canonical install/update command is + +```bash +go install github.com/microsoft/azure-linux-dev-tools/cmd/azldev@main +``` + +`@main` pins to the latest published source — fine for an actively-tracked dev tool. The binary lands at `$(go env GOPATH)/bin/azldev`. Verify with `azldev --version`. Re-run the same command to update; new features (e.g. project-config validation rules) land continuously, so running stale `azldev` against a fresh repo state can produce false-positive CI failures. + **Agent-friendly flags:** Use `-q` (quiet) to reduce noise and `-O json` for machine-parseable output. These are global flags and work on all commands. | Task | Command | @@ -97,8 +105,14 @@ Run all commands from the repo root (where `azldev.toml` lives). If the terminal ## Repository Hygiene Rules -1. **Overlay descriptions**: Every overlay MUST include a `description` field explaining *why* the change is needed. +1. **Descriptions over comments**: Use the schema-provided fields (`description` on overlays, `replace-reason`, `skip_reason`, etc.) instead of TOML comments. Every overlay MUST set `description`, and every rationale string MUST be brief — one short line, motivational (the *why*), not implementation detail. See [`comp-toml.instructions.md` — Descriptions over comments](instructions/comp-toml.instructions.md#descriptions-over-comments). 2. **Naming**: Component names should match the upstream package name. Use `upstream-name` when the upstream name differs (e.g., `upstream-name = "redhat-rpm-config"` for `azurelinux-rpm-config`). 3. **Schema validation**: The authoritative schema is `external/schemas/azldev.schema.json`. Do NOT add `$schema` keys to TOML files — `$` is invalid at the start of a bare TOML key. 4. **Don't edit generated output**: Build artifacts in `base/out/` and `base/build/` are generated (configured by `output-dir`, `log-dir`, `work-dir` in `base/project.toml`) — never edit them directly. Note: `prep-sources -o ` writes to a user-specified directory, separate from these project output dirs. 5. **Mandatory testing**: Any change that affects a component's output (overlays, build config, spec edits, version bumps, new components) MUST be validated by building AND testing the resulting RPMs. A successful build alone is not sufficient — smoke-test in a mock chroot. Pure organizational changes (moving definitions between files, editing descriptions/comments) do not require rebuild. See `AGENTS.md` for the full testing protocol. +6. **Commit signing**: Every commit MUST be GPG-signed (`git commit -S`, or `commit.gpgsign = true` in your git config). Verify with `git log -1 --show-signature` — the output must include a `Good signature` line. Do **not** disable signing (`--no-gpg-sign`) even for fix-branch / amend / force-push workflows; if a commit fails due to a signing-tool issue, surface the error rather than working around it. +7. **Public-content hygiene**: In committed content — overlay `description`s, `replace-reason` strings, `*.comp.toml` comments, `*.spec` files, `modify_source.sh` echo lines, commit messages, PR descriptions — describe motivations **technically and neutrally**. Do not name specific Microsoft-internal infrastructure (signing services, scanner brand names, internal pipeline names, internal CLI wrappers, internal Azure tenants, etc.). Use neutral phrasing like "automated package-signing pipeline", "FS-aware deep scanner", "automated malware scan". See [`comp-toml.instructions.md` — Public-content hygiene](instructions/comp-toml.instructions.md#public-content-hygiene) for examples. +8. **Component-modification order of operations**: For any component change, the canonical sequence is `edit comp.toml → azldev comp update -p → commit ALL working-tree changes (comp.toml + lock + anything else like source-side scripts) → azldev comp render -p → amend (rendered spec)`. The **commit between `update` and `render` is mandatory** and must include the entire working tree, not just the lock and comp.toml: `%changelog` / `Release:` are derived from `git log` for the component, and `azldev` treats any uncommitted working-tree change as a synthetic "dirty" commit. Rendering with uncommitted changes therefore produces an extra `Release:` bump and a `Local changes (uncommitted)` changelog entry that diverges from the actual commit — the `Check Rendered Specs` CI check then fails. Commit first; render after; amend the rendered spec into the same single commit. See [`skill-update-component`](skills/skill-update-component/SKILL.md). +9. **Single-commit PRs**: Per [`CONTRIBUTING.md` — Pull Request Workflow](../CONTRIBUTING.md#pull-request-workflow), each PR should land as a single commit. If you produce intermediate commits during development (e.g., separate the source override from a follow-up simplification), squash them into one GPG-signed commit before opening or marking the PR ready for review. Use `git reset --soft ` followed by `git commit -S -F ` rather than `--squash` so the commit metadata stays clean. A multi-commit PR will fail the merge bar. +10. **Reviewer-facing-only commit/PR descriptions**: Commit messages and pull-request descriptions MUST describe **only changes that are visible in the diff** to a reviewer. Do NOT include narrative about investigations, findings, side-observations, or background that did not produce any file changes — that context belongs in the working-session log, internal notes, or a follow-up issue. For example, a commit that strips one file from a tarball must not include a section about an unrelated `gzip: stdin: not in gzip format` warning the author noticed while investigating, because there's no diff for a reviewer to evaluate against it. If an investigation produced a follow-up issue or TODO, link to it from the commit message ("filed as #N for follow-up") — the link itself is the diff-visible artefact. Commit/PR descriptions are reviewer-facing artefacts that explain *what this change does*, not *what the author thought about while writing it*. See [`skill-update-component`](skills/skill-update-component/SKILL.md). +11. **Fix drift locally, never by downloading CI patch artifacts**: When the `Render + drift check` (or `Update locks + drift check`) PR check fails and posts a comment linking the `rendered-specs-patch` / `locks-patch` artifact, do NOT `gh run download` it and `git apply` the result. Re-run the relevant `azldev` command locally instead (`azldev comp render -p ` and/or `azldev comp update -p `), inspect the diff, and amend the commit with the locally-produced output. The CI patch is a convenience for human reviewers eyeballing a small change — using it as an automated fix path skips the local-reproduction step, leaves the workflow non-reproducible from a fresh clone, and masks any environment-skew bug between local `azldev` and the CI container. If local `azldev` does NOT reproduce the CI render, the fix is to update your local toolchain (`go install github.com/microsoft/azure-linux-dev-tools/cmd/azldev@main`) — **not** to bypass it. If they still disagree after that, surface the discrepancy as a bug to investigate rather than papering over it with the patch artifact. diff --git a/.github/instructions/comp-toml.instructions.md b/.github/instructions/comp-toml.instructions.md index dedbe2d55b6..ab379ded567 100644 --- a/.github/instructions/comp-toml.instructions.md +++ b/.github/instructions/comp-toml.instructions.md @@ -148,6 +148,75 @@ Optional fields that apply to multiple types: `section` (target spec section), ` - **Dedicated** (`/.comp.toml`): when overlays, build config, or local spec are needed - Rule of thumb: if it's more than `[components.]`, give it a dedicated file - `components.toml` has `includes = ["**/*.comp.toml"]` — dedicated files are picked up automatically +- **When moving a component out of inline `components.toml` into a dedicated `.comp.toml` file, DELETE the inline entry.** Don't leave a "moved to X" pointer comment. Discovery is automatic via the `**/*.comp.toml` include glob. +- **When removing a component outright, DELETE the inline entry.** No tombstone comments. + +## `replace-upstream` Source Override + +When a component must serve a **locally-modified** Source0 (or any other source) under the **same filename** as the upstream sources manifest declares, use a `[[components..source-files]]` block with `replace-upstream = true`. This swaps the same-named entry in the Fedora `sources` manifest **in place** during render — there is **no** separate `file-remove` overlay needed against the `sources` file, and no need to invent a new filename. + +```toml +[[components.examplepkg.source-files]] +filename = "examplepkg-1.2.3.tar.xz" +hash = "" +hash-type = "SHA512" +replace-upstream = true +replace-reason = "Upstream tarball ships a vendored copy of a third-party library plus a 'tests/network' tree that trip an automated package-signing pipeline's deep scanner. Repacked under the same filename with those subtrees stripped via base/comps/examplepkg/modify_source.sh; the surviving bits are byte-identical to upstream so the build is unaffected." + +[components.examplepkg.source-files.origin] +uri = "https://azltempstaginglookaside.blob.core.windows.net/repo/pkgs_modified/examplepkg/examplepkg-1.2.3.tar.xz/sha512//examplepkg-1.2.3.tar.xz" +``` + +### Required field semantics + +| Field | Notes | +|-------|-------| +| `filename` | Must match the upstream Source0/N filename exactly. That's how the override is keyed. | +| `hash` | SHA-512 of the **locally-modified** tarball. Lowercase hex. | +| `hash-type` | `"SHA512"`. | +| `replace-upstream` | `true`. Tells render to swap the same-named entry in the upstream manifest instead of appending a new one. | +| `replace-reason` | Single TOML string (basic or literal, not triple-quoted/multi-line). Must self-contain the full WHY so no TOML comment is needed in addition. See "`replace-reason` style" below. | +| `origin.uri` | Lookaside URL. The `$hash` path segment MUST be the **same SHA-512** as the `hash` field. | + +### Lookaside URL pattern + +``` +https://azltempstaginglookaside.blob.core.windows.net/repo/pkgs_modified/$pkg/$filename/$hashtype/$hash/$filename +``` + +- `$pkg` = component name (e.g., `examplepkg`) +- `$filename` = upstream Source0 filename (appears twice) +- `$hashtype` = `sha512` (lowercase in the URL even though the TOML field is `"SHA512"`) +- `$hash` = same SHA-512 as `hash`. If they ever diverge, the upload at one URL won't be findable from the other. + +### `replace-reason` style + +- A **single-line TOML string** (basic `"..."` or literal `'...'`). No triple-quoted / multi-line strings. +- Self-contained: must include *what* was changed and *why* clearly enough that no separate TOML comment is needed. +- Use **neutral, public-safe wording** for the motivation. Phrases like "automated package-signing pipeline", "FS-aware deep scanner", "automated malware scan" are fine. Do **not** name specific internal Microsoft scanners, pipelines, CLIs, tenants, or wrappers. Describe the *shape* of the bad content (what subtrees / what scanner class flagged it), not the brand of the tool. +- One short banner-style line at the very top of the `*.comp.toml` summarizing the override is fine. **Multi-paragraph header comments are not** — keep the explanation inside `replace-reason`. + +### What render emits + +Render emits an audit `WARN`-level log entry naming the override and the from/to SHA-512 pair. That is **expected and desired** — it makes overrides discoverable in render output. + +### How the modified tarball is produced + +Use a `modify_source.sh` script alongside the `*.comp.toml`. The script MUST be byte-deterministic (same input → same hash, across machines and re-runs), or the lookaside URL and the `hash` field will drift on every re-pack. The canonical pattern is documented in [`skill-modify-source`](../skills/skill-modify-source/SKILL.md). + +## Public-content hygiene + +In committed content — `*.comp.toml` files (overlay `description`, `replace-reason`, etc.), local `*.spec` files, `modify_source.sh` scripts, commit messages, PR descriptions — describe motivations **technically and neutrally**. Do not name specific Microsoft-internal infrastructure (signing services, scanner brand names, internal pipeline names, internal CLI wrappers, internal Azure tenants, etc.) by name. + +Use neutral phrasing instead: + +| Avoid | Prefer | +|-------|--------| +| Brand name of an internal signing service | "automated package-signing pipeline" | +| Brand name of an internal malware scanner | "FS-aware deep scanner", "automated malware scan" | +| Names of internal CI/CD pipelines or wrappers | "the build pipeline", or just `azldev` for tool references | + +The technical *what* (the shape of the content that's being changed, the class of scanner that flagged it, the nature of the false positive) belongs in the description. The internal *brand* does not. ## Build Configuration @@ -165,6 +234,21 @@ with = ["feature_x"] without = ["plugin_rhsm"] ``` +### Build-flag overrides: don't duplicate distro-level disablement + +Several build flags are applied across many components via shared **disablement groups** rather than per-component `[components..build]` blocks. Before adding a `build.without` / `build.with` / `build.defines` override to a `*.comp.toml`, check whether the same flag is already applied at one of these layers — duplicating it in the per-component file is redundant and creates two sources of truth that can drift. + +Check these layers, in order: + +1. **`base/comps/component-mingw-disablement.toml`** — applies `build.without = ["mingw"]` to every component listed under `[component-groups.mingw-disabled]` via the group's `default-component-config.build` block. Azure Linux does not ship mingw cross-compilation toolchains; any component whose upstream spec has a `mingw` bcond should be added to that list, **not** carry its own `without = ["mingw"]`. +2. **`base/comps/component-check-disablement.toml`** — applies `build.check = { skip = true, ... }` to components listed under `[component-groups.check-skip-initial-failures]` (initial-bringup `%check` failures). Don't duplicate the check skip in a per-component file. +3. **Any other `component-*-disablement.toml`** under `base/comps/` — the pattern is `[component-groups..default-component-config.build]`. +4. **`distro/azurelinux.distro.toml`** — `[distros.azurelinux.versions.''.default-component-config]` blocks set distro-wide defaults that every component inherits unless explicitly overridden. + +If the desired build flag is already applied at any of those layers, **do not duplicate it in the per-component file**. If you're moving a component from an inline entry in `components.toml` into a dedicated `.comp.toml` and the original inline entry had no `build` block, the dedicated file should also not have one — let the group / distro defaults apply. + +Conversely, only add a per-component `[components..build]` block when the flag genuinely diverges from what the disablement groups and distro defaults already provide (e.g., a `with` that isn't shared by other components, a `defines` specific to this package). + ## Release Configuration By default (`release.calculation = "auto"`), `azldev` auto-calculates the `Release` tag during rendering. There are four modes: @@ -245,40 +329,59 @@ skip-file-filter = true `skip-file-filter` disables post-render file filtering. During rendering, `azldev` normally prunes source/patch files not referenced by the rendered spec. If a spec uses unexpandable macros in `Source` or `Patch` tags (e.g., `Source0: %{name}-%{version}%{?prerelease}.tar.gz`), the filter can't resolve the filename and may incorrectly remove needed files. Setting `skip-file-filter = true` keeps all files. The tool auto-detects unexpandable macros and handles them correctly in the vast majority of cases - this is an edge case escape hatch that should almost never be needed in practice. -## Adding Description Comments +## Descriptions over comments -When adding or modifying fields in a `.comp.toml` file, check the schema (`external/schemas/azldev.schema.json`) for a `description` on that field. If the schema field **does not** have a `description` key (or equivalent, i.e. `skip_reason` for check skip), add a TOML comment above the field explaining what it does and why it's set to that value. This helps future readers (human and agent) understand fields that aren't self-documenting via the schema. +Several `.comp.toml` fields carry a free-form rationale string per the schema (`external/schemas/azldev.schema.json`): overlays take `description`, `replace-upstream` takes `replace-reason`, check skips take `skip_reason`, and so on. **Prefer those structured fields over TOML comments** — they're schema-validated, greppable, and surfaced in render diagnostics. -**BUT**... don't add pointless noise - if the change is self-explanatory and requires no additional context, a comment may not be necessary. Use your judgement, but when in doubt, add a comment. +**Keep every rationale brief.** State *why* the change is needed in one short line. Do not narrate the implementation — the field body shows the *what*, the description supplies the *why*. A reviewer should be able to skim the description list and form a high-level picture of the divergence without reading code. DO: ```toml -# Pin to Fedora rawhide since we need feature 'x' that is not in stable releases yet. -# Re-align to a stable release once the feature is backported or a new release is available. -spec = { type = "upstream", upstream-distro = { name = "fedora", version = "rawhide" } } +[[components.mypackage.overlays]] +type = "spec-remove-subpackage" +description = "Drop -doc subpackage; conflicts with system docs." +name = "doc" ``` -Offers important context about *why* the component is pinned to rawhide, which is not obvious from the TOML field alone. Future readers will understand the rationale and know to check for backports or new releases to remove the rawhide pin. - DO NOT: ```toml -# Need new stuff. +[[components.mypackage.overlays]] +type = "spec-remove-subpackage" +description = "Remove the -doc subpackage using spec-remove-subpackage, which deletes the matching %package, %description and %files blocks; this is needed because the docs conflict with the system-wide ones." +name = "doc" +``` + +### When TOML comments are appropriate + +Avoid TOML comments by default. They aren't schema-validated, aren't surfaced anywhere except in the raw file, and drift over time. Reach for them only when no schema field can carry the rationale — e.g., justifying a non-default `upstream-distro` pin, or attaching a URL to a one-off block. Keep them as brief as a description: one short line, plus an optional URL. + +DO: + +```toml +# Pinned to rawhide — needs feature X (not yet in stable). spec = { type = "upstream", upstream-distro = { name = "fedora", version = "rawhide" } } ``` -This offers no real information beyond what the TOML field already says, and doesn't explain *why* the new stuff is needed, or what that new stuff is. It's just noise. +DO NOT: + +```toml +# This component depends on feature X from upstream which was added in rawhide +# but has not yet been backported to a stable Fedora release. Once the feature +# is available in a stable release (Fedora 42 or later expected), we should +# re-align this pin. +spec = { type = "upstream", upstream-distro = { name = "fedora", version = "rawhide" } } +``` ### References -When making changes based on external information (e.g. a bug report, an upstream commit, a changelog entry, etc.), include a link to the relevant source in a comment (prefer full URL for ease of use). This provides valuable context for future readers to understand the reasoning behind the change and investigate further if needed. It also makes it easier to determine if the change is still relevant or if there have been updates since. +When a change is driven by an external source (upstream bug, commit, changelog entry), include the URL — inside the `description` if it fits, otherwise as a single-line comment above the field. Prefer full URLs for clickability. ```toml -# ... # Fixed upstream in my-package-1.2.3 (rawhide): https://src.fedoraproject.org/rpms/my-package/c/abcdef123456 (rhbz#1234567) [[components.mypackage.overlays]] -description = "Fix build failure due to missing dependency that was added in my-package-1.2.3" +description = "Fix build failure from missing dep added in my-package-1.2.3." ... ``` diff --git a/.github/skills/skill-fix-overlay/SKILL.md b/.github/skills/skill-fix-overlay/SKILL.md index 70f801255d8..6eb30c90a9c 100644 --- a/.github/skills/skill-fix-overlay/SKILL.md +++ b/.github/skills/skill-fix-overlay/SKILL.md @@ -55,6 +55,29 @@ The regex doesn't match anything in the spec. Causes: The spec section (`%prep`, `%build`, `%install`, etc.) doesn't exist or has different casing. Check the actual section names in `/.spec`. +### `spec-append-lines` / `spec-prepend-lines`: lines land on the wrong section + +Multi-section types like `%files`, `%description`, `%package` appear MANY times in a typical spec (one per subpackage). An unscoped `section = "%files"` append silently lands on the FIRST `%files` (often the unnamed main `%files`) — not on the section you visually wrote it "next to". Always scope by `package = ""` (or `package = ""` for the unnamed/main section if that's what you really want) when targeting `%files` / `%description` / `%package`. + +Example — the wrong way (lands on the unnamed main `%files`): +```toml +{ type = "spec-append-lines", section = "%files", lines = ["%if %{have_libblkio}"] } +``` +The right way (lands at the end of `%files common`, which is the desired anchor): +```toml +{ type = "spec-append-lines", section = "%files", package = "common", lines = ["%if %{have_libblkio}"] } +``` + +### `spec-remove-subpackage` / `spec-remove-section`: vanished macros & stray buildroot files + +`spec-remove-subpackage` (and `spec-remove-section` for `%description` / `%files`) walks forward from the matched directive to the *next* section directive and deletes everything in between. The engine balances `%if` / `%endif` wrappers around the removed section automatically, but does not look inside the removed body, so two failure modes remain: + +1. **`%define` / `%global` inside the removed body vanishes.** If a macro is `%define`d inside the removed `%package` body but referenced from `%install` or `%build`, the reference becomes undefined after the removal. Hoist an equivalent declaration to the top of the spec via `spec-search-replace` (anchor on an existing `%global ...` line you can find). Tracked in [`microsoft/azure-linux-dev-tools#203`](https://github.com/microsoft/azure-linux-dev-tools/issues/203). + +2. **Stray buildroot files.** If `%install` writes files that the removed `%files ` used to claim, RPM fails with "Installed (but unpackaged) file(s)". Either edit `%install` to gate the writes (`spec-search-replace`) or append `rm -rf` / `rm -f` lines to `%install` (`spec-append-lines`) that delete the orphaned files from `%{buildroot}`. + +See `microsoft/azurelinux#17212` for a worked example. + ### `file-*`: file not found The file doesn't exist in the upstream sources. Check `ls /` for actual filenames. Globs (`**/*`) are supported for `file-search-replace`. diff --git a/.github/skills/skill-modify-source/SKILL.md b/.github/skills/skill-modify-source/SKILL.md new file mode 100644 index 00000000000..a32687da963 --- /dev/null +++ b/.github/skills/skill-modify-source/SKILL.md @@ -0,0 +1,267 @@ +--- +name: skill-modify-source +description: "[Skill] Author `modify_source.sh` scripts that byte-deterministically repack a locally-modified Source0 tarball for use with a `replace-upstream` source override in `comp.toml`. Use when stripping subtrees from an upstream tarball under the same filename so that re-runs and rebuilds across machines produce identical SHA-512 hashes. Triggers: modify_source.sh, replace-upstream, deterministic tarball repack, locally-modified Source0, reproducible build inputs, lookaside hash drift, tarball repack." +--- + +# Reproducible Source-Tarball Modification (`modify_source.sh`) + +## Why this matters + +`replace-upstream` source overrides in `*.comp.toml` (see [`comp-toml.instructions.md`](../../instructions/comp-toml.instructions.md#replace-upstream-source-override)) serve a locally-modified tarball from a lookaside URL whose path **embeds the SHA-512 of the served file**: + +``` +https://azltempstaginglookaside.blob.core.windows.net/repo/pkgs_modified/$pkg/$filename/sha512/$hash/$filename +``` + +If the repack script is not byte-deterministic, every re-run produces a new hash. The `hash` field in `comp.toml` and the `$hash` segment in `origin.uri` drift apart, the lookaside URL fails to resolve, and `Check Rendered Specs` / build CI break. The whole pattern hinges on reproducibility: **same upstream input → same byte-identical output → same SHA-512**, across machines and across time. + +## (a) Anatomy of `modify_source.sh` + +- **Lives at** `base/comps//modify_source.sh`, alongside its `.comp.toml`. +- **Locates the repo root from its own location** using `BASH_SOURCE[0]`, so it can be invoked from any working directory as `bash base/comps//modify_source.sh` (or just `bash modify_source.sh` from the component directory). +- **Writes all intermediates and the output tarball** under `base/build/work/scratch//`. Never use `/tmp` or a bare `mktemp -d` (see [`AGENTS.md`](../../../AGENTS.md#conventions)). +- **Caches the verified upstream tarball** in that scratch directory so re-runs skip the download (the determinism self-test relies on the cache being preserved while the modified output is removed). +- **Starts with `set -euo pipefail`** for fail-fast semantics — any unset variable, failed command, or broken pipe aborts immediately. + +## (b) The byte-deterministic repack contract + +"Deterministic" here means: **same upstream input ⇒ same byte-identical output ⇒ same SHA-512**, across machines and across re-runs. Zero variation from host time, uid/gid, locale, thread scheduling, or filesystem iteration order. + +### Required `tar` flags + +| Flag | Why | +|------|-----| +| `--sort=name` | Stable entry order regardless of filesystem iteration order | +| `--owner=0 --group=0` | No host uid/gid leakage | +| `--numeric-owner` | Force numeric ids, ignore `/etc/passwd` mapping differences | +| `--mtime='@1577836800'` | Fixed UTC epoch for every entry. Convention: `@1577836800` (2020-01-01 UTC). Any fixed epoch works, but pick one and document it. | +| `--format=gnu` | Deterministic long-path handling; pax format adds variable-length headers | + +Always export `LC_ALL=C` before invoking `tar` so `--sort=name` uses locale-independent collation. + +### Required `xz` flags + +| Flag | Why | +|------|-----| +| `-T 1` | **Single-threaded only.** Multi-threaded xz splits the stream into non-reproducible blocks unless `--block-size` is also pinned. For tarballs of typical source-package size the throughput gain is marginal — not worth the determinism risk. | +| `-9` | Pin the compression level so re-runs produce identical compressed bytes (default level may vary across xz versions). | + +### Forbidden + +- `tar --gzip` / `tar -cz` — gzip embeds the mtime in its header unless you also pass `-n` to gzip explicitly; cleaner to drive `xz` ourselves through a pipe. +- `xz -T 0` or `xz -T ` without a pinned `--block-size`. +- `mktemp` (or any `$$`, `$RANDOM`, or `date`-derived filename) for the **output** tarball or any intermediate that ends up inside it. The whole scratch dir is ephemeral; the output filename must be fixed. +- `find ... -newer ...` for selection — host clock leaks into what gets packed. +- Any direct call to `gzip` without `-n`. + +## (c) Verify the upstream tarball before extracting + +Always SHA-512-check the upstream tarball before extracting and re-packing: + +```bash +echo "${UPSTREAM_SHA512} ${UPSTREAM_PATH}" | sha512sum --check --status \ + || { echo "ERROR: upstream SHA-512 mismatch — upstream may have re-tagged" >&2; exit 1; } +``` + +Upstream re-tagging (publishing different bytes under the same version) is rare but real. The script must fail loudly rather than silently re-pack different content under the same filename. + +## (d) Strip-list vs keep-list — pick the one that audits better + +Both strategies are equally valid. Use the one whose list is shorter and easier to review. + +### Strip-list (deletion-based) + +Extract the full tarball, then `rm -rf` the offending subtrees. Best when the bad content is a small fraction of the tarball. + +```bash +STRIP_PATHS=( + docs + tests/network + third_party/vendored-bigblob +) + +for p in "${STRIP_PATHS[@]}"; do + rm -rf -- "${EXTRACT_DIR}/${p}" +done +``` + +### Keep-list (allowlist-based) + +Extract everything, then delete everything except an explicit list of paths to keep. Best when the bad content is most of the tarball, or when the keep-list is easier to audit. Canonical example: a vendored JS engine that lives inside a much larger browser monorepo, where keeping only `js/src/` plus a handful of build-tooling paths is dramatically easier to review than enumerating everything to strip. + +```bash +KEEP_PATHS=( + js/src + build + config + python + taskcluster +) + +# Move keep paths into a staging dir, wipe the rest, move them back. +mkdir -p "${EXTRACT_DIR}.keep" +for p in "${KEEP_PATHS[@]}"; do + src="${EXTRACT_DIR}/${p}" + [[ -e "${src}" ]] || { echo "ERROR: keep-path missing: ${p}" >&2; exit 1; } + mkdir -p "$(dirname "${EXTRACT_DIR}.keep/${p}")" + mv "${src}" "${EXTRACT_DIR}.keep/${p}" +done +rm -rf "${EXTRACT_DIR}" +mv "${EXTRACT_DIR}.keep" "${EXTRACT_DIR}" +``` + +### List discipline (both strategies) + +- Lists live in the script as **data**, not as code: a bash array (one item per line, trailing comma optional) or a here-doc that's read into an array. Keep the list dense and auditable. +- **No inline commentary** mid-list. If a single entry needs context, put a one-line comment immediately above it, not on the same line. +- Sort the list. Sorted lists are easier to diff and to review. +- Validate that each keep-path actually exists in the extracted tree. If a keep-path is missing, abort — upstream restructured and the list is stale. + +## (e) Output and reporting + +On success the script prints, in order: + +1. The absolute path to the modified tarball under `base/build/work/scratch//`. +2. The SHA-512 of that file. +3. A ready-to-paste `az storage blob upload` command targeting the lookaside path `pkgs_modified/$pkg/$filename/sha512/$hash/$filename`. +4. A short reminder that **both** the `hash` field **and** the `$hash` segment of `origin.uri` in `.comp.toml` must be updated to the new SHA-512. + +Example tail of `modify_source.sh`: + +```bash +out_hash=$(sha512sum "${OUTPUT_TARBALL}" | awk '{print $1}') + +cat </modify_source.sh +sha512sum base/build/work/scratch// + +# Remove the OUTPUT only — keep the upstream cache so the second run +# exercises the same extract+repack path. +rm base/build/work/scratch// + +bash base/comps//modify_source.sh +sha512sum base/build/work/scratch// # MUST match the first sum +``` + +If the two hashes don't match, the script has a determinism bug — diagnose and fix before merging. Common culprits: + +- Multi-threaded `xz` without a pinned block size. +- A `tar` flag missing from the required set in (b). +- `LC_ALL=C` not exported in the shell that invokes `tar`. +- A leftover `mktemp`, `$RANDOM`, `$$`, or `$(date ...)` somewhere in the pipeline. +- Host-time leak via `find -newer`, `touch`, or implicit mtime preservation during the strip step. + +## (g) Anti-patterns + +- ❌ `mktemp` for the output filename. The output filename is fixed; only the scratch dir is ephemeral. +- ❌ `$$` or `$RANDOM` anywhere in an intermediate path that ends up inside the tarball. +- ❌ `$(date ...)` in any filename or as a tar `--mtime` value. +- ❌ `gzip` without `-n`, or `tar --gzip` / `tar -cz`. Use `xz` driven through a pipe. +- ❌ `xz -T 0` (or any `-T N` > 1) without `--block-size` pinned. +- ❌ `find ... -newer ...` to select what to pack — host clock leaks in. +- ❌ Tar without `--sort=name`, `--owner=0 --group=0 --numeric-owner`, `--mtime`, or `--format=gnu`. +- ❌ Forgetting `LC_ALL=C` — sort collation becomes locale-dependent and the same input produces different orderings on different hosts. + +## (h) Canonical skeleton + +The full pattern, end-to-end, using the strip-list strategy. The keep-list variant swaps only the strip section (see (d)); everything else is identical. + +```bash +#!/usr/bin/env bash +# Repack upstream Source0 with local modifications under the same filename. +# Produces a byte-deterministic tarball; SHA-512 is stable across machines and re-runs. +set -euo pipefail + +# Resolve repo root from the script's own location: base/comps//modify_source.sh +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)" + +PKG="examplepkg" +UPSTREAM_FILENAME="examplepkg-1.2.3.tar.xz" +UPSTREAM_URL="https://upstream.example.org/${UPSTREAM_FILENAME}" +UPSTREAM_SHA512="" + +OUTPUT_FILENAME="${UPSTREAM_FILENAME}" # same name as upstream; replace-upstream swaps in place +SCRATCH_DIR="${REPO_ROOT}/base/build/work/scratch/${PKG}" +UPSTREAM_PATH="${SCRATCH_DIR}/${UPSTREAM_FILENAME}" +EXTRACT_DIR="${SCRATCH_DIR}/extract" +OUTPUT_TARBALL="${SCRATCH_DIR}/${OUTPUT_FILENAME}" + +STRIP_PATHS=( + docs + tests + third_party/vendored-bigblob +) + +mkdir -p "${SCRATCH_DIR}" + +# 1. Fetch (cache across runs) and verify upstream. +[[ -f "${UPSTREAM_PATH}" ]] || curl -fSL -o "${UPSTREAM_PATH}" "${UPSTREAM_URL}" +echo "${UPSTREAM_SHA512} ${UPSTREAM_PATH}" | sha512sum --check --status \ + || { echo "ERROR: upstream SHA-512 mismatch — upstream may have re-tagged" >&2; exit 1; } + +# 2. Extract fresh. +rm -rf "${EXTRACT_DIR}" +mkdir -p "${EXTRACT_DIR}" +tar -xf "${UPSTREAM_PATH}" -C "${EXTRACT_DIR}" + +# 3. Strip. +for p in "${STRIP_PATHS[@]}"; do + rm -rf -- "${EXTRACT_DIR:?}/${p}" +done + +# 4. Repack deterministically. +rm -f "${OUTPUT_TARBALL}" +( cd "${EXTRACT_DIR}" && LC_ALL=C tar \ + --sort=name \ + --owner=0 --group=0 --numeric-owner \ + --mtime='@1577836800' \ + --format=gnu \ + -cf - . \ + | xz -T 1 -9 ) > "${OUTPUT_TARBALL}" + +# 5. Report. +out_hash=$(sha512sum "${OUTPUT_TARBALL}" | awk '{print $1}') +cat < with the component name and with the sub-package suffix (or just alone). +grep -rn '-\|\b' base/comps/ specs/ base/images/ 2>/dev/null | head -50 +``` + +Look for: + +- `Requires:` / `BuildRequires:` references in other components' specs or comp.toml overlays. +- Image manifest inclusions (`base/images/**`). +- References in `base/comps/*/components.toml` or any other comp.toml as a dependency. + +If any reverse-dep exists, path (a) or (b) is **not safe** — fall through to (c). + +For multi-component or whole-suite drops, see [`skill-remove-component`](../skill-remove-component/SKILL.md). + +## Validation requirements per path + +The [`AGENTS.md` Mandatory Testing](../../../AGENTS.md#mandatory-testing) protocol always applies, but each path has different specifics: + +| Path | What to validate | Build required? | +|------|------------------|-----------------| +| (a) Drop component | Image builds still resolve (`azldev image build` for affected images) | No rebuild of the dropped component (it's gone); rebuild of any image that referenced it | +| (b) Drop sub-package | `rpmspec -P specs///.spec` shows the sub-package is gone; for test-only sub-packages, no rebuild is strictly required if nothing else changed. Note explicitly in the PR what alternative validation was performed. | Often no — but build + smoke-test the main package if you're unsure | +| (c) Strip subtrees | Full build + smoke-test of the modified component AND of at least one direct reverse-dep, to confirm the stripped subtrees weren't actually needed | Yes | + +When a change does **not** require a full rebuild (e.g., dropping a test-only sub-package with no consumers), the PR description must say so explicitly and list what was validated instead (`rpmspec -P` output, reverse-dep scan, etc.). Don't silently skip the test step. + +## Public-content hygiene + +Across `comp.toml` `description` / `replace-reason` fields, `modify_source.sh` echo lines, commit messages, and PR descriptions: describe motivations **technically and neutrally**. See [`comp-toml.instructions.md` — Public-content hygiene](../../instructions/comp-toml.instructions.md#public-content-hygiene) for the wording rules. Briefly: don't name specific internal Microsoft scanners, pipelines, CLIs, or wrappers; describe the *class* of tool and the *shape* of the flagged content instead. + +## Related + +- [`skill-modify-source`](../skill-modify-source/SKILL.md) — byte-deterministic repack for path (c). +- [`skill-fix-overlay`](../skill-fix-overlay/SKILL.md) — overlay-level debugging when path (b) is implemented as a `spec-remove-subpackage` overlay. +- [`skill-remove-component`](../skill-remove-component/SKILL.md) — mechanical workflow for path (a). +- [`comp-toml.instructions.md` — `replace-upstream`](../../instructions/comp-toml.instructions.md#replace-upstream-source-override) — comp.toml syntax for path (c). diff --git a/.github/skills/skill-update-component/SKILL.md b/.github/skills/skill-update-component/SKILL.md index daf489651fb..0f31b03a60b 100644 --- a/.github/skills/skill-update-component/SKILL.md +++ b/.github/skills/skill-update-component/SKILL.md @@ -20,20 +20,33 @@ description: "[Skill] Refresh component lock files with `azldev comp update`. Us ## End-of-work refresh (the common case) -For most component edits — overlays, build flags, metadata, descriptions — run `update` once at the end, then re-render *after committing* so the changelog and release reflect your new commit. +For most component edits — overlays, build flags, metadata, descriptions — the canonical order is: + +1. Edit `comp.toml` (and any source-side files). +2. `azldev comp update -p ` to refresh the lock. +3. **Commit ALL working-tree changes** — `comp.toml`, `lock`, and any source-side scripts (e.g., `modify_source.sh`). No rendered spec yet. The commit between `update` and `render` is mandatory and must include the entire working tree, not just `comp.toml` + lock. Keep the commit message scoped to what's in the diff (rule 10 of [`copilot-instructions.md`](../../copilot-instructions.md)) — no investigation narrative for things that didn't produce a file change. +4. `azldev comp render -p ` so `%changelog` / `Release:` track the just-made commit. +5. Amend the rendered spec into the same commit (preferred), or commit it separately. ```bash +# 1-2. Edit and refresh lock. azldev comp update -p -azldev comp render -p -git add base/comps// locks/.lock specs/// -git commit -m "fix(pkg): Fix bug" -# Now re-render and amend so %changelog / Release: track the new commit. +# 3. Commit the input change (ALL working-tree changes — comp.toml, +# lock, and any source-side scripts like modify_source.sh) first. +git add -A +git commit -S -m "fix(pkg): Fix bug" + +# 4. Render against the just-committed history. azldev comp render -p + +# 5. Fold the rendered spec into the same commit. git add specs/// -git commit --amend --no-edit +git commit --amend -S --no-edit ``` +Commit **all working-tree changes together first**, then render against that committed history, then amend in the rendered spec. If you skip the commit and run `render` with a dirty working tree, `azldev` treats the dirty state as a synthetic commit, the rendered `%changelog` adds a `- Local changes (uncommitted)` entry, and the `Release:` integer bumps by one extra — both of which diverge from the actual commit and fail the `Check Rendered Specs` CI check. + ### Why the second render-and-amend? > **`%changelog` and `Release:` are derived from `git log` for the component, not from the working tree.** rpmautospec walks the commit history every time it renders. The first render happens before your commit exists, so it produces a spec keyed off `HEAD`. The moment you `git commit`, the rendered output drifts — a fresh render will add a new `%changelog` entry for your commit and bump the `Release:` integer. Without the post-commit re-render and amend, the `Check Rendered Specs` CI check will fail with that exact diff. @@ -103,6 +116,8 @@ update → render → iterate → commit lock → render → amend `Check Rendered Specs` and `Update Locks` both run against the **PR's committed state**, not the working tree. The end-of-work refresh (or the amend in step 6 of a pin bump) is what keeps both checks green together — without it, render check would flag a stale changelog or lock check would flag a stale fingerprint. +**Drift-fix discipline:** When either check fails and posts a comment linking the `rendered-specs-patch` / `locks-patch` artifact, re-run `azldev comp render -p ` / `azldev comp update -p ` locally and amend with the local output. Do NOT `gh run download` the patch and `git apply` it — that skips local reproduction and hides any environment-skew bug between your `azldev` and the CI container. If the local render disagrees with CI, the fix is to upgrade your local toolchain (`go install github.com/microsoft/azure-linux-dev-tools/cmd/azldev@main`), not to bypass it. See rule 11 in [`copilot-instructions.md`](../../copilot-instructions.md#repository-hygiene-rules). + ## When to use `-a` Wholesale lock refresh is slow. Use it only when: diff --git a/AGENTS.md b/AGENTS.md index 529cc846e76..e8beeb30bdf 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -58,6 +58,8 @@ Detailed workflows live in skills (loaded on-demand when relevant): | Add a new component to the distro | [`skill-add-component`](.github/skills/skill-add-component/SKILL.md) | | Remove component(s) from the distro | [`skill-remove-component`](.github/skills/skill-remove-component/SKILL.md) | | Diagnose and fix overlay issues | [`skill-fix-overlay`](.github/skills/skill-fix-overlay/SKILL.md) | +| Byte-deterministic Source0 repack (`modify_source.sh` + `replace-upstream`) | [`skill-modify-source`](.github/skills/skill-modify-source/SKILL.md) | +| Remediate component-level signing/scan failures | [`skill-signing-failure-remediation`](.github/skills/skill-signing-failure-remediation/SKILL.md) | | Test and inspect packages in mock chroot | [`skill-mock`](.github/skills/skill-mock/SKILL.md) | | Review component for hygiene and best practices | [`skill-review-component`](.github/skills/skill-review-component/SKILL.md) | | Triage Koji build failures | [`skill-koji-triage`](.github/skills/skill-koji-triage/SKILL.md) | diff --git a/base/comps/AGENTS.md b/base/comps/AGENTS.md index 20f79a12e72..6209c553ce8 100644 --- a/base/comps/AGENTS.md +++ b/base/comps/AGENTS.md @@ -4,6 +4,8 @@ - Non-trivial components (overlays, build config) SHOULD have their own `/.comp.toml`. Simple imports stay inline in `components.toml`. - `components.toml` uses `includes` to pull in all `**/*.comp.toml` — no manual wiring needed. +- **When moving a component out of inline `components.toml` into a dedicated `/.comp.toml`, DELETE the inline entry.** Do **not** leave a "moved to X" pointer or any other tombstone comment behind. Discovery is automatic via the `**/*.comp.toml` include glob. +- **When removing a component outright, DELETE the inline entry.** Don't leave a TODO/removed-from-here comment — git history is the record. - Canonical example: [`rpm/rpm.comp.toml`](rpm/rpm.comp.toml) — uses modern `[[...overlays]]` syntax with `description` fields. For a larger overlay example (legacy inline syntax, not to be copied for new work), see [`azurelinux-rpm-config/azurelinux-rpm-config.comp.toml`](azurelinux-rpm-config/azurelinux-rpm-config.comp.toml). - Schema: [`azldev.schema.json`](../../external/schemas/azldev.schema.json) @@ -12,5 +14,7 @@ - Adding a new component → [`skill-add-component`](../../.github/skills/skill-add-component/SKILL.md) - Removing component(s) → [`skill-remove-component`](../../.github/skills/skill-remove-component/SKILL.md) - Fixing overlay issues → [`skill-fix-overlay`](../../.github/skills/skill-fix-overlay/SKILL.md) +- Byte-deterministic Source0 repack (`modify_source.sh` + `replace-upstream`) → [`skill-modify-source`](../../.github/skills/skill-modify-source/SKILL.md) +- Remediating component-level signing/scan failures → [`skill-signing-failure-remediation`](../../.github/skills/skill-signing-failure-remediation/SKILL.md) - Building and debugging → [`skill-build-component`](../../.github/skills/skill-build-component/SKILL.md) - Testing in mock chroot → [`skill-mock`](../../.github/skills/skill-mock/SKILL.md)