Skip to content

release: v0.3.0-beta.1 - #156

Merged
gnanirahulnutakki merged 12 commits into
mainfrom
dev
Jul 14, 2026
Merged

release: v0.3.0-beta.1#156
gnanirahulnutakki merged 12 commits into
mainfrom
dev

Conversation

@gnanirahulnutakki

@gnanirahulnutakki gnanirahulnutakki commented Jul 14, 2026

Copy link
Copy Markdown
Member

Beta release boundary

Promotes the current tested integration branch to main for the signed v0.3.0-beta.1 prerelease.

  • macOS Apple Silicon is a first-class archive target: darwin_arm64 is built with the same checksum, SPDX SBOM, Sigstore bundle, and attestation chain as the other platforms
  • feat(e9): publish signed beta prereleases #155 adds the narrow beta-tag policy: only vMAJOR.MINOR.PATCH-beta.N; it remains a GitHub prerelease and cannot replace the latest stable release
  • no mutable tag, unsigned tag, release from dev, Homebrew beta channel, or registry publication is included

Integration evidence

  • exact dev CI on 42d3951: 29364444017 passed
  • exact dev CodeQL on 42d3951: 29364462265 passed
  • Dependabot, code scanning, and secret scanning: 0 / 0 / 0

After this PR merges, the tagger will run make ci and make release-check on main, verify SSH tagger identity, push an annotated signed v0.3.0-beta.1 tag, and verify the published macOS-arm64 artifact locally.

Summary by CodeRabbit

  • New Features
    • Added tenant-scoped CVE searches by exact image digest and exact canonical CVE identifier.
    • Extended governed-hub and fleet data to include bounded immutable CVE facts, including normalized severity and coverage semantics.
    • Added authenticated hub HTTP routes for CVE lookup with privacy-preserving, normalized responses.
  • Release
    • Improved release-tag validation and beta publishing behavior to avoid updating “latest” for beta prereleases.
  • Documentation
    • Updated release and roadmap guidance for stable vs beta tag naming and CVE fact/search semantics.
  • Tests
    • Expanded unit/integration/e2e and release-policy scripts, including stricter coverage assessment validation and CVE evidence handling.
  • CI
    • Broadened CI triggers to run on both dev and main for pushes and PRs.

Gnani Rahul and others added 10 commits July 14, 2026 11:49
Verify the configured tagger email and SSH signing key against GitHub before a release tag exists. Distinguish local tag validation from GitHub tag-object verification and retain immutable patch-version recovery.

GSTACK-Checkpoint: 2026-07-14/release-tag-identity#1
Signed-off-by: Gnani Rahul <gnani.nutakki@gmail.com>
…ag-identity

docs(release): preflight GitHub SSH tag identity
Export deterministic gap detail from normalized coverage so policy layers can conservatively abstain without reinterpreting query counters. Preserve stale and unreachable scope evidence, detect unaccounted and inconsistent metadata fail closed, and prove the behavior with unit and real Kind-federation scenarios.

GSTACK-Checkpoint: 2026-07-14/e2-coverage-assessment#1
Signed-off-by: Gnani Rahul <gnani.nutakki@gmail.com>
Merged after green hosted CI and CodeQL, a clean local peer review, forced-RLS isolation, release reproducibility, and real two-cluster validation.
GSTACK-Checkpoint: 2026-07-14/e2-vulnerability-report-facts#1
Signed-off-by: Gnani Rahul <gnani.nutakki@gmail.com>
GSTACK-Checkpoint: 2026-07-14/e2-exact-cve-search#1
Signed-off-by: Gnani Rahul <gnani.nutakki@gmail.com>
…cve-search

feat(e2): search runtime-proven CVE facts
GSTACK-Checkpoint: 2026-07-14/e9-beta-prerelease#1
Signed-off-by: Gnani Rahul <gnani.nutakki@gmail.com>
…rerelease

feat(e9): publish signed beta prereleases
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

CVE fact vertical

Layer / File(s) Summary
CVE contracts and evidence validation
internal/fleet/*, internal/hubfleet/collector*
Adds canonical CVE normalization, validation, and snapshot evidence checks.
OCM vulnerability-report projection
internal/hubocm/*, hack/experiments/m0-ocm-falsification.sh
Collects bounded vulnerability reports, matches runtime digests, aggregates CVE facts, and adds fixtures and integration coverage.
CVE persistence and query selectors
internal/hubdb/*
Adds exact CVE selectors, indexes, and workspace-isolation tests.
Authorized CVE search API
internal/hubfleet/cve_search.go, internal/hubserver/fleet.go, internal/hubruntime/*, internal/pep/pep.go
Adds PEP-gated image and identifier searches, HTTP routes, runtime wiring, and tests.
CVE contract documentation
README.md, docs/ROADMAP.md, sessions/*
Documents CVE scope, route semantics, coverage behavior, and verification evidence.

Coverage assessment

Layer / File(s) Summary
Coverage assessment contract
internal/fleet/model.go, internal/fleet/coverage_test.go
Adds fail-closed coverage gap classification, inconsistency detection, and non-aliasing tests.
Coverage end-to-end verification
tests/e2e/kind_read_federation_test.go, sessions/2026-07-14-e2-coverage-assessment.md
Validates stale and unreachable coverage through Coverage.Assessment() and records the assessment work.

Beta release publication

Layer / File(s) Summary
Tag classification and workflow branching
hack/release-tag-classify.sh, .github/workflows/release.yml, Makefile
Classifies stable and beta tags and publishes beta releases without marking them latest.
Release tag identity procedure
docs/RELEASE.md, sessions/2026-07-14-release-tag-identity.md
Documents identity preflight, SSH-signed annotated tags, GitHub verification, and immutable failure handling.
Release policy and CI tests
tests/scripts/*, .github/workflows/ci.yml, sessions/2026-07-14-e9-*
Tests release-tag and release-PR policies and enables CI validation for dev and main branches.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related issues

  • Issue 152 — Adds exact canonical CVE-identifier search routes, persistence support, runtime wiring, and tests.
  • Issue 154 — Adds canonical beta-tag classification and beta publication without replacing the latest stable release.
  • Issue 150 — Adds vulnerability-report CVE fact collection, normalization, persistence, search, fixtures, and tests.

Sequence Diagram(s)

sequenceDiagram
  participant OCM
  participant DirectAdapter
  participant FleetDB
  participant CVESearcher
  participant FleetHandler
  OCM->>DirectAdapter: list VulnerabilityReport resources
  DirectAdapter->>DirectAdapter: match runtime image digest and normalize CVE facts
  DirectAdapter->>FleetDB: persist FactCVE snapshot evidence
  FleetHandler->>CVESearcher: authorize and execute exact CVE search
  CVESearcher->>FleetDB: query FactCVE by digest or identifier
  FleetDB-->>FleetHandler: return facts and coverage
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.96% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and clearly identifies the release and beta version targeted by the changeset.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev

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

GSTACK-Checkpoint: 2026-07-14/e9-release-pr-ci-gate#1
Signed-off-by: Gnani Rahul <gnani.nutakki@gmail.com>

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

🧹 Nitpick comments (1)
internal/hubserver/fleet.go (1)

190-235: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Extract the repeated unescape/escape round-trip check into a helper.

The url.PathUnescape + re-url.PathEscape comparison (rejecting non-canonical percent-encoding) is duplicated for the workspace, CVE identifier, and image digest segments. Since this check is what prevents route-confusion via double-encoding, consolidating it into one helper reduces the risk that a future route addition forgets it.

func canonicalPathSegment(segment string) (string, bool) {
	value, err := url.PathUnescape(segment)
	if err != nil || url.PathEscape(value) != segment {
		return "", false
	}
	return value, true
}
🤖 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 `@internal/hubserver/fleet.go` around lines 190 - 235, Extract the repeated
url.PathUnescape and url.PathEscape canonicalization logic into a
canonicalPathSegment helper. Update the workspace, CVE identifier, and image
digest parsing in the route parser to use this helper while preserving their
existing validation and rejection behavior.
🤖 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 `@docs/RELEASE.md`:
- Around line 127-140: Make the shell snippet fail closed by enabling strict
error handling with set -euo pipefail near the start, or explicitly exiting on
failure for every test and grep command. Ensure failures in identity validation,
email lookup, and signing-key verification stop execution rather than allowing
the snippet to report success.

In `@internal/hubdb/fleet.go`:
- Around line 335-345: Add a default branch to the Image-selector switch in
queryFacts that fails closed when query.Kinds[0] is not fleet.FactInventory or
fleet.FactCVE. Ensure unexpected kinds cannot omit the image condition and
broaden the query; use the existing error/validation mechanism for reporting the
invalid selector kind.

In `@internal/hubocm/direct.go`:
- Around line 482-562: Update collectCVEFacts so a fleet.CanonicalCVEObservation
error for one aggregated digest is skipped rather than returned, allowing
processing to continue for remaining digests and preserving non-CVE facts. Keep
JSON marshaling errors and successful evidence generation behavior unchanged.

In `@tests/scripts/release_tag_policy_test.sh`:
- Around line 51-54: Strengthen the release workflow assertions around the
tested classifier so tests/scripts/release_tag_policy_test.sh verifies that
RELEASE_KIND is exported and consumed, the beta branch is selected, and
prerelease=true is set. Keep the existing classifier invocation and
make_latest=false checks, ensuring the complete beta publication contract is
covered.

---

Nitpick comments:
In `@internal/hubserver/fleet.go`:
- Around line 190-235: Extract the repeated url.PathUnescape and url.PathEscape
canonicalization logic into a canonicalPathSegment helper. Update the workspace,
CVE identifier, and image digest parsing in the route parser to use this helper
while preserving their existing validation and rejection behavior.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7df15ba0-9e99-446d-b0b9-4c364b011141

📥 Commits

Reviewing files that changed from the base of the PR and between 045778d and 42d3951.

📒 Files selected for processing (36)
  • .github/workflows/release.yml
  • Makefile
  • README.md
  • docs/RELEASE.md
  • docs/ROADMAP.md
  • hack/experiments/m0-ocm-falsification.sh
  • hack/release-tag-classify.sh
  • internal/fleet/coverage_test.go
  • internal/fleet/cve.go
  • internal/fleet/cve_test.go
  • internal/fleet/model.go
  • internal/hubdb/fleet.go
  • internal/hubdb/fleet_query_test.go
  • internal/hubdb/migrations/0007_fleet_cve_images.sql
  • internal/hubdb/migrations/0008_fleet_cve_identifiers.sql
  • internal/hubdb/postgres_integration_test.go
  • internal/hubfleet/collector.go
  • internal/hubfleet/collector_test.go
  • internal/hubfleet/cve_search.go
  • internal/hubfleet/cve_search_test.go
  • internal/hubocm/direct.go
  • internal/hubocm/direct_test.go
  • internal/hubocm/ocm_integration_test.go
  • internal/hubruntime/config.go
  • internal/hubruntime/ocm_integration_test.go
  • internal/hubserver/fleet.go
  • internal/hubserver/fleet_test.go
  • internal/pep/pep.go
  • sessions/2026-07-14-e2-coverage-assessment.md
  • sessions/2026-07-14-e2-exact-cve-search.md
  • sessions/2026-07-14-e2-vulnerability-report-facts.md
  • sessions/2026-07-14-e9-beta-prerelease.md
  • sessions/2026-07-14-release-tag-identity.md
  • tests/e2e/kind_read_federation_test.go
  • tests/scripts/release_tag_identity_guide_test.sh
  • tests/scripts/release_tag_policy_test.sh

Comment thread docs/RELEASE.md
Comment on lines +127 to +140
```bash
tagger_email="$(git config user.email)"
test -n "$tagger_email"
test "$(git config gpg.format)" = ssh
signing_key_file="$(git config user.signingkey)"
test -f "$signing_key_file"
signing_key="$(awk '{print $1 " " $2}' "$signing_key_file")"
verified_emails="$(gh api user/emails --paginate \
--jq '.[] | select(.verified) | .email')"
grep -Fxq -- "$tagger_email" <<<"$verified_emails"
github_login="$(gh api user --jq '.login')"
registered_signing_keys="$(gh api "users/${github_login}/ssh_signing_keys" \
--paginate --jq '.[].key' | awk '{print $1 " " $2}')"
grep -Fxq -- "$signing_key" <<<"$registered_signing_keys"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -e

# Inspect the relevant section of docs/RELEASE.md with line numbers.
wc -l docs/RELEASE.md
sed -n '110,155p' docs/RELEASE.md | cat -n

Repository: ArdurAI/sith

Length of output: 3559


🏁 Script executed:

#!/bin/bash
set -e

# Read the immediate surrounding context to see whether the block is wrapped
# in error handling or only shown as a standalone preflight.
sed -n '120,150p' docs/RELEASE.md

Repository: ArdurAI/sith

Length of output: 2213


Make this snippet fail closed. Add set -euo pipefail or explicit || exit 1 checks so a failed test/grep cannot fall through and report success on invalid local identity config.

🤖 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 `@docs/RELEASE.md` around lines 127 - 140, Make the shell snippet fail closed
by enabling strict error handling with set -euo pipefail near the start, or
explicitly exiting on failure for every test and grep command. Ensure failures
in identity validation, email lookup, and signing-key verification stop
execution rather than allowing the snippet to report success.

Comment thread internal/hubdb/fleet.go
Comment on lines 335 to 345
if query.Selector.Image != "" {
conditions = append(conditions, "fact.payload->'image_digests' ? "+placeholder(query.Selector.Image))
switch query.Kinds[0] {
case fleet.FactInventory:
conditions = append(conditions, "fact.payload->'image_digests' ? "+placeholder(query.Selector.Image))
case fleet.FactCVE:
conditions = append(conditions, "fact.payload->>'image' = "+placeholder(query.Selector.Image))
}
}
if query.Selector.CVE != "" {
conditions = append(conditions, "(fact.payload -> 'ids') ? "+placeholder(query.Selector.CVE))
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Add a default case to the Image-selector switch to fail closed.

queryFacts relies entirely on normalizeFleetQuery having already restricted query.Kinds[0] to FactInventory/FactCVE whenever Selector.Image != "". If that invariant is ever broken by a future change, this switch silently omits the image filter instead of failing, letting the query return facts scoped only by kind/workspace — broader than the "exact selector" contract these endpoints (image/CVE search) are supposed to enforce.

🛡️ Proposed fix: fail closed on unexpected kinds
 	if query.Selector.Image != "" {
 		switch query.Kinds[0] {
 		case fleet.FactInventory:
 			conditions = append(conditions, "fact.payload->'image_digests' ? "+placeholder(query.Selector.Image))
 		case fleet.FactCVE:
 			conditions = append(conditions, "fact.payload->>'image' = "+placeholder(query.Selector.Image))
+		default:
+			return nil, fmt.Errorf("image selector is not supported for fact kind %q", query.Kinds[0])
 		}
 	}
📝 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
if query.Selector.Image != "" {
conditions = append(conditions, "fact.payload->'image_digests' ? "+placeholder(query.Selector.Image))
switch query.Kinds[0] {
case fleet.FactInventory:
conditions = append(conditions, "fact.payload->'image_digests' ? "+placeholder(query.Selector.Image))
case fleet.FactCVE:
conditions = append(conditions, "fact.payload->>'image' = "+placeholder(query.Selector.Image))
}
}
if query.Selector.CVE != "" {
conditions = append(conditions, "(fact.payload -> 'ids') ? "+placeholder(query.Selector.CVE))
}
if query.Selector.Image != "" {
switch query.Kinds[0] {
case fleet.FactInventory:
conditions = append(conditions, "fact.payload->'image_digests' ? "+placeholder(query.Selector.Image))
case fleet.FactCVE:
conditions = append(conditions, "fact.payload->>'image' = "+placeholder(query.Selector.Image))
default:
return nil, fmt.Errorf("image selector is not supported for fact kind %q", query.Kinds[0])
}
}
if query.Selector.CVE != "" {
conditions = append(conditions, "(fact.payload -> 'ids') ? "+placeholder(query.Selector.CVE))
}
🤖 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 `@internal/hubdb/fleet.go` around lines 335 - 345, Add a default branch to the
Image-selector switch in queryFacts that fails closed when query.Kinds[0] is not
fleet.FactInventory or fleet.FactCVE. Ensure unexpected kinds cannot omit the
image condition and broaden the query; use the existing error/validation
mechanism for reporting the invalid selector kind.

Comment thread internal/hubocm/direct.go
Comment on lines +482 to +562
func collectCVEFacts(
ctx context.Context,
client snapshotClient,
spokeID string,
knownDigests map[string]struct{},
observedAt time.Time,
) ([]fleet.Evidence, error) {
if len(knownDigests) == 0 {
return nil, nil
}
remaining := maxVulnerabilityReports
continueToken := ""
aggregates := make(map[string]cveAggregate)
for pages := 0; ; pages++ {
if pages >= maxVulnerabilityReportPages {
return nil, fmt.Errorf("direct OCM snapshot exceeds the bounded vulnerability report page limit")
}
page, err := client.ListVulnerabilityReports(ctx, vulnerabilityReportListOptions(continueToken, remaining))
if apierrors.IsNotFound(err) && continueToken == "" {
return []fleet.Evidence{}, nil
}
if err != nil {
return nil, contextOrGeneric(ctx, "list vulnerability reports")
}
if len(page.Items) > remaining || (len(page.Items) == remaining && page.GetContinue() != "") {
return nil, fmt.Errorf("direct OCM snapshot exceeds the bounded vulnerability report limit")
}
for index := range page.Items {
digest, identifiers, severity, found := reportCVEObservation(page.Items[index], knownDigests)
if !found {
continue
}
aggregate := aggregates[digest]
if aggregate.identifiers == nil {
aggregate.identifiers = make(map[string]struct{}, len(identifiers))
}
for _, identifier := range identifiers {
aggregate.identifiers[identifier] = struct{}{}
}
if cveSeverityRank(severity) > cveSeverityRank(aggregate.severity) {
aggregate.severity = severity
}
aggregates[digest] = aggregate
}
remaining -= len(page.Items)
continueToken = page.GetContinue()
if continueToken == "" {
break
}
}
digests := make([]string, 0, len(aggregates))
for digest := range aggregates {
digests = append(digests, digest)
}
sort.Strings(digests)
facts := make([]fleet.Evidence, 0, len(digests))
for _, digest := range digests {
aggregate := aggregates[digest]
identifiers := make([]string, 0, len(aggregate.identifiers))
for identifier := range aggregate.identifiers {
identifiers = append(identifiers, identifier)
}
observation, err := fleet.CanonicalCVEObservation(digest, identifiers, aggregate.severity)
if err != nil {
return nil, fmt.Errorf("normalize vulnerability report observation: %w", err)
}
payload, err := json.Marshal(observation)
if err != nil {
return nil, fmt.Errorf("encode normalized vulnerability report observation: %w", err)
}
facts = append(facts, fleet.Evidence{
Ref: fleet.ResourceRef{SourceKind: hubfleet.SourceKind, Scope: spokeID, Kind: "Image", Name: digest},
Kind: fleet.FactCVE,
Observed: payload,
ObservedAt: observedAt,
Source: spokeID,
Provenance: fleet.Provenance{Adapter: hubfleet.SourceKind, ProtocolV: protocolVersion},
})
}
return facts, nil
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: check maxCVEIdentifiers bound and how likely it is to be exceeded by aggregation.
rg -n 'maxCVEIdentifiers' internal/fleet/cve.go

Repository: ArdurAI/sith

Length of output: 549


Skip one bad CVE digest instead of failing the whole snapshot
collectCVEFacts returns an error when fleet.CanonicalCVEObservation rejects an aggregated digest (for example, if the merged ID set exceeds maxCVEIdentifiers), which aborts the entire spoke snapshot and drops already-collected non-CVE facts. Consider skipping just that digest’s CVE evidence and continuing with the rest.

🤖 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 `@internal/hubocm/direct.go` around lines 482 - 562, Update collectCVEFacts so
a fleet.CanonicalCVEObservation error for one aggregated digest is skipped
rather than returned, allowing processing to continue for remaining digests and
preserving non-CVE facts. Keep JSON marshaling errors and successful evidence
generation behavior unchanged.

Comment on lines +51 to +54
grep -Fq 'hack/release-tag-classify.sh "$GITHUB_REF_NAME"' "${workflow}"
printf '[release-tag-policy] PASS: workflow invokes the tested classifier\n'
grep -Fq -- '-f make_latest=false' "${workflow}"
printf '[release-tag-policy] PASS: beta publication cannot replace latest stable\n'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Assert the complete beta publication contract.

This test would still pass if RELEASE_KIND stopped being exported or consumed, or if prerelease=true were removed. That could publish a beta as a normal release. Add assertions for the environment export, beta branch, and prerelease field.

Proposed test assertions
 grep -Fq 'hack/release-tag-classify.sh "$GITHUB_REF_NAME"' "${workflow}"
 printf '[release-tag-policy] PASS: workflow invokes the tested classifier\n'
+grep -Fq 'echo "RELEASE_KIND=${release_kind}" >> "$GITHUB_ENV"' "${workflow}"
+grep -Fq 'if [[ "$RELEASE_KIND" == "beta" ]]' "${workflow}"
 grep -Fq -- '-f make_latest=false' "${workflow}"
 printf '[release-tag-policy] PASS: beta publication cannot replace latest stable\n'
+grep -Fq -- '-F prerelease=true' "${workflow}"
+printf '[release-tag-policy] PASS: beta publication remains a prerelease\n'
📝 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
grep -Fq 'hack/release-tag-classify.sh "$GITHUB_REF_NAME"' "${workflow}"
printf '[release-tag-policy] PASS: workflow invokes the tested classifier\n'
grep -Fq -- '-f make_latest=false' "${workflow}"
printf '[release-tag-policy] PASS: beta publication cannot replace latest stable\n'
grep -Fq 'hack/release-tag-classify.sh "$GITHUB_REF_NAME"' "${workflow}"
printf '[release-tag-policy] PASS: workflow invokes the tested classifier\n'
grep -Fq 'echo "RELEASE_KIND=${release_kind}" >> "$GITHUB_ENV"' "${workflow}"
grep -Fq 'if [[ "$RELEASE_KIND" == "beta" ]]' "${workflow}"
grep -Fq -- '-f make_latest=false' "${workflow}"
printf '[release-tag-policy] PASS: beta publication cannot replace latest stable\n'
grep -Fq -- '-F prerelease=true' "${workflow}"
printf '[release-tag-policy] PASS: beta publication remains a prerelease\n'
🧰 Tools
🪛 Shellcheck (0.11.0)

[info] 51-51: Expressions don't expand in single quotes, use double quotes for that.

(SC2016)

🤖 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 `@tests/scripts/release_tag_policy_test.sh` around lines 51 - 54, Strengthen
the release workflow assertions around the tested classifier so
tests/scripts/release_tag_policy_test.sh verifies that RELEASE_KIND is exported
and consumed, the beta branch is selected, and prerelease=true is set. Keep the
existing classifier invocation and make_latest=false checks, ensuring the
complete beta publication contract is covered.

…gate

ci(e9): gate dev-to-main release PRs

@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

🧹 Nitpick comments (1)
.github/workflows/ci.yml (1)

5-7: 🎯 Functional Correctness | 🔵 Trivial

Verify that CI checks are required by branch protection.

These triggers schedule CI for dev and main, but do not themselves prevent a release PR from merging with failed or missing checks. Confirm that the main ruleset/branch protection requires the complete CI workflow before merge and release promotion.

🤖 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/ci.yml around lines 5 - 7, Verify the branch protection or
ruleset configuration for main and require the complete CI workflow checks
before merging or promoting releases. Keep the existing workflow triggers for
dev and main unchanged, and update the repository protection settings rather
than relying on the pull_request configuration alone.
🤖 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 `@sessions/2026-07-14-e9-release-pr-ci-gate.md`:
- Around line 46-47: Remove the leading “#” from the sentence beginning “#157”
in the release documentation so it remains a paragraph and no longer parses as a
malformed ATX heading. Preserve the issue reference and the existing sentence
content.

In `@tests/scripts/release_pr_gate_policy_test.sh`:
- Around line 13-14: Update the branch checks in the release policy test so they
match only active YAML branches keys under the relevant push and pull_request
sections, not commented or unrelated text. Anchor each regex to the YAML key and
preserve the existing dev/main branch validation.

---

Nitpick comments:
In @.github/workflows/ci.yml:
- Around line 5-7: Verify the branch protection or ruleset configuration for
main and require the complete CI workflow checks before merging or promoting
releases. Keep the existing workflow triggers for dev and main unchanged, and
update the repository protection settings rather than relying on the
pull_request configuration alone.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7c2651d2-cbbe-4d80-8ce7-318dc1746ae9

📥 Commits

Reviewing files that changed from the base of the PR and between 42d3951 and ff07b60.

📒 Files selected for processing (5)
  • .github/workflows/ci.yml
  • Makefile
  • docs/RELEASE.md
  • sessions/2026-07-14-e9-release-pr-ci-gate.md
  • tests/scripts/release_pr_gate_policy_test.sh
🚧 Files skipped from review as they are similar to previous changes (2)
  • Makefile
  • docs/RELEASE.md

Comment on lines +46 to +47
#157 closes only after the resulting `dev` to `main` release PR has an independently green full CI
run and a later exact `main` push CI record is available for the beta release commit.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Fix the malformed Markdown issue reference.

#157 is not a valid ATX heading prefix and triggers MD018. Keep this as a paragraph, for example:

-#157 closes only after the resulting `dev` to `main` release PR has an independently green full CI
+Issue `#157` closes only after the resulting `dev` to `main` release PR has an independently green full CI
📝 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
#157 closes only after the resulting `dev` to `main` release PR has an independently green full CI
run and a later exact `main` push CI record is available for the beta release commit.
Issue `#157` closes only after the resulting `dev` to `main` release PR has an independently green full CI
run and a later exact `main` push CI record is available for the beta release commit.
🧰 Tools
🪛 markdownlint-cli2 (0.23.0)

[warning] 46-46: No space after hash on atx style heading

(MD018, no-missing-space-atx)

🤖 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 `@sessions/2026-07-14-e9-release-pr-ci-gate.md` around lines 46 - 47, Remove
the leading “#” from the sentence beginning “#157” in the release documentation
so it remains a paragraph and no longer parses as a malformed ATX heading.
Preserve the issue reference and the existing sentence content.

Source: Linters/SAST tools

Comment on lines +13 to +14
section == "push" && /branches: \[dev, main\]/ { push_ok = 1 }
section == "pull_request" && /branches: \[dev, main\]/ { pull_request_ok = 1 }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Anchor the branch assertions to active YAML keys.

The current regex also matches commented-out or unrelated text, so a removed trigger could still pass if branches: [dev, main] remains in a comment. Anchor the match to the YAML key, or use a YAML parser.

🔧 Proposed fix
-  section == "push" && /branches: \[dev, main\]/ { push_ok = 1 }
-  section == "pull_request" && /branches: \[dev, main\]/ { pull_request_ok = 1 }
+  section == "push" && /^[[:space:]]+branches:[[:space:]]*\[[[:space:]]*dev,[[:space:]]*main[[:space:]]*\][[:space:]]*$/ { push_ok = 1 }
+  section == "pull_request" && /^[[:space:]]+branches:[[:space:]]*\[[[:space:]]*dev,[[:space:]]*main[[:space:]]*\][[:space:]]*$/ { pull_request_ok = 1 }
📝 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
section == "push" && /branches: \[dev, main\]/ { push_ok = 1 }
section == "pull_request" && /branches: \[dev, main\]/ { pull_request_ok = 1 }
section == "push" && /^[[:space:]]+branches:[[:space:]]*\[[[:space:]]*dev,[[:space:]]*main[[:space:]]*\][[:space:]]*$/ { push_ok = 1 }
section == "pull_request" && /^[[:space:]]+branches:[[:space:]]*\[[[:space:]]*dev,[[:space:]]*main[[:space:]]*\][[:space:]]*$/ { pull_request_ok = 1 }
🤖 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 `@tests/scripts/release_pr_gate_policy_test.sh` around lines 13 - 14, Update
the branch checks in the release policy test so they match only active YAML
branches keys under the relevant push and pull_request sections, not commented
or unrelated text. Anchor each regex to the YAML key and preserve the existing
dev/main branch validation.

@gnanirahulnutakki
gnanirahulnutakki merged commit 5fe4008 into main Jul 14, 2026
12 checks passed
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.

1 participant