summaryrefslogtreecommitdiff
path: root/.github/workflows
AgeCommit message (Collapse)Author
2026-06-03T8960: ai-validation β€” mint vyos-bot token via get-token@productionYuriy Andamasov
Swap the cross-repo-checkout token from the dedicated vyos-docs-reviewer App (VYOS_APP_ID/_PRIVATE_KEY) to the shared vyos-bot App via the fleet get-token composite, scoped contents:read. Skip-check now gates on APP_CLIENT_ID + APP_PRIVATE_KEY + ANTHROPIC_API_KEY (org-level vyos-bot creds); reword the stale token comment. PR-comment posting still uses the default GITHUB_TOKEN. Byte-identical to the paired reference copy in VyOS-Networks/vyos-docs-opus-reviewer (scripts/ai-validation.yml). πŸ€– Generated by [robots](https://vyos.io) (cherry picked from commit 459109f13adf026d012c4e803f4a86a8f84e6ce3)
2026-05-29ci(ai-validation): allow Pass 2 review on external-contributor PRsYuriy Andamasov
The upstream `anthropics/claude-code-action` performs a write-permission check on `github.actor` before our skip logic runs. On `pull_request_target` the actor is the PR author; external contributors resolve to `read` and the action exits 1 with `Actor does not have write permissions to the repository`. Net effect: AI validation has been failing on every external-contributor PR (LiudmylaNad, teslazonda, scottlaird in the last 4 weeks) while succeeding on maintainer PRs. Failure reproduced on run 26541079685 (PR #2061). Fix: set `allowed_non_write_users: '*'` on the Pass 2 step. The action bypasses the actor check when this input is set and `github_token` is provided (already the case). The action also auto-scrubs Anthropic / cloud / GHA secrets from subprocess envs when this input is set. Safe in THIS workflow because the existing defense-in-depth bounds what Pass 2 can do with untrusted PR content: - `allowedTools` restricted to inline-comment + read-only surfaces - `github_token` is the PR-scoped default (not the broader VYOS_APP_ID) - prompt marks PR content as untrusted via `<UNTRUSTED-PR-CONTENT>` - workspace-wipe removes `CLAUDE.md` / `.claude/` before Pass 2 - prepare bundles MD via `git show HEAD:<path>` (blob, not `cp`) Full rationale inlined as a comment block above the new input. πŸ€– Generated by [robots](https://vyos.io) (cherry picked from commit 1fa39bd7ac64898f7a21922bc0f195d115d3cdeb)
2026-05-14Merge pull request #2042 from vyos/mergify/bp/sagitta/pr-2023Yuriy Andamasov
ci(doc-linter): fix 12 accumulated bugs flagged across PR #2014/#2019/#2020 reviews (backport #2023)
2026-05-14ci(ai-validation): skip prepare on Mergify-authored PRsYuriy Andamasov
Lifts the existing Mergify-author short-circuit (today inside validate's `secrets-check` step) to a job-level `if:` on `prepare`, so the whole pipeline skips for backport/queue PRs. Why now: every Mergify backport whose merge ref shares no shallow ancestor with the (advanced) base branch fails the prepare step at git diff "$BASE...HEAD" --name-only ... fatal: FETCH_HEAD...HEAD: no merge base (because base is `git fetch --no-tags --depth=1` and the merge ref is `fetch-depth: 2`). Proximate symptom: run 25842928620 on PR #2042 (sagitta backport of #2023). AI Validation isn't a required check so the queue isn't blocked, but every Mergify backport is left with a red "prepare" check that adds noise to PR review. The validate-level skip in commit 0e8a2956 was correct for the "claude-code-action rejects bot-initiated runs" failure mode but fires too late β€” prepare has already run and crashed before validate's `if: needs.prepare.outputs.has_md_changes == 'true'` even evaluates. Implementation: single job-level `if:` on prepare. validate's `needs: [prepare]` cascades the skip naturally (skipped needs make the dependent's expression-based `if:` evaluate against empty outputs). The in-step author check in validate stays as defense-in-depth. πŸ€– Generated by [robots](https://vyos.io) (cherry picked from commit 7d94d6116be1a4776b7317cb5190a83dd065e571)
2026-05-14ci(lint-doc): pin all GitHub Actions to commit SHAsYuriy Andamasov
Each `uses:` line was pinned to a mutable version tag (`@v6`, `@v0.8.4`, …). Tags can be rewritten to point to malicious code β€” CVE-2025-30066 (reviewdog/action-setup) and the tj-actions/changed-files incident in 2025 are the canonical real-world examples. GitHub's hardening guide for Actions recommends pinning to full-length commit SHAs and keeping the tag as a trailing comment for human readability. Resolved each action's tag to its commit SHA via `gh api /repos/<repo>/git/refs/tags/<tag>` and verified the SHA is a commit (not an annotated-tag object) via `gh api /repos/<repo>/git/commits/<sha>`: - actions/checkout v6 -> de0fac2e4500dabe0009e67214ff5f5447ce83dd - bullfrogsec/bullfrog v0.8.4 -> 1831f79cce8ad602eef14d2163873f27081ebfb3 - trilom/file-changes-action v1.2.4 -> a6ca26c14274c33b15e6499323aac178af06ad4b - actions/setup-python v6 -> a309ff8b426b58ec0e2a45f0f869d46889d02405 This change covers `lint-doc.yml` only. A fleet-wide sweep across every workflow in `.github/workflows/` is a separate effort β€” worth doing because the drift / supply-chain risk is the same in every one. Tracked as a follow-up to this PR's review. Tracked as item 11 of the rolling-side cleanup backlog from PR #2014 / #2019 / #2020 reviews. πŸ€– Generated by [robots](https://vyos.io) (cherry picked from commit e99182b911dbe9d3a3f02e000426f7075cadc608)
2026-05-14ci(doc-linter): lint added + renamed files, not only modifiedYuriy Andamasov
Previous workflow: env: FILES_MODIFIED: ${{ steps.file_changes.outputs.files_modified }} run: python scripts/doc-linter.py "$FILES_MODIFIED" `trilom/file-changes-action`'s `files_modified` output is modifications-only. A PR adding a new `.md`/`.rst` doc page passed `files_added`, never `files_modified`, so a brand-new page with long lines or real public IPs slipped past the linter entirely. Workflow: also pass `files_added` and `files_renamed` as separate positional args. Each output is a JSON array (action v1.2.4) and is passed via env to avoid shell-quoting issues. Linter: `main()` now accepts one OR multiple positional argv entries, each a JSON array of paths. Arrays are merged and deduplicated before linting. Single-arg invocations remain backward-compatible. Switched from `ast.literal_eval` to `json.loads` β€” the action's outputs are JSON, and `json.loads` is the right tool (and dodges literal_eval-via-`eval`-substring linter warnings). Test coverage: - Two JSON arrays merge -> single linter run on union. - Empty-string argv entry skipped (no `files_renamed` in many PRs). - Malformed JSON -> falls back to walking DOCS_ROOT. - No argv -> walks DOCS_ROOT. - Single-arg invocation -> backward-compat preserved. Tracked as item 7 of the rolling-side cleanup backlog from PR #2014 / #2019 / #2020 reviews. πŸ€– Generated by [robots](https://vyos.io) (cherry picked from commit 1ef5684729646ca3a24aff83ab8edd0aa57914c7)
2026-05-14ci(workflows): remove conflict markers from update-version-tags.ymlYuriy Andamasov
`.github/workflows/update-version-tags.yml` has carried literal git conflict markers in its YAML header since the Mergify auto-backport in #1988 (cherry-pick of #1984 onto sagitta) β€” the cherry-pick conflicted, but Mergify's `ignore_conflicts: true` default at the time committed the markers verbatim into the file. (The central Mergify config switched to `ignore_conflicts: false` a few days later as a direct response to this class of incident; #1988 had already merged.) Result: GitHub's YAML parser fails on `<<<<<<<` / `=======` / `>>>>>>>` and the workflow can't schedule any job. Every push to sagitta touching `docs/**` or `context7.json` since #1988 merged reported "this run likely failed because of a workflow file issue" with 0 jobs. The downstream `context7-refresh.yml` (which uses `workflow_run` to react to this workflow's name) also never fired, so Context7's `1.4` index has been getting stale on every doc push. Fix: replace the file with rolling's verbatim content. The post-conflict-marker content on sagitta was already byte-identical to rolling; only the marker block + the obsolete comment block above it differ. Confirmed with `diff <(git show origin/rolling:.../update-version-tags.yml) <(...)` returning empty after stripping markers. After this merges: - `Update version tags` runs on every docs-affecting push to sagitta, moves the `1.4` tag to HEAD. - `Context7 refresh` fires via `workflow_run` and refreshes the `1.4` index. πŸ€– Generated by [robots](https://vyos.io)
2026-05-13ci(ai-validation): skip validation on Mergify-authored PRsYuriy Andamasov
The upstream anthropics/claude-code-action rejects bot-initiated workflow runs with: Workflow initiated by non-human actor: mergify (type: Bot). Add bot to allowed_bots list or use '*' to allow all bots. This leaves a failing required check on every Mergify backport PR (#2025 today is the proximate symptom β€” circinus backport of #2016 is blocked from auto-merging by this). Per org policy, Mergify-authored PRs skip the bot-review flow entirely: the underlying change was already reviewed on the source PR. Re-running validation on the backport would also post duplicate findings. Fix: extend the existing `secrets-check` step (kept the id for backwards compat with the cascade of `if: steps.secrets-check.outputs.skip != 'true'` gates) to also short-circuit when `github.event.pull_request.user.login` equals `mergify[bot]`. Add a `skip_reason` output so the notify-on-PR step can distinguish bot-skip (silent) from missing-secrets (post a notice comment). Implementation notes: - PR author is bound through env: PR_AUTHOR (same pattern as secret bindings) so the template-expansion happens before bash sees the value as an already-quoted env variable. - Uses `printf '...%s\n' "$PR_AUTHOR"` for the notice line so the shell β€” not the YAML template engine β€” interpolates the author into the workflow log output. - Renamed step name to "Decide whether to skip validation" to reflect the broader scope; step id stays `secrets-check`. πŸ€– Generated by [robots](https://vyos.io) (cherry picked from commit d75de0c24b971a2d87e817ac3cb8e09cb816aac0)
2026-05-14ci: inline doc-linter on sagitta to match rollingYuriy Andamasov
The lint workflow on this branch referenced `vyos/.github/.github/workflows/lint-doc.yml@feature/T6349-reusable-workflows`, which doesn't exist in `vyos/.github` (no commit found for that ref). The reusable-workflow `uses:` line has been silently failing (no `doc-lint` check on recent sagitta PRs). Mirror rolling's approach (inlined 2026-05-10): - Add `scripts/doc-linter.py` in this repo. Sourced from PR #2014's HEAD on rolling (the version that includes the `is_docs_path()` docs/-only scope guard so repo-root meta files like AGENTS.md and README.md are out of scope, plus the realpath / narrow-exception / Ruff fixes from that PR's review pass). - Replace `.github/workflows/lint-doc.yml` with an in-repo workflow that runs `python scripts/doc-linter.py "$FILES_MODIFIED"` against the changed-file list from `trilom/file-changes-action`. Same job shape as rolling. - Update the `## Lint` and `## CI` bullets in AGENTS.md to point at the in-repo paths. This restores actual lint coverage on sagitta PRs and keeps the toolchain identical across rolling / circinus / sagitta. Behavior on the 2 remaining `.rst` pages (cli.rst, aws.rst) is unchanged β€” the linter's SUPPORTED_EXTS already covers `.md`, `.rst`, and `.txt`. πŸ€– Generated by [robots](https://vyos.io)
2026-05-12ci(ai-validation): stop retargeting origin to fork URLYuriy Andamasov
The previous CR-driven fix (7a7a8002) re-pointed origin to the PR's HEAD fork on the assumption that claude-code-action calls `git fetch origin <head-branch>`. That assumption is wrong for fork PRs: the action detects a fork PR and fetches `pull/<n>/head` instead, and those refs only exist on the base repo. Retargeting origin to the fork therefore breaks the fork-PR fetch path with: PR #<n> is from a fork, fetching via refs/pull/<n>/head... fatal: couldn't find remote ref pull/<n>/head Observed on run 25689321499 (PR #1891 from natali-rs1985/vyos-documentation). Leave origin pointed at vyos/vyos-documentation (the base repo) β€” that's where actions/checkout left it after the merge-ref checkout, and where `pull/<n>/head` resolves. Replace the step with an explanatory comment. πŸ€– Generated by [robots](https://vyos.io) (cherry picked from commit 68f87deca228ad2b6b07c4eb64f201c0838e494f)
2026-05-11ci(ai-validation): pin validate to prepare merge_sha + fix 2 stray escapesYuriy Andamasov
CR/Copilot pass on the merge-ref + retarget-origin revision raised: 1. (CR on #1991, line 278, Major) Re-resolving refs/pull/<n>/merge in validate is racy. GitHub may advance the merge ref between prepare and validate (rapid pushes), so Pass 1 (artifact from prepare) and Pass 2 (validate workspace) can see different revisions. concurrency.cancel-in-progress narrows the window but does not make the ref immutable. Fix: prepare outputs its post-checkout `git rev-parse HEAD` as `merge_sha`, validate checks out THAT exact sha. Both jobs now operate on the same revision regardless of subsequent pushes. 2. (Copilot on #1991, two findings on line 294) The merge-ref + retarget commit introduced two NEW stray heredoc escapes: `PR"'sclaude-code-action'"s`. Same class of error as the previous-fix-cycle: shell heredoc escape sequences survived verbatim into YAML. Fix: replace with plain `PRs` / `claude-code-actions` in the inline comment block and step name. 3. (Copilot on #1990/#1991, "PR title/description says HEAD repo + head.sha") Metadata cleanup tracked separately β€” PR titles + bodies updated to describe the merge-ref + retarget-origin approach (no workflow file change for that part). Mirrored byte-identically across the 3 open wave-5 PRs (#1990 rolling, #1991 circinus, #1993 sagitta follow-up). Canonical sync once these merge.
2026-05-11ci(ai-validation): merge-ref checkout + retarget origin to fork + wipe ↡Yuriy Andamasov
CLAUDE.md/.claude (CR findings) Two new CR findings on PR #1990 (both raised on the same review cycle): 1. (CR, line 296, Major, Heavy lift) Tree drift between Pass 1 and Pass 2. The prior fix in this PR used `head.sha` (origin=fork) so the action's `git fetch origin <head-ref>` resolves β€” but that left validate's workspace tree at PR HEAD while prepare bundled `diff-md.patch` + `_changed_md/` from `refs/pull/<n>/merge`. For PRs where base also contributes content to a touched file, Pass 2 workspace-Read could disagree with Pass 1's view. Fix: switch the validate checkout back to `refs/pull/<n>/merge` (workspace tree now matches the prepare bundle) and address the action's `git fetch origin <head-ref>` requirement separately by `git remote set-url origin <fork-url>` after the checkout. The workspace tree is unchanged by the remote retarget; only the fetch destination is updated. For public forks (the only kind targeting public vyos/vyos-documentation) the unauthenticated fetch succeeds. 2. (CR, line 316, Major) `anthropics/claude-code-action@v1` invokes the Claude Code CLI, which auto-discovers and loads `CLAUDE.md` and `.claude/**` (memory, skills, hooks, MCP, plugins) from the workspace at session startup. A malicious fork could pre-place a `CLAUDE.md` with prompt-injection content, or a `.claude/skill.md` marked as auto-load. Documented at: - code.claude.com/docs/en/claude-directory.md - code.claude.com/docs/en/memory Fix: add `CLAUDE.md` and `.claude` to the wipe list so Claude's auto-discovery starts from a clean slate (workflow-controlled state only). `--bare` would also disable this but would lose the `mcp__github_inline_comment` MCP server the action provides, so wiping reserved paths is the better fit. Mirrored across rolling (#1990), circinus (#1991), and the sagitta follow-up (#1993). Canonical sync once these merge.
2026-05-11ci(ai-validation, sagitta): follow-up β€” uniform rm -rf + wipe .venv + drop ↡Yuriy Andamasov
stray quote escape Sagitta merge of #1992 landed BEFORE the 3 follow-up Copilot/CR findings on the same wave were addressed. This PR brings sagitta back to byte-identity with rolling (PR #1990) and circinus (PR #1991) β€” same 3 fixes that those PRs got via their second-round commit: 1. (CR on #1991) Switch wipe step to use `rm -rf` uniformly for all reserved paths. `rm -f` silently no-ops on directories; a fork could commit `pass1-findings.json/` AS a directory and bypass the wipe. 2. (Copilot on #1991) Add `.venv` to the wipe list. astral-sh/setup-uv with activate-environment:true creates ${{ github.workspace }}/.venv and prepends its bin/ to PATH; a fork-pre-populated .venv/bin/ could poison PATH for subsequent steps. 3. (Copilot on #1992) Replace literal `PR"\'"\'"s` (a stray shell heredoc escape) with plain `PR's` in the inline comment block. No behavioural change beyond the three fixes; sagitta now matches the post-follow-up state of rolling/circinus.
2026-05-11ci(ai-validation): address 4 Copilot findings on validate-checkout stepYuriy Andamasov
CR pass on #1990/#1991/#1992 raised 4 findings on the new validate- side actions/checkout step. All valid; folding into one bundle: 1. Fork PR head-ref not found in base repo (#1990, #1991, #1992 each raised this). The previous form checked out refs/pull/<n>/merge from origin (vyos/vyos-documentation), leaving origin pointed at the base repo. claude-code-action then runs `git fetch origin <head-ref>` where <head-ref> is e.g. `contributor/branch` β€” which does NOT exist in the base repo for fork PRs, so the fetch fails the same way "no .git" failed before. Fix: check out the PR HEAD repo (the fork for fork PRs) at the head sha. origin now resolves to the head repo where the head ref exists, so the action's fetch succeeds. github.token is the default GITHUB_TOKEN which can read public forks (the only kind of fork that can target vyos/vyos-documentation, since that repo is public). 2. Untrusted PR content at workspace root can pre-create reserved paths (#1991). The PRs tree at workspace root could in principle contain `.reference-db/extracted/...`, `pass1-findings.json`, `_changed_md/poisoned.md`, etc., which subsequent steps would treat as workflow-generated. The fail- closed gate only checks `[ ! -d .reference-db/extracted ]` so a PR-placed empty `.reference-db/extracted/` would bypass it. Fix: add a "Wipe reserved workspace paths" step right after the checkout that `rm -rf`s the workflow-owned paths (.reference-db, .vyos-1x, reviewer, reviewer-src, _changed_md) and `rm -f`s the workflow-owned files (pass1-findings.json, diff-md.patch, changed-*.txt) β€” every one of these is recreated by the trusted producer step immediately following. 3. Checkout runs unconditionally even on skip=true paths (#1991). The checkout only needs to run when validate will actually use it (skip != true β†’ Pass 2 runs). Fix: gate the checkout (and the wipe step) on `if: steps.secrets-check.outputs.skip != true`. Move them AFTER the secrets-check step in step order so the conditional is meaningful. 4. "NO credentials" wording misleading (#1992). actions/checkout uses the GITHUB_TOKEN to download the tree β€” it is not an unauthenticated fetch. `persist-credentials: false` only suppresses writing the token into the resulting .git/config. Fix: rename the step to "Checkout PR HEAD (no persisted credentials)" and update the inline comment block to be explicit that the token is used for the download but not persisted. Mirrored byte-identically across rolling/circinus/sagitta + canonical follow-up after this wave merges.
2026-05-11ci(ai-validation): checkout PR merge ref in validate (claude-code-action ↡Yuriy Andamasov
needs git workspace) Smoke verify on PR #1977 (run 25659408343 after github_token fix landed) reached Pass 2 with valid auth but failed at action setup: fatal: could not open `docs/quick-start.md` for reading: No such file or directory fatal: not a git repository (or any of the parent directories): .git Action failed with error: Command failed: git fetch origin --depth=20 yuriy/docs-smoke-test-quickstart The action expects to run inside a git checkout of the PR's repo so it can `git fetch <head-ref>` and read changed files from the working directory. Our split-job design checks out vyos-1x, reviewer-src, and the reviewer-config sparse-checkout into named subdirs but leaves the workspace root empty (we only have _changed_md/ via artifact download). Add `actions/checkout` of `refs/pull/<n>/merge` as the FIRST step in validate. Subsequent steps that create named subdirs (_changed_md/, .vyos-1x/, reviewer-src/, reviewer/, .reference-db/) coexist with the docs/scripts/.github content checked out here β€” no path collisions. Trust model unchanged: * persist-credentials: false (no token in fork-content .git/config) * No shell step executes fork code (no pip/npm/make against the workspace; Pass 1 runs the trusted reviewer CLI; Pass 2 only uses allowlisted Read/Glob/Grep/mcp__github_inline_comment + Bash restricted to `gh pr comment|diff|view`) * The prepare job continues to provide the canonical bundled changed-md set via the pr-input artifact; validate now ALSO has the full PR tree available for cross-reference reads Mirrored byte-identically across rolling/circinus/sagitta + canonical follow-up.
2026-05-11Merge pull request #1988 from vyos/mergify/bp/sagitta/pr-1984Yuriy Andamasov
ci(context7): gate version-tag move + refresh on docs-only changes (backport #1984)
2026-05-11ci(context7): gate version-tag move + refresh on docs-only changesYuriy Andamasov
Add `paths:` filter to `Update version tags` push trigger so infra-only pushes (workflows, scripts, Docker, README, etc.) skip the tag move and therefore skip the downstream Context7 refresh. Context7 only ingests `docs/**` (per context7.json `folders`), so refreshing on infra commits wasted API calls and gave no benefit. `context7.json` is also covered because changes to its `rules`/`folders`/`excludeFolders` affect what Context7 returns even when no .md files moved. The version tag's only consumer is Context7, so skipping the move on infra-only pushes is semantically correct β€” the tag still represents the latest docs state. `workflow_dispatch` on context7-refresh.yml remains available to force a refresh out of band. πŸ€– Generated by [robots](https://vyos.io) (cherry picked from commit 800acbfd4c7ddeb6e60be6ddf60d5d8ef433d735) # Conflicts: # .github/workflows/context7-refresh.yml # .github/workflows/update-version-tags.yml
2026-05-11ci(ai-validation): pass github_token to claude-code-action (skip OIDC exchange)Yuriy Andamasov
Second smoke verify on PR #1977 (run 25658927427) reached Pass 2 but failed with "Action failed with error: Invalid OIDC token". Diagnosis: The action (anthropics/claude-code-action@v1) mints an OIDC token with audience `claude-code-github-action` and POSTs it to `api.anthropic.com/api/github/github-app-token-exchange` to receive an Anthropic-managed GitHub App token for posting back to the PR. The endpoint validated our OIDC token and rejected it ("Invalid OIDC token") β€” this happens on repos where Anthropic-side OIDC federation hasn't been provisioned for the GitHub org. Bypass the exchange entirely by providing `github_token` to the action: `setupGitHubToken()` in src/github/token.ts checks `process.env.OVERRIDE_GITHUB_TOKEN` first and uses it directly if set, skipping OIDC. `${{ github.token }}` is auto-scoped to the current PR repo and gets the validate job's permissions (pull-requests: write, issues: write, contents: read) β€” exactly what the action needs. `steps.app.outputs.token` (our App token) is NOT suitable here: it's scoped to the VyOS-Networks org (vyos-1x, vyos-docs-opus-reviewer) for the earlier reviewer-source / vyos-1x checkouts, and has no write access on vyos/vyos-documentation PRs. Side effect: comments now post as github-actions[bot] (the workflow's own bot identity) rather than as claude[bot] (the Anthropic-managed GitHub App). Acceptable trade-off, and arguably preferable β€” the inline-review identity is now visibly the VyOS workflow rather than an external bot. Mirrored byte-identically across rolling/circinus/sagitta + canonical PR (folded into the still-open VyOS-Networks/vyos-docs-opus-reviewer#17).
2026-05-11ci(ai-validation): restore id-token: write (claude-code-action@v1 uses OIDC)Yuriy Andamasov
Revert of the id-token drop from #1969. Smoke verify on PR #1977 (empty-commit retrigger run 25658256103) failed at the Pass 2 step: Action failed with error: Could not fetch an OIDC token. Did you remember to add `id-token: write` to your workflow permissions? The earlier Copilot finding that motivated dropping the permission ("no step uses OIDC") was incomplete. No shell step in the workflow invokes OIDC directly, but anthropics/claude-code-action@v1 itself calls actions/core's `getIDToken()` internally β€” likely for the Claude/Anthropic auth federation path. The required scope is the third-party action's, not the workflow body's. Adding id-token: write back with an inline comment block citing the specific failure mode + the run ID where it was reproduced so this isn't re-dropped on a future review pass. Mirrored byte-identically across rolling/circinus/sagitta + canonical.
2026-05-11ci(ai-validation): add :(glob) pathspec magic so top-level docs/*.md matchYuriy Andamasov
CRITICAL BUG discovered during smoke verify of PR #1977 (a draft PR touching docs/quick-start.md). The prepare job ran SUCCESS but validate SKIPPED because has_md_changes was false: $ git diff origin/rolling...HEAD --name-only -- 'docs/**/*.md' (empty) $ git diff origin/rolling...HEAD --name-only -- ':(glob)docs/**/*.md' docs/quick-start.md Root cause: git's default pathspec syntax uses fnmatch with FNM_PATHNAME (slashes are not crossed by `*`). The `**` glob is NOT recognized in default pathspec β€” it's treated as plain `**` (two consecutive asterisks). For `docs/**/*.md` this means git requires at least one `/` between `docs/` and `*.md`, so: - docs/configuration/service/foo.md β†’ MATCHES (1+ subdir) - docs/quick-start.md β†’ NO MATCH (0 subdirs) - docs/index.md β†’ NO MATCH (0 subdirs) - docs/cli.md β†’ NO MATCH (0 subdirs) - docs/copyright.md β†’ NO MATCH (0 subdirs) - docs/coverage.md β†’ NO MATCH (0 subdirs) - docs/404.md β†’ NO MATCH (0 subdirs) - docs/documentation.md β†’ NO MATCH (0 subdirs) 7 top-level files silently bypass validation on every PR that touches them. Adding the `:(glob)` magic prefix opts in to true shell-style ** semantics (also recognised by git's pathspec parser per gitignore-pattern rules): - docs/**/*.md β†’ :(glob)docs/**/*.md - docs/**/*.rst β†’ :(glob)docs/**/*.rst Three pathspec usages updated in the prepare job; comment references to "docs/**/*.md" left unchanged (they read as informal shorthand). Paired PR on VyOS-Networks/vyos-docs-opus-reviewer adjusts the canonical scripts/ai-validation.yml identically.
2026-05-11ci(ai-validation): collapse split-backtick comment span (Copilot)Yuriy Andamasov
Copilot finding on #1974/#1975: the inline backtick code span `[ -s diff-md.patch ] && [ ! -d .reference-db/extracted ]` was split across two YAML comment lines, which renders poorly in Markdown viewers (the backtick crosses the line boundary). Reworded to put the relevant filesystem-presence check (`-d .reference-db/extracted`) on a single line and drop the redundant `-s diff-md.patch` half β€” the latter is the job-level gate condition, not part of the fail-closed gate logic, so omitting it from this rationale comment removes the wrap-required spread without losing meaning. The `-d` check on its own is what defines "the gate is a presence check, not a schema check". Mirrored byte-identically across rolling/circinus/sagitta.
2026-05-11ci(ai-validation): bump REVIEWER_REF to v1.0.2Yuriy Andamasov
Single-line follow-up now that `reviewer-v1.0.2` is tagged (after canonical-sync PR VyOS-Networks/vyos-docs-opus-reviewer#15 merged). v1.0.2 is functionally identical to v1.0.1 β€” the Python package and `branches.json` are unchanged; the bump aligns this deployed copy with the canonical default for hygiene. The companion PRs on rolling/circinus also include the fail-closed- gate comment alignment that landed on this branch in #1973.
2026-05-11ci(ai-validation): clarify where schema-mismatch protection lives (Copilot)Yuriy Andamasov
Copilot finding on this PR: the previous comment block claimed the workflow's fail-closed gate catches DB schema incompatibilities, but that gate is purely a presence check (`-d .reference-db/extracted`) β€” it does not load or validate the DB. The actual schema-mismatch protection lives inside the pinned reviewer Python code (loaded via `uv pip install ./reviewer-src` from `env.REVIEWER_REF`): newer reviewer code would error at DB-load time inside the `Pass 1 β€” deterministic checks` step. Reword the rationale so it correctly attributes the protection to where it actually executes. Comment-only change; no behavior delta. Will sync the same wording to rolling/circinus/canonical in a small follow-up batch once this PR merges.
2026-05-11ci(ai-validation): revert DB-download tag pin to latest:trueYuriy Andamasov
Backport of #1971 to this release branch β€” keeps the workflow file byte-identical across rolling/circinus/sagitta after the wave of #1968 / #1969 / #1959 / #1960 / #1970. Reason (full text in the rolling-side PR description): PR #1969's `tag: ${{ env.REVIEWER_REF }}` on the reference-DB download would 404 β€” the `reviewer-v1.x.x` tag has no GitHub release with DB assets attached. Reference DBs are published to a separate release stream (`ref-db-<timestamp>`) by the matrixed rebuild-reference workflow. Reverting to `latest: true` with an explicit inline comment block explaining the release-asset topology so this is not re-broken on a future review pass. The bug only manifests on a PR with `has_md_changes=true` (i.e. a real `.md`-changing PR); the recent merges that landed the regression were all infrastructure-only and never invoked the DB step. Companion PRs: #1971 (rolling), VyOS-Networks/vyos-docs-opus-reviewer#15 (canonical sync).
2026-05-11Merge pull request #1960 from vyos/yuriy/ai-validation-on-sagittaYuriy Andamasov
ci(sagitta): add AI Validation workflow
2026-05-11ci(ai-validation): 3 CR/Copilot follow-ups β€” NUL guard restore + issues: ↡Yuriy Andamasov
write + fail-closed gate CodeRabbit / Copilot findings on the merged PR #1959 and the still-open PRs #1960 + #1969: 1. NUL/control-char guard regressed. PR #1959 (merged into circinus without this) — and likewise sagitta/rolling-followup — had the guard inadvertently dropped during the rolling@HEAD rebase, because rolling@HEAD never had the corrected form. Restoring the user-corrected form: `LC_ALL=C grep -zPq "[\\x01-\\x1F\\x7F]"` reads the NUL-delimited *.z files directly (NUL is the record delimiter, not a forbidden byte) and rejects every other control byte 0x01-0x1F + 0x7F. An earlier `tr -d "\\0\\n\\r" | grep ...` form stripped the very bytes it was meant to reject before the grep ran — see the inline comment block for the contract. 2. `gh pr comment` requires `issues: write`. The skip-notice step and Pass 2 summary comment both post via `POST /repos/{owner}/{repo}/ issues/{number}/comments` — a PR conversation comment IS an issue comment in GitHub's data model. With only `pull-requests: write` the call can 403 on repos whose default GITHUB_TOKEN permission split routes issue-comment writes through `issues:`. Adding `issues: write` alongside the existing `pull-requests: write` keeps every comment path working without expanding the trust surface beyond what the original validate job needed. 3. Fail-closed gate used `[ -s changed-md.txt ]` (--diff-filter=ACMRT, excludes deletions) but validate is now gated on `has_md_changes` which is computed from `[ -s diff-md.patch ]` (unfiltered, deletion- aware). A deletion-only PR with a missing reference DB would reach validate (has_md_changes=true) but bypass the fail-closed gate (changed-md.txt empty), masking the DB-missing condition. Switch the gate to `[ -s diff-md.patch ]` so both signals agree. A separate Copilot finding (line 170, "validate gated on has_md_changes means RST-only PRs do not run") is pushed back on the PR thread as intentional design — RST is legacy per the RST→MyST migration, and the RST bookkeeping in prepare exists to surface mixed-MD-RST PRs in the Pass 2 prompt, not to drive validation on RST-only PRs.
2026-05-11ci(ai-validation): fail-fast on traversal paths (consistency w/ non-regular ↡Yuriy Andamasov
guard) Copilot finding on PR #1959 (line 114): the path-traversal hardening block used `continue` (skip-with-warning) on detection of absolute / traversal paths, but the non-regular-tree-entry check below uses `exit 1`. The asymmetry meant a fork PR that smuggles in a path like `docs/../../outside.md` would silently bypass Pass 1 (no file copied into _changed_md/) and Pass 2 (LLM Read/Glob/Grep tools see no content) β€” reducing validation coverage on exactly the inputs that warrant the closest look. Change `continue` to `exit 1` so both unsafe-input checks have consistent visible-failure semantics. Maintainers must explicitly address an offending path rather than have it skipped. Mirrored byte-identically across all three open workflow PRs.
2026-05-11ci(ai-validation): rebase onto rolling@HEAD (#1968) β€” keep SHA pins/DB ↡Yuriy Andamasov
pin/no-id-token PR #1968 merged on rolling while this PR was in CR review. It adds: * has_md_changes output on prepare + job-level if-gate on validate (fixes the empty-_changed_md/ crash on infrastructure-only PRs) * fetch base by refs/heads/<ref> + use FETCH_HEAD instead of origin/<ref> (fixes the tag-vs-branch ambiguity on vyos/vyos- documentation where 'rolling' exists as both a branch and a tag) * skip validate on deletion-only Markdown PRs This commit pulls in rolling@HEAD's ai-validation.yml verbatim, then re-applies the 4 still-needed fixes raised by CR/Copilot: 1. SHA-pin actions/checkout@v6 (x4) + actions/upload-artifact@v4 + actions/download-artifact@v4 + anthropics/claude-code-action@v1 2. Pin reference-DB download to tag: ${{ env.REVIEWER_REF }} (was latest: true) β€” aligns DB version with pinned reviewer code 3. Drop id-token: write from validate job permissions (no OIDC use) Items already present in rolling@HEAD via PR #1968 (no further action here): * NUL/control-char rejection in changed-*.z (the corrected grep -zPq '[\x01-\x1F\x7F]' form, plus the explanatory comment block about why NUL is excluded from the rejection class) * Job-level if-gate on validate so infrastructure-only PRs skip the entire expensive validate chain * mkdir _changed_md defensive step in validate Result: 3 PRs (this one + the circinus/sagitta companions) now share a single byte-identical workflow file that is also a strict superset of rolling@HEAD's current file (3 fixes layered on top).
2026-05-11ci(ai-validation): reword NUL-guard comment for accuracyYuriy Andamasov
Two factual issues in prior wording (flagged by Copilot on #1969): - "NUL inside the name" implied embedded NUL is something to reject; NUL cannot appear in a git pathname (it is the tree-entry terminator). - "Filesystems … typically reject these" was wrong for LF/CR β€” POSIX filesystems allow them and git stores them fine. The actual hazard is in our line-delimited downstream tooling. Consolidated the two adjacent comment blocks into one accurate explanation. No behavior change. πŸ€– Generated by [robots](https://vyos.io)
2026-05-11ci(ai-validation): fix control-char guard β€” use grep -z, exclude NULYuriy Andamasov
Previous form `tr -d '\0\n\r' | grep -Pq [\x00-\x1F\x7F]` stripped the very chars (LF, CR) it was meant to catch before the grep ran, so a path containing newlines or carriage returns slipped through. `grep -z` keeps NUL as the record delimiter (legitimate separator from git diff -z) and the pattern excludes 0x00 while catching every other control byte 0x01-0x1F + 0x7F. LF/CR inside any path now correctly fail the guard. Surfaced by Copilot on #1969; applied to all three branch copies (rolling/circinus/sagitta) so the workflow stays in sync. πŸ€– Generated by [robots](https://vyos.io)
2026-05-11ci(ai-validation): scope GitHub App token to permission-contents: readYuriy Andamasov
The token is used only for read-only repo operations (sparse-checkout of reviewer branches.json, full checkout of vyos-1x, download of the reference-DB release asset). Without an explicit permission-* input the token inherits all installation permissions. Scope it down so a compromise cannot mutate either repo. Surfaced by CodeRabbit on #1960; applied to all three branch copies (rolling via #1969 follow-up + circinus #1959 + sagitta #1960) so the workflow stays in sync across the version-train branches. πŸ€– Generated by [robots](https://vyos.io)
2026-05-11ci(ai-validation): mirror 4 CR/Copilot findings from #1959Yuriy Andamasov
Mirrors 025319ea from PR #1959 β€” addresses the same four findings on this PR (CR raised on #1959, Copilot raised the id-token finding here in addition). One bundle on both PRs keeps the workflow file byte-identical across branches: 1. SHA-pin actions/checkout@v6 (x4), actions/upload-artifact@v4, actions/download-artifact@v4, anthropics/claude-code-action@v1 2. Reject control-char paths before tr-conversion to newline manifest 3. Pin DB download to env.REVIEWER_REF (drop latest: true) 4. Drop id-token: write (no OIDC usage) See #1959 commit message for full rationale.
2026-05-11ci: backport update-version-tags hardening to sagitta (rolls up #1953 + #1958)Yuriy Andamasov
Brings sagitta's `.github/workflows/update-version-tags.yml` to the final post-#1958 rolling-side state. This rolls up the entire hardening series in one shot because Mergify's per-PR backport could not cherry-pick #1958 cleanly onto a base that never received #1953 (the prior backport, #1955, was closed). Final workflow shape: - Two-job pipeline. `check_head` resolves the per-branch tag name and filters stale "Re-run jobs" replays via a HEAD-equivalence check against the live branch HEAD; sets `is_current` output. - `retag` depends on `check_head`, runs only when `is_current=='true'`, carries job-level `concurrency: {group: version-tag-<ref>, cancel-in-progress: false}` so back-to-back pushes serialize in commit order. - `retag` re-validates HEAD inside its own job before PATCH so that GitHub's "Re-run failed jobs" (which can re-execute retag in isolation) cannot move the tag to a stale `github.sha`. - Tag mutation: PATCH-first, fallback to POST only on HTTP 404, fail loud on any other gh-api error. Verbatim copy of `origin/rolling:.github/workflows/update-version-tags.yml` substantive content, with the LTS branch's "keep all three copies in sync" header preserved (rolling has a context7-refresh-specific header because that workflow is rolling-only). πŸ€– Generated by [robots](https://vyos.io)
2026-05-11ci(ai-validation): mirror comment refresh from #1957Yuriy Andamasov
Mirrors bf50e7d1 from PR #1957 β€” two stale-comment fixes flagged by Copilot on the paired circinus PR #1959 (review of f02cc04): the 'cp loop' wording and the 'setup-uv on Debian 12' rationale. Documentation-only; keeps this branch's workflow byte-identical with rolling's #1957 head and circinus's #1959 head.
2026-05-11ci: add AI Validation workflow on sagittaYuriy Andamasov
Adds .github/workflows/ai-validation.yml on the sagitta release branch. Byte-identical to the version that lands on rolling via #1957 and on circinus via the paired companion PR. Background ---------- For pull_request_target events, GitHub Actions evaluates the workflow file from the repo's DEFAULT branch (rolling), so the rolling workflow already fires on PRs targeting sagitta β€” that's why phantom queue entries for "AI Validation" appeared on Mergify backport PRs to sagitta (#1955) even when this file didn't yet exist on the branch. Adding the file here is therefore not a behavioral fix; it's governance/clarity: * makes the workflow visible to maintainers reading the sagitta branch in isolation, * preserves the workflow if rolling's copy is ever removed or renamed, * allows independent edits per release branch in the future (e.g. different REVIEWER_REF pin if a release branch needs a frozen reviewer version). Runner pool ----------- `runs-on: ubuntu-latest` on both prepare and validate β€” see #1957 for the full rationale (the vyos org has no self-hosted runners labeled `web`; ubuntu-latest is the only available pool for this repo). Branches-map and reference DB ----------------------------- The reviewer's branches.json already maps `sagitta β†’ sagitta` (see VyOS-Networks/vyos-docs-opus-reviewer/branches.json on reviewer-v1.0.1), and the matrixed rebuild-reference workflow already publishes `reference-db-sagitta.tar.gz` as part of each rebuild. No reviewer-side change needed.
2026-05-10ci: bring update-version-tags.yml to sagittaYuriy Andamasov
This workflow exists on rolling but was never present on sagitta, so push events to sagitta do not trigger the tag-move job and tag 1.4 has been drifting from sagitta HEAD. Add the workflow verbatim from rolling plus a cross-branch sync reminder comment. Prerequisite for the Context7 GitHub Actions integration. See spec ~/.claude/specs/2026-05-10-context7-github-actions-integration-design.md. πŸ€– Generated by [robots](https://vyos.io)
2024-05-28T6410: workflow fixeskumvijaya
(cherry picked from commit 8e5147d802561dd0bf3b4ef7061c2234d5ab5b2b)
2024-05-28T6410: worflow fixeskumvijaya
(cherry picked from commit 7ddd96be3dd3f4ad9f886d6e7110d4380074ffb8)
2023-09-12Merge branch 'master' of github.com:vyos/vyos-documentationrebortg
2023-09-12include releasenotes of 1.5rebortg
2023-09-06github: schedule update-translations actionRobert GΓΆhler
2023-08-27Update update-translations.ymlRobert GΓΆhler
2023-06-30add localazy as the translation providerrebortg
2023-06-05T5258: git Actions use ubuntu-22.04 for PR conflicts checkerViacheslav Hletenko
git Actions use ubuntu-22.04 instead of deprecated ubuntu-18.04 for PR conflicts checker actions/runner-images#6002
2022-04-12downgrade phabricator api sdkRobert GΓΆhler
2021-06-12GitHub: add workflows for PR triage and merge conflictsChristian Poessinger
2021-02-15Github: prevent to run update releasenotes without the apikeyrebortg
2021-02-10Releasenotes: add weekly releasenote create scriptrebortg
2021-02-06Github: adjust githubactions for sagitta and equuleusrebortg
2021-02-03Coverage: reenable coverage page progress (#441)Robert GΓΆhler
* Github: correct branch for vyos-1x submodule * Coverage: reenable coverage page progress * Update vyos-1x submodule (#24) Co-authored-by: rebortg <rebortg@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: rebortg <rebortg@users.noreply.github.com>