Skip to content

ci(qa): cross-run history accumulation + must-pass gate; development on push (JG-18) - #70

Merged
mobileskyfi merged 3 commits into
mainfrom
ci-qa-accumulation-gate
Jun 21, 2026
Merged

ci(qa): cross-run history accumulation + must-pass gate; development on push (JG-18)#70
mobileskyfi merged 3 commits into
mainfrom
ci-qa-accumulation-gate

Conversation

@mobileskyfi

@mobileskyfi mobileskyfi commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Track B of the June-Gloom closeout: the harder half of JG-18. Per-run
bun:sqlite results shipped in #52, but nothing accumulated them across runs or
gated on them. This adds both, and promotes development into the push matrix
now that JG-31 (#68) is CHR-passed.

What changed

Policy (one source of truth, unit-tested) — scripts/qa-results-db.ts

  • MUST_PASS_CHANNELS = [stable, long-term], channelPolicy, evaluateMustPassGate.
    Released channels are must-pass; development/testing are best-effort.
  • JSONL round-trip (serializeRun, parseHistoryJsonl, rebuildHistoryDb) for
    the durable append-log, plus allRuns.

Accumulator — scripts/qa-history.ts (new, thin orchestrator)

  • Reads every downloaded per-leg qa-results.sqlite, gates over this run
    (stale history never reds a fresh clean run), appends to the history JSONL,
    writes a job-summary table, exits non-zero only on a released-channel fail.

Workflow — .github/workflows/qa.yaml

  • Push/schedule active set → [stable, long-term, development].
  • Best-effort legs are continue-on-error (mirrors channelPolicy), so a beta
    btest/EC-SRP5 flake never reds main — the new accumulate-and-gate job is the
    sole merge-reddening authority.
  • accumulate-and-gate (needs: chr-matrix, if: always()): downloads per-leg
    artifacts → worktree on the qa-history orphan branch (created on first
    run) → accumulate + gate → commit history (if: always() + main-only, with a
    rebase-retry push). History is written before the gate verdict so a failing
    run still lands in history.

Docs — tiers instruction qa.yaml bullet + CHANGELOG Unreleased.

Decisions (locked with maintainer)

  • History store = qa-history orphan branch (durable; artifact retention is
    finite and channel→version drift is the point) — vs. ephemeral artifact round-trip.
  • development = run on push, best-effort (must-pass = stable + long-term) —
    honors the JG-31 promotion while keeping the "never red a merge on a beta flake"
    guarantee.

Verification

  • bun run lint, bun run lint:ci, bun run test (735 pass / 0 fail), bun run build — all green.
  • New unit coverage: gate verdicts, JSONL skip/rebuild, end-to-end accumulation
    across runs (test/unit/qa-results-db.test.ts, test/unit/qa-history.test.ts).
  • Pending: a qa.yaml CHR dispatch on this branch to exercise the
    accumulate-and-gate wiring end-to-end on real CHR (orphan-branch creation +
    gate; the history push only runs post-merge on main). Will attach the run.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Chores
    • Updated QA workflow to expand testing across stable, long-term, and development channels. Released channels (stable, long-term) now enforce must-pass gating, while development is treated as best-effort. Introduced persistent cross-run history tracking to monitor QA results over time, enabling better regression detection and trend analysis.

…on push (JG-18)

The harder half of JG-18: per-run `bun:sqlite` results shipped in #52, but
nothing accumulated them or gated on them. This adds both, and promotes
`development` into the push matrix now that JG-31 (#68) is CHR-passed.

- `qa.yaml` push/schedule active set is now `[stable, long-term, development]`.
  Best-effort (non-released) legs are `continue-on-error`, so a beta
  btest/EC-SRP5 flake never reds main; the must-pass authority is a new
  `accumulate-and-gate` job, not the individual legs.
- New `accumulate-and-gate` job (`needs: chr-matrix`, `if: always()`):
  downloads every per-leg result, appends each run to a durable append-log on
  the `qa-history` orphan branch (per-run artifacts have finite retention; the
  channel→version drift is what long history captures), and fails only on a
  released-channel regression. History is written before the gate verdict so the
  `if: always()` commit persists even a failing run.
- Policy lives once in `scripts/qa-results-db.ts` (`MUST_PASS_CHANNELS`,
  `channelPolicy`, `evaluateMustPassGate`) + JSONL round-trip helpers, mirrored
  by the matrix `continue-on-error`. Cross-run accumulator is the thin
  `scripts/qa-history.ts`. Both unit-tested (gate verdicts, JSONL skip/rebuild,
  end-to-end accumulation across runs).
- Tiers instruction + CHANGELOG updated to match.

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

coderabbitai Bot commented Jun 21, 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: a03c441a-ba74-464b-815d-7f4b9582dcf0

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

Adds a durable QA run history system: scripts/qa-results-db.ts gains gate-policy exports (JSONL serialization, evaluateMustPassGate, rebuildHistoryDb); a new scripts/qa-history.ts CLI accumulates per-leg SQLite results into an append-only qa-runs.jsonl; qa.yaml adds development to the push matrix and introduces an accumulate-and-gate job that persists history to a qa-history branch. Only stable and long-term regressions fail the gate.

Changes

QA Cross-Run History and Must-Pass Gate

Layer / File(s) Summary
Gate policy data model and history JSONL exports
scripts/qa-results-db.ts, test/unit/qa-results-db.test.ts
Adds allRuns, QaRunRecord, serializeRun, parseHistoryJsonl, MUST_PASS_CHANNELS, GatePolicy, channelPolicy, GateResult, evaluateMustPassGate, and rebuildHistoryDb. Unit tests cover policy mapping (must-pass vs best-effort), gate evaluation for pass/fail/best-effort scenarios, and JSONL round-trip including malformed-line skipping.
qa-history.ts accumulator CLI and integration tests
scripts/qa-history.ts, test/unit/qa-history.test.ts
New CLI script scans --legs directories for qa-results.sqlite, appends serialized runs to qa-runs.jsonl, rebuilds a history DB, evaluates the must-pass gate, and writes an optional Markdown summary. Integration tests assert exit codes for green runs, released-channel regressions, multi-run accumulation, and empty leg directories.
qa.yaml matrix expansion, accumulate-and-gate job, and docs
.github/workflows/qa.yaml, .github/instructions/ci-test-tiers-and-release-versioning.instructions.md, CHANGELOG.md, GLOSSARY.txt
Adds development to the schedule/push matrix with continue-on-error. Adds an accumulate-and-gate job that downloads per-leg artifacts, prepares a qa-history branch worktree, runs scripts/qa-history.ts, and commits qa-runs.jsonl on main with a rebase-retry push loop. Docs, changelog, and glossary are updated to reflect the new behavior.

Sequence Diagram(s)

sequenceDiagram
  participant Push as Push / Schedule event
  participant Matrix as chr-test (stable, long-term, development)
  participant Artifacts as GitHub Artifacts Store
  participant Gate as accumulate-and-gate job
  participant Script as scripts/qa-history.ts
  participant Branch as qa-history branch

  Push->>Matrix: trigger CHR jobs
  Matrix->>Artifacts: upload qa-results-{channel} (sqlite)
  Artifacts->>Gate: download all qa-results-*
  Gate->>Branch: fetch or create orphan qa-history worktree
  Gate->>Script: run --legs --history qa-runs.jsonl --summary
  Script->>Script: collectLegRuns → evaluateMustPassGate
  Script->>Branch: append runs to qa-runs.jsonl
  Script-->>Gate: exit 0 (pass) or 1 (stable/long-term regressed)
  Gate->>Branch: commit qa-runs.jsonl (always, rebase-retry push)
  Gate-->>Push: fail main only on released-channel regression
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • tikoci/centrs#52: Introduced the per-run bun:sqlite QA results store in scripts/qa-results-db.ts and the qa.yaml artifact upload pattern that this PR extends with cross-run accumulation and gate evaluation.

Poem

🐇 Hippity-hop through the history log,
Each run appended, no lost dialogue!
stable and long-term must pass the gate,
development wobbles — that's perfectly great.
The rabbit commits, retries, and then cheers:
A qa-history branch that persists through the years! 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main changes: cross-run history accumulation, must-pass gating, and promoting development to push matrix.
Description check ✅ Passed The description comprehensively covers what changed (policy, accumulator, workflow, docs), design decisions, and verification, despite not strictly following the template structure.
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-accumulation-gate

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

Adds cross-run QA history accumulation and a must-pass gate to the qa.yaml workflow, persisting CHR results across runs and gating merges only on released-channel regressions (stable/long-term) while keeping development best-effort.

Changes:

  • Introduces a durable JSONL-backed accumulation flow (qa-history branch) and a must-pass gate evaluated per run.
  • Adds policy + JSONL round-trip utilities to the QA results DB module, with new unit coverage.
  • Updates QA workflow + contributor docs/changelog to reflect the new active set and gating behavior.

Reviewed changes

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

Show a summary per file
File Description
scripts/qa-results-db.ts Adds must-pass policy evaluation + JSONL history serialization/parsing helpers.
scripts/qa-history.ts New accumulator/gate orchestrator that appends per-leg DB results to history and writes a summary.
.github/workflows/qa.yaml Runs development on push/schedule; adds accumulate-and-gate job and best-effort leg behavior.
test/unit/qa-results-db.test.ts Adds unit tests for channel policy, gate evaluation, and JSONL round-trip/rebuild.
test/unit/qa-history.test.ts Adds unit tests for end-to-end accumulation + gating behavior across runs.
CHANGELOG.md Documents QA accumulation + must-pass gate and development joining push matrix.
GLOSSARY.txt Adds CI vocabulary entry for worktree used by the new QA job.
.github/instructions/ci-test-tiers-and-release-versioning.instructions.md Updates CI tier documentation to match new QA workflow behavior.

Comment thread scripts/qa-history.ts
Comment on lines +25 to +36
import { Database } from "bun:sqlite";
import { Glob } from "bun";
import {
allRuns,
channelPolicy,
channelStatuses,
evaluateMustPassGate,
parseHistoryJsonl,
type QaRunRow,
rebuildHistoryDb,
serializeRun,
} from "./qa-results-db.ts";
Comment thread scripts/qa-history.ts
Comment on lines +130 to +151
// Append this run to the accumulating history (written before the verdict so
// an `if: always()` commit persists even a failing run).
const existing = (await Bun.file(historyPath).exists())
? await Bun.file(historyPath).text()
: "";
const appended = currentRuns.map(serializeRun).join("\n");
const prefix =
existing && !existing.endsWith("\n") ? `${existing}\n` : existing;
const next = appended ? `${prefix}${appended}\n` : prefix;
await Bun.write(historyPath, next);

const historyDb = rebuildHistoryDb(parseHistoryJsonl(next));
const summary = gateSummaryMarkdown(
currentRuns,
historyDb,
gate.ok,
gate.failures,
);
const summaryPath = flag(args, "--summary");
if (summaryPath) {
await Bun.write(summaryPath, summary);
}
Comment thread scripts/qa-history.ts

@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: 2

🤖 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 203-213: Replace the mutable action version tags with immutable
commit SHAs in the accumulate-and-gate job. Change actions/checkout@v7 to a
pinned commit SHA reference and change actions/download-artifact@v7 to a pinned
commit SHA reference, following the same pattern as the oven-sh/setup-bun action
which is already correctly pinned with a commit SHA. This reduces supply-chain
exposure by ensuring these actions cannot be unexpectedly modified.

In `@scripts/qa-history.ts`:
- Around line 44-50: The Database handle opened with `new Database(path, {
readonly: true })` is not guaranteed to be closed if `allRuns(db)` throws an
error. To fix this, add a `finally` block after the `catch` block to ensure
`db.close()` is always executed regardless of whether an error occurs during the
try block. This prevents file handle leaks when scanning many artifacts.
🪄 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: f6f08f79-edd3-4d71-9470-73ba5654b6fb

📥 Commits

Reviewing files that changed from the base of the PR and between a14bba4 and 4b808b1.

📒 Files selected for processing (8)
  • .github/instructions/ci-test-tiers-and-release-versioning.instructions.md
  • .github/workflows/qa.yaml
  • CHANGELOG.md
  • GLOSSARY.txt
  • scripts/qa-history.ts
  • scripts/qa-results-db.ts
  • test/unit/qa-history.test.ts
  • test/unit/qa-results-db.test.ts

Comment thread .github/workflows/qa.yaml
Comment on lines +203 to +213
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Setup Bun 1.3.13
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # setup-bun action v2.2.0
with:
bun-version: 1.3.13
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Download per-leg QA results
uses: actions/download-artifact@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:

#!/bin/bash
# Verify all unpinned workflow action references (tag-based uses).
rg -nP '^\s*uses:\s*[^@\s]+@v[0-9]+(\.[0-9]+){0,2}\s*$' .github/workflows

Repository: tikoci/centrs

Length of output: 1797


🏁 Script executed:

sed -n '190,215p' .github/workflows/qa.yaml

Repository: tikoci/centrs

Length of output: 1010


Pin action references to immutable SHAs in the accumulate-and-gate job.

actions/checkout@v7 and actions/download-artifact@v7 are mutable tags. In this merge-gating pipeline, these should be commit-SHA pinned to reduce supply-chain exposure.

🧰 Tools
🪛 zizmor (1.25.2)

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

(unpinned-uses)


[error] 213-213: 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 203 - 213, Replace the mutable action
version tags with immutable commit SHAs in the accumulate-and-gate job. Change
actions/checkout@v7 to a pinned commit SHA reference and change
actions/download-artifact@v7 to a pinned commit SHA reference, following the
same pattern as the oven-sh/setup-bun action which is already correctly pinned
with a commit SHA. This reduces supply-chain exposure by ensuring these actions
cannot be unexpectedly modified.

Source: Linters/SAST tools

Comment thread scripts/qa-history.ts
Comment on lines +44 to +50
try {
const db = new Database(path, { readonly: true });
runs.push(...allRuns(db));
db.close();
} catch (error) {
console.error(`::warning title=QA history::skipped ${path}: ${error}`);
}

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 | 🟡 Minor | ⚡ Quick win

Ensure DB handles are closed on read failures.

If allRuns(db) throws, the current code skips db.close(), which can leak file handles while scanning many artifacts.

Suggested fix
 for await (const rel of glob.scan({ cwd: legsDir, onlyFiles: true })) {
 	const path = `${legsDir}/${rel}`;
+	let db: Database | undefined;
 	try {
-		const db = new Database(path, { readonly: true });
+		db = new Database(path, { readonly: true });
 		runs.push(...allRuns(db));
-		db.close();
 	} catch (error) {
 		console.error(`::warning title=QA history::skipped ${path}: ${error}`);
+	} finally {
+		db?.close();
 	}
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
const db = new Database(path, { readonly: true });
runs.push(...allRuns(db));
db.close();
} catch (error) {
console.error(`::warning title=QA history::skipped ${path}: ${error}`);
}
let db: Database | undefined;
try {
db = new Database(path, { readonly: true });
runs.push(...allRuns(db));
} catch (error) {
console.error(`::warning title=QA history::skipped ${path}: ${error}`);
} finally {
db?.close();
}
🤖 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 `@scripts/qa-history.ts` around lines 44 - 50, The Database handle opened with
`new Database(path, { readonly: true })` is not guaranteed to be closed if
`allRuns(db)` throws an error. To fix this, add a `finally` block after the
`catch` block to ensure `db.close()` is always executed regardless of whether an
error occurs during the try block. This prevents file handle leaks when scanning
many artifacts.

CodeRabbit/Copilot: if allRuns(db) throws while scanning a per-leg artifact,
db.close() was skipped, leaking the handle. Move the close into a finally and
stringify the error explicitly.

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

Copy link
Copy Markdown
Contributor Author

Review dispositions

Fixededc74de:

  • DB handle leak on read failure (CodeRabbit qa-history.ts:50, Copilot :51): collectLegRuns now closes the leg DB in a finally and stringifies the error.

Resolved on merit (declined, with reason):

  • Bun.write parent-dir creation (Copilot qa-history.ts:36/151): not applicable to Bun — Bun.write() creates intermediate directories. Proven by test/unit/qa-history.test.ts"accumulates across runs", which writes qa-history/qa-runs.jsonl into a qa-history/ dir that does not exist beforehand and passes. (Unlike node:fs writeFile; the gen-error-pages.ts mkdir predates this and isn't required for Bun.)
  • SHA-pin actions/checkout@v7 / actions/download-artifact@v7 (CodeRabbit/zizmor qa.yaml:203/213): declined to keep repo convention consistent. This repo deliberately tag-pins first-party actions/* (every actions/checkout, actions/upload-artifact, actions/cache, actions/setup-node across all 5 workflows is a @vN tag) and SHA-pins only third-party actions (oven-sh/setup-bun). This is the same disposition made on ci(qa): OS axis on scheduled tests + bun:sqlite channel↔version results store (JG-18) #52. Pinning only these two new lines would be inconsistent; a SHA-pin policy should be a separate repo-wide change (incl. the dependabot config) rather than landing piecemeal here.

…ot recency)

The file header still described the old intended policy ("current long-term and
newer must pass") — which is recency-based and contradicts what this file now
implements: the gate is maturity-based (released channels gate; pre-release is
best-effort regardless of how new its version is). Recency matters only for
which pre-release channels are worth sampling. Also note testing/development are
not monotonically ordered.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@mobileskyfi
mobileskyfi merged commit 7dbe870 into main Jun 21, 2026
11 of 12 checks passed
@mobileskyfi
mobileskyfi deleted the ci-qa-accumulation-gate branch June 21, 2026 16:26
mobileskyfi added a commit that referenced this pull request Jun 22, 2026
…arts (#77)

The release-tier job calls qa.yaml as a reusable workflow. Since #70, qa.yaml's
accumulate-and-gate job requests contents:write (it commits results to the
qa-history branch). A called workflow can't be granted more permission than the
calling job, and release.yaml's workflow default is contents:read, so the v0.1.0
tag run failed at startup ("workflow file issue"). Grant the calling release-tier
job contents:write; the publish job keeps contents:read + id-token:write.

First surfaced now because the 2026-06-19 release dry-run predated the qa.yaml
accumulate job.

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