Skip to content

ci(qa): recency-aware active-channel matrix via quickchr 0.4.2 (B-2) - #71

Merged
mobileskyfi merged 2 commits into
mainfrom
ci/qa-recency-matrix-b2
Jun 22, 2026
Merged

ci(qa): recency-aware active-channel matrix via quickchr 0.4.2 (B-2)#71
mobileskyfi merged 2 commits into
mainfrom
ci/qa-recency-matrix-b2

Conversation

@mobileskyfi

@mobileskyfi mobileskyfi commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

What

Replaces qa.yaml's hard-coded [stable, long-term, development] matrix axis with a recency-aware active set, resolved per run by a new resolve-matrix pre-flight job that consumes quickchr 0.4.2's public version/channel API (quickchr#3).

The active set = released channels (stable, long-term) always, plus any pre-release (testing, development) at or ahead of stable. Because the four RouterOS channels are not monotonically ordered, this:

  • closes the testing blind spot (previously only reachable via explicit single-channel dispatch), and
  • auto-adapts as MikroTik promotes builds — a stale testing rc is skipped and picked up automatically once it leapfrogs stable. This relies on quickchr's now suffix-aware compareRouterOsVersion (7.24beta2 < 7.24rc1 < 7.24).

Boundary (kept)

  • quickchr owns recency — what's worth booting (resolveAllVersions / selectActiveChannels).
  • centrs owns the gate — what must pass. MUST_PASS_CHANNELS / evaluateMustPassGate stay in scripts/qa-results-db.ts, mirrored by the matrix continue-on-error. A pre-release leg can be booted (active) yet never gate a merge.

The new scripts/qa-active-channels.ts only maps the active set onto the GitHub Actions matrix axis. A concrete dispatch channel still pins a single leg; all / push / schedule / the release sweep fan the active set. On a network failure it falls back to the released channels (never drops a must-pass leg) instead of hard-failing.

quickchr is loaded through a runtime specifier (as in test/integration/chr.ts) so its shipped .ts stays out of centrs's stricter tsc graph.

Changes

  • package.json / bun.lock: @tikoci/quickchr ^0.4.1^0.4.2.
  • scripts/qa-active-channels.ts (new): recency-aware matrix-axis resolver.
  • test/unit/qa-active-channels.test.ts (new): 10 tests — pure mapping, fallback, and the recency contract on fixtures.
  • .github/workflows/qa.yaml: resolve-matrix pre-flight job; chr-matrix consumes needs.resolve-matrix.outputs.channels.
  • .github/instructions/ci-test-tiers-and-release-versioning.instructions.md, CHANGELOG.md: doctrine + Unreleased entry.

Verification

  • bun run lint + bun run lint:ci + bun run test (745 pass) + bun run build — all green locally.
  • CHR validation: dispatched qa.yaml -f routeros_channel=all on this branch (recency-aware matrix end-to-end on real CHR). Today's resolved active set = [stable, long-term, development] (testing 7.23rc4 correctly excluded, behind stable 7.23.1).

Closeout: June-Gloom Track B-2. Consumes quickchr#3.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • QA testing matrix now dynamically resolves which RouterOS channels to test against per-run, replacing fixed version sets.
  • Documentation

    • Updated QA documentation and CHANGELOG to clarify must-pass vs. best-effort test policies and recency-aware channel selection.
  • Chores

    • Updated quickchr dependency to v0.4.2.

Replace qa.yaml's hard-coded `[stable, long-term, development]` matrix axis
with a `resolve-matrix` pre-flight job that asks quickchr 0.4.2's public
version/channel API which channels are worth booting: released channels
(stable, long-term) always, plus any pre-release (testing, development) at or
ahead of stable. The four RouterOS channels are not monotonically ordered, so
this closes the `testing` blind spot and auto-adapts as MikroTik promotes
builds — a stale `testing` rc is skipped and picked up automatically once it
leapfrogs stable (quickchr's suffix-aware compareRouterOsVersion now orders
7.24beta2 < 7.24rc1 < 7.24).

Boundary: quickchr owns recency ("what's worth booting"); centrs owns the gate
("what must pass"). The must-pass policy stays in scripts/qa-results-db.ts
(MUST_PASS_CHANNELS / evaluateMustPassGate, mirrored by the matrix
continue-on-error); the new scripts/qa-active-channels.ts only maps the active
set onto the matrix axis. A concrete dispatch channel still pins a single leg;
"all"/push/schedule/the release sweep fan the active set. On a network failure
the script falls back to the released channels (never drops a must-pass leg)
rather than hard-failing.

quickchr is loaded through a runtime specifier (as in test/integration/chr.ts)
so its shipped .ts stays out of centrs's stricter tsc graph. Bumps
@tikoci/quickchr ^0.4.1 → ^0.4.2 (the release that adds the public exports,
`version --json`, and the suffix-aware comparator — quickchr#3).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 22, 2026 02:16
@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: b7d780f5-0bdc-47b2-96b8-e2bffbf40198

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Replaces the static RouterOS channel list in qa.yaml with a new resolve-matrix pre-flight job that runs scripts/qa-active-channels.ts. This script calls @tikoci/quickchr to compute a live, recency-aware active channel set per run, merges it with must-pass channels, and exports the result as a JSON array for the chr-matrix job. Tests, CHANGELOG, and CI instructions docs are updated accordingly.

Changes

Recency-aware QA channel matrix resolution

Layer / File(s) Summary
Channel types, classification contracts, and quickchr API surface
scripts/qa-active-channels.ts
Defines Channel, ChannelStatus, ChannelPlan, QuickChrVersionApi, CONCRETE_CHANNELS, and isConcreteChannel — the core data shapes and type guards consumed by all downstream logic in the script.
Channel plan resolution and matrix merging
scripts/qa-active-channels.ts, package.json
Implements resolveChannelPlan with null-on-failure degradation and matrixChannels merging the active set with MUST_PASS_CHANNELS or pinning a single concrete channel. Bumps @tikoci/quickchr to ^0.4.2.
main CLI entrypoint and summary rendering
scripts/qa-active-channels.ts
Implements main (arg parsing, pinned vs. network resolution, GITHUB_OUTPUT write, stdout JSON), activeMatrixSummary (GH Markdown table), flag helper, and the import.meta.main Bun entrypoint.
resolve-matrix job and chr-matrix wiring
.github/workflows/qa.yaml
Adds the resolve-matrix job (Bun setup, run qa-active-channels.ts, export channels) and updates chr-matrix to consume fromJSON(needs.resolve-matrix.outputs.channels) instead of the hard-coded channel expression.
Unit tests for qa-active-channels
test/unit/qa-active-channels.test.ts
Bun test suite covering isConcreteChannel, matrixChannels, resolveChannelPlan (injected API and null-on-failure), QuickCHR-driven recency contracts, and activeMatrixSummary rendering.
CHANGELOG and instructions documentation
CHANGELOG.md, .github/instructions/ci-test-tiers-and-release-versioning.instructions.md
Updates CHANGELOG to describe the per-run channel resolution, version-ordering logic, and gating semantics. Updates CI instructions doc to replace fixed active-set text with the resolve-matrix pre-flight pattern.

Sequence Diagram(s)

sequenceDiagram
  participant Trigger as Workflow Trigger
  participant ResolveMatrix as resolve-matrix job
  participant QAScript as qa-active-channels.ts
  participant QuickChr as `@tikoci/quickchr`
  participant ChrMatrix as chr-matrix job

  Trigger->>ResolveMatrix: inputs.routeros_channel (REQUESTED_CHANNEL)
  ResolveMatrix->>QAScript: bun run --requested-channel $REQUESTED_CHANNEL
  QAScript->>QuickChr: resolveAllVersions()
  QuickChr-->>QAScript: Record<Channel, string>
  QAScript->>QuickChr: classifyChannels() / selectActiveChannels()
  QuickChr-->>QAScript: ChannelStatus[], active Channel[]
  QAScript->>QAScript: matrixChannels() merges with MUST_PASS_CHANNELS
  QAScript-->>ResolveMatrix: channels=["stable","long-term",...] → GITHUB_OUTPUT
  ResolveMatrix-->>ChrMatrix: outputs.channels
  ChrMatrix->>ChrMatrix: fromJSON(outputs.channels) → matrix.routeros_channel
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • tikoci/centrs#54: Previous @tikoci/quickchr bump (^0.4.0^0.4.1) in the same package.json devDependency entry that this PR advances to ^0.4.2.
  • tikoci/centrs#70: Introduced the must-pass gating model (MUST_PASS_CHANNELS, qa-results-db.ts, qa-history.ts) that qa-active-channels.ts references as its must-pass floor and policy boundary.

Poem

🐇 Hop-hop, no more fixed lists to keep,
Each run now wakes the channels from sleep!
quickchr speaks — "what's released? what's ahead?"
The matrix resolves what must boot, what's best-effort instead.
From stable to testing, recency's the guide,
With null-on-failure, no rabbit left behind! 🌿

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The description is comprehensive and well-structured. It covers the what, boundary, changes, and verification. However, it does not follow the provided template with explicit sections for 'Links', 'Change type', and 'Notes'. Structure the description to match the template: add explicit 'Links' section with linked spec/work item, mark the applicable 'Change type' checkbox (appears to be tooling/workflow change), and populate 'Notes' with validation and assumptions.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and specifically summarizes the main change: implementing a recency-aware active-channel matrix that relies on quickchr 0.4.2.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ci/qa-recency-matrix-b2

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Introduces a recency-aware RouterOS channel selection for the qa.yaml CHR matrix by delegating “what’s worth booting” to @tikoci/quickchr’s channel/version API, while keeping centrs’ must-pass gating policy unchanged.

Changes:

  • Adds a new resolve-matrix pre-flight job in qa.yaml that computes the active channel set per run and feeds it into the CHR matrix.
  • Adds scripts/qa-active-channels.ts plus unit tests to resolve/fallback and summarize the chosen matrix axis.
  • Bumps @tikoci/quickchr to ^0.4.2 and updates docs/changelog to reflect the new QA behavior.

Reviewed changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
test/unit/qa-active-channels.test.ts Adds unit coverage for channel pinning/fan-out, fallback behavior, and the quickchr-driven recency contract.
scripts/qa-active-channels.ts New resolver script that queries quickchr for the active set, falls back safely, and emits matrix JSON + summary.
package.json Bumps @tikoci/quickchr to ^0.4.2.
bun.lock Lockfile update for @tikoci/quickchr@0.4.2.
CHANGELOG.md Documents the recency-aware QA matrix behavior and boundary between quickchr recency vs centrs gating.
.github/workflows/qa.yaml Adds resolve-matrix job and wires its output into the CHR matrix.
.github/instructions/ci-test-tiers-and-release-versioning.instructions.md Updates CI doctrine to describe the new recency-aware QA matrix resolution.

Comment thread .github/workflows/qa.yaml
Comment on lines +71 to +72
- name: Checkout repository
uses: actions/checkout@v7
Comment thread scripts/qa-active-channels.ts Outdated
Comment on lines +182 to +186
await Bun.write(
summaryPath,
activeMatrixSummary(statuses, channels, degraded),
);
}
Comment thread .github/workflows/qa.yaml
Comment on lines +84 to +90
id: matrix
env:
REQUESTED_CHANNEL: ${{ inputs.routeros_channel || '' }}
run: |
bun run scripts/qa-active-channels.ts \
--requested-channel "$REQUESTED_CHANNEL" \
--summary "$GITHUB_STEP_SUMMARY"

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
CHANGELOG.md (1)

51-51: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Replace "exactly" with a less-repetitive alternative.

The word "exactly" is flagged by static analysis as overused; consider alternatives like "precisely" or a rephrasing to "is what a long history captures" (dropping the intensifier).

Suggested rewording
- and the channel→version drift over time is exactly what a long history captures) and fails the run **only when a released
+ and the channel→version drift over time is what a long history captures) and fails the run **only when a released

Or:

- and the channel→version drift over time is exactly what a long history captures) and fails the run **only when a released
+ and the channel→version drift over time is precisely what a long history captures) and fails the run **only when a released
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CHANGELOG.md` at line 51, In the CHANGELOG.md file, locate the word "exactly"
in the phrase "exactly what a long history captures" around line 51. Replace
"exactly" with a less repetitive alternative such as "precisely", or
alternatively rephrase the sentence to remove the intensifier entirely by
changing it to "is what a long history captures" to improve readability and
reduce word overuse as flagged by static analysis.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/qa.yaml:
- Around line 71-72: In the resolve-matrix job where actions/checkout is used at
line 72, replace the version tag reference (actions/checkout@v7) with an
explicit commit SHA pin to improve supply-chain security. Additionally, since
this job only reads the repository and does not require git credentials for any
modifications, add a with block to disable credential persistence by setting
persist-credentials to false. This reduces unnecessary token exposure while
maintaining the necessary repository access for matrix resolution.

---

Nitpick comments:
In `@CHANGELOG.md`:
- Line 51: In the CHANGELOG.md file, locate the word "exactly" in the phrase
"exactly what a long history captures" around line 51. Replace "exactly" with a
less repetitive alternative such as "precisely", or alternatively rephrase the
sentence to remove the intensifier entirely by changing it to "is what a long
history captures" to improve readability and reduce word overuse as flagged by
static analysis.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 716dfaf5-b6c1-4e3b-8e89-373656495dcd

📥 Commits

Reviewing files that changed from the base of the PR and between 7dbe870 and d725c13.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (6)
  • .github/instructions/ci-test-tiers-and-release-versioning.instructions.md
  • .github/workflows/qa.yaml
  • CHANGELOG.md
  • package.json
  • scripts/qa-active-channels.ts
  • test/unit/qa-active-channels.test.ts

Comment thread .github/workflows/qa.yaml
Comment on lines +71 to +72
- name: Checkout repository
uses: actions/checkout@v7

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Read the workflow file to understand context
cat -n .github/workflows/qa.yaml | sed -n '65,85p'

Repository: tikoci/centrs

Length of output: 1067


🏁 Script executed:

# Also check the resolve-matrix job definition to understand its purpose
cat -n .github/workflows/qa.yaml | sed -n '50,120p'

Repository: tikoci/centrs

Length of output: 4098


Pin actions/checkout by SHA and disable credential persistence in resolve-matrix.

Line 72 uses actions/checkout@v7 without a commit SHA pin. This job only reads the repository to resolve the active channel matrix—it doesn't require git credentials and makes no modifications. Pinning by version tag rather than commit SHA weakens supply-chain security, and persisting credentials unnecessarily increases token exposure.

Suggested hardening
-      - name: Checkout repository
-        uses: actions/checkout@v7
+      - name: Checkout repository
+        uses: actions/checkout@<full-commit-sha-for-v7>
+        with:
+          persist-credentials: false
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 71-72: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 72-72: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/qa.yaml around lines 71 - 72, In the resolve-matrix job
where actions/checkout is used at line 72, replace the version tag reference
(actions/checkout@v7) with an explicit commit SHA pin to improve supply-chain
security. Additionally, since this job only reads the repository and does not
require git credentials for any modifications, add a with block to disable
credential persistence by setting persist-credentials to false. This reduces
unnecessary token exposure while maintaining the necessary repository access for
matrix resolution.

Source: Linters/SAST tools

… summary append

- resolve-matrix: pin a single CHR leg when `routeros_version` is set (it
  overrides recency), instead of fanning the active set and re-booting the same
  build N times (Copilot). A concrete channel keeps its label; otherwise the leg
  is labelled `stable`.
- qa-active-channels.ts: write the job summary with appendFile, not Bun.write,
  so it coexists with other summary writers (Copilot, matches chr.ts).
- resolve-matrix checkout: `persist-credentials: false` — the job only reads the
  repo, so it needs no git credentials (CodeRabbit/zizmor). Kept the `@v7` tag:
  repo convention is tags for first-party actions/*, SHA pins for third-party.
- CHANGELOG: drop the flagged "exactly" intensifier (CodeRabbit nitpick).
- Tests: cover the version-pinned single-leg path (12 unit tests).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@mobileskyfi

Copy link
Copy Markdown
Contributor Author

Review disposition

Thanks @coderabbitai / Copilot — all items addressed in 9876027:

Item Disposition
routeros_version + all fans the matrix, booting the same build N times (Copilot) Fixed. resolve-matrix now pins a single leg when a version is given (the version overrides recency); a concrete channel keeps its label, otherwise the leg is labelled stable. Added 2 unit tests.
Bun.write($GITHUB_STEP_SUMMARY) overwrites prior summary (Copilot) Fixed. Switched to appendFile, matching test/integration/chr.ts.
resolve-matrix checkout: drop credentials (CodeRabbit/zizmor artipacked) Fixed. Added persist-credentials: false — the job is read-only (resolves the matrix axis, no commits).
Pin actions/checkout by commit SHA (CodeRabbit/zizmor unpinned-uses, Copilot) Resolved on merit — kept the @v7 tag. Repo convention (established with actions/upload-artifact/actions/cache in #52) is tag-pins for first-party actions/* and SHA-pins for third-party actions (e.g. oven-sh/setup-bun). Every checkout in this workflow uses @v7; pinning only this one to a SHA would be inconsistent. Happy to flip the whole repo to SHA-pinned first-party actions as a separate hygiene PR if preferred.
CHANGELOG "exactly" overused (CodeRabbit nitpick) Fixed. Dropped the intensifier.

Note: the failing Unit tests (windows-latest) check is the pre-existing Windows UDP-loopback bind ENOTSUP (MNDP/btest loopback) tracked in #69 — informational/non-gating, unrelated to this change.

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@mobileskyfi Thanks for the thorough disposition table — all five items are accounted for. A few notes on each:

  • Matrix fanning fix — pinning to a single leg when routeros_version is provided is the right call; no further concerns.
  • appendFile for step summary — good catch; glad it's consistent with test/integration/chr.ts now.
  • persist-credentials: false — correct for a read-only resolution job.
  • @v7 tag-pin rationale — the consistency argument holds. Mixing SHA-pins only on the new checkout while leaving every other first-party actions/* step tag-pinned would be more confusing than helpful. A whole-repo hygiene sweep as a separate PR sounds like the right path if you ever want full SHA-pinning.
  • CHANGELOG "exactly" — appreciated.

The Windows bind ENOTSUP pre-existing failure (tracked in #69) is understood and noted as non-gating here.

🐇✨

@mobileskyfi
mobileskyfi merged commit c5334a4 into main Jun 22, 2026
16 of 17 checks passed
@mobileskyfi
mobileskyfi deleted the ci/qa-recency-matrix-b2 branch June 22, 2026 02:49
mobileskyfi added a commit that referenced this pull request Jun 22, 2026
…des #10) (#72)

The npm dev-dep group bump dependabot proposed in #10, redone on fresh main
(its branch went stale/conflicting after the quickchr 0.4.2 bump in #71):

- @biomejs/biome 2.4.14 → 2.5.0
- secretlint + @secretlint/secretlint-rule-preset-recommend ^12.3.1 → ^13.0.2

Ran `biome migrate`: `$schema` → 2.5.0 and the deprecated `recommended: true`
linter field → `preset: "recommended"` (the 2.6 removal). No new lint findings;
secretlint 13 runs clean. Full recipe green (lint, lint:ci, test, build).

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants