mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-10 03:27:59 +00:00
Five reviewers (maintainability, reliability, testing, agent-native,
adversarial) flagged that ci-report.yml's sticky PR comment ignores
scope-parity entirely. Add scope_parity_result to the artifact read
step, render it in the Pipeline Status table, and include it in the
OVERALL pass/fail condition. Skipped is now visualised distinctly from
cancelled (skipped → ⏭, cancelled → 🛑).
Add `.github/scripts/check-language-filters.py` and wire it into
ci-quality.yml's workflow-convention job. The script parses
MIGRATED_LANGUAGES from registry-primary-flag.ts and asserts:
1. ci.yml has a per-language filter block for every migrated slug
2. ci.yml's FULL_LANG_LIST env var matches
3. ci-scope-parity.yml's changed-languages default matches
This catches the most common drift mode: adding a language to
MIGRATED_LANGUAGES without updating the two YAML hardcoded lists,
which would silently make scope-parity skip the new language. Negative-
tested with a synthetic flag.ts that adds Kotlin without updating the
filter blocks — script correctly fails with all three location-
specific errors.
Move the resolver test-file existence check from the parity matrix
job into the discover job, iterating the full MIGRATED_LANGUAGES set
rather than just the filtered matrix. A missing test file now fails
the gate even on docs-only PRs that prune the matrix to empty,
holding the invariant regardless of which slugs were filtered.
Dismissed (verified out-of-scope): the performance reviewer's
suggestion to replace setup-gitnexus with a lighter setup in the
discover job. gitnexus-shared/package.json exports only ./dist/, so
`tsx` cannot resolve `import 'gitnexus-shared'` without the build
step. The optimisation would require modifying gitnexus-shared's
exports field to expose ./src/, which is a separate refactor.
206 lines
9.9 KiB
YAML
206 lines
9.9 KiB
YAML
name: Scope Resolution Parity
|
|
|
|
# Reusable workflow — called from ci.yml. Does NOT declare concurrency;
|
|
# it inherits the caller's concurrency group per the convention documented
|
|
# in CONTRIBUTING.md → "GitHub Actions — Concurrency Convention".
|
|
#
|
|
# ── Purpose (RFC #909 Ring 3, §6.4 "Observability gates") ──────────────
|
|
# For every language in `MIGRATED_LANGUAGES` (exported from
|
|
# `gitnexus/src/core/ingestion/registry-primary-flag.ts`), run the
|
|
# resolver integration test at `test/integration/resolvers/<slug>.test.ts`
|
|
# TWICE on every PR:
|
|
#
|
|
# 1. `REGISTRY_PRIMARY_<LANG>=0` — legacy DAG path (guarantees we haven't
|
|
# broken the old path while migrating). Known legacy gaps may be skipped
|
|
# through the resolver test helper's expected-failure list.
|
|
# 2. `REGISTRY_PRIMARY_<LANG>=1` — registry-primary path (guarantees the
|
|
# new path carries the same behavior — the parity gate).
|
|
#
|
|
# BOTH must pass. The source of truth is the TypeScript constant — adding
|
|
# a language to that `Set` is the ONLY contributor action; CI auto-
|
|
# discovers it, runs parity, and the language's default production path
|
|
# flips to registry-primary in the same change.
|
|
#
|
|
# ── Path-aware matrix pruning ─────────────────────────────────────────
|
|
# `ci.yml` computes change detection ONCE per run and passes:
|
|
# - `shared-resolution-changed` — when 'true', shared ingestion code
|
|
# changed and ALL migrated languages must run (any could regress).
|
|
# - `changed-languages` — JSON array of language slugs whose source or
|
|
# resolver-test files changed (e.g. `["python","go"]`).
|
|
#
|
|
# The discover job intersects MIGRATED_LANGUAGES with `changed-languages`
|
|
# unless `shared-resolution-changed=true`, in which case the full matrix
|
|
# runs. On non-PR callers (release-candidate.yml) both inputs default to
|
|
# the full set, so the full matrix runs there too.
|
|
#
|
|
# When the filtered matrix is empty, the matrix job is skipped via the
|
|
# `if: needs.discover.outputs.languages != '[]'` guard. The outer workflow
|
|
# conclusion stays `success` so the `ci-status` aggregator in ci.yml
|
|
# passes the scope-parity gate.
|
|
|
|
on:
|
|
workflow_call:
|
|
inputs:
|
|
shared-resolution-changed:
|
|
description: >-
|
|
When `'true'`, shared ingestion/resolution code changed and the
|
|
full migrated-languages matrix must run. Conservatively defaults
|
|
to `'true'` so any caller that omits the input gets the full
|
|
battery.
|
|
required: false
|
|
type: string
|
|
default: 'true'
|
|
changed-languages:
|
|
description: >-
|
|
JSON array of migrated language slugs whose source or resolver
|
|
test files changed on this PR (e.g. `["python","go"]`). Used to
|
|
prune the parity matrix when `shared-resolution-changed` is
|
|
`'false'`. Defaults to the full set so omitting the input runs
|
|
everything.
|
|
required: false
|
|
type: string
|
|
default: '["python","csharp","typescript","go","c","cpp","php"]'
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
discover:
|
|
name: Discover migrated languages
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 5
|
|
outputs:
|
|
languages: ${{ steps.read.outputs.languages }}
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
- uses: ./.github/actions/setup-gitnexus
|
|
|
|
- name: Extract MIGRATED_LANGUAGES and filter by changed paths
|
|
id: read
|
|
shell: bash
|
|
working-directory: gitnexus
|
|
env:
|
|
SHARED_CHANGED: ${{ inputs.shared-resolution-changed }}
|
|
CHANGED_LANGS: ${{ inputs.changed-languages }}
|
|
run: |
|
|
set -euo pipefail
|
|
# `tsx` evaluates the TS source directly (no build step), imports
|
|
# the exported `Set`, and emits a GH-Actions-friendly JSON matrix
|
|
# array: [{slug, envvar}, ...].
|
|
ALL_LANGS=$(npx tsx scripts/ci-list-migrated-languages.ts)
|
|
# Validate the discovery script's output is a well-formed JSON
|
|
# array. A crash or schema regression here would otherwise
|
|
# propagate to `fromJSON('')` at matrix expansion time, where
|
|
# the failure surface is much harder to read.
|
|
if ! printf '%s' "$ALL_LANGS" | jq -e 'type == "array"' >/dev/null; then
|
|
echo "::error::ci-list-migrated-languages.ts produced non-array output: $ALL_LANGS"
|
|
exit 1
|
|
fi
|
|
ALL_COUNT=$(printf '%s' "$ALL_LANGS" | jq 'length')
|
|
echo "MIGRATED_LANGUAGES: $ALL_LANGS ($ALL_COUNT entries)"
|
|
|
|
# Shared-resolution change forces the full matrix — any change
|
|
# to shared ingestion code could regress any language's parity.
|
|
if [ "${SHARED_CHANGED:-true}" = "true" ]; then
|
|
LANGS="$ALL_LANGS"
|
|
REASON="shared-resolution changed → running full matrix"
|
|
else
|
|
# Validate the caller-supplied CHANGED_LANGS is a JSON array
|
|
# of strings. Malformed input would otherwise crash `jq
|
|
# --argjson` with a hard-to-diagnose parse error inside the
|
|
# filter pipeline.
|
|
if ! printf '%s' "${CHANGED_LANGS:-[]}" | jq -e 'type == "array" and all(type == "string")' >/dev/null; then
|
|
echo "::error::changed-languages input is not a JSON array of strings: ${CHANGED_LANGS:-<unset>}"
|
|
exit 1
|
|
fi
|
|
# Intersect MIGRATED_LANGUAGES with the changed-languages list.
|
|
# `IN(.slug; $changed[])` is strict equality membership — using
|
|
# `inside()` here would substring-match (`"c"` would match
|
|
# `"cpp"` because the `c` string is a prefix), which would
|
|
# over-trigger the matrix. The empty-list case is handled by
|
|
# the parity job's `if: != '[]'` guard.
|
|
LANGS=$(jq -c --argjson changed "${CHANGED_LANGS:-[]}" \
|
|
'[.[] | select(IN(.slug; $changed[]))]' <<< "$ALL_LANGS")
|
|
REASON="pruned by changed-languages: $CHANGED_LANGS"
|
|
fi
|
|
|
|
echo "languages=$LANGS" >> "$GITHUB_OUTPUT"
|
|
COUNT=$(printf '%s' "$LANGS" | jq 'length')
|
|
echo "Parity matrix entries: $COUNT ($REASON)"
|
|
|
|
# Resolver test-file existence is verified against the FULL
|
|
# MIGRATED_LANGUAGES set (ALL_LANGS), not just the filtered
|
|
# matrix. This catches a missing test file even on a docs-only
|
|
# PR that prunes the matrix to empty — otherwise the invariant
|
|
# could only fail on a PR that happens to touch the affected
|
|
# language. The check runs from repo root because the test
|
|
# paths are relative to `gitnexus/`.
|
|
while IFS= read -r slug; do
|
|
TEST_FILE="test/integration/resolvers/${slug}.test.ts"
|
|
if [[ ! -f "$TEST_FILE" ]]; then
|
|
echo "::error title=Missing resolver test::Expected gitnexus/$TEST_FILE for migrated language '$slug'. Either fix the slug, add the test file, or remove the language from MIGRATED_LANGUAGES."
|
|
exit 1
|
|
fi
|
|
done < <(printf '%s' "$ALL_LANGS" | jq -r '.[].slug')
|
|
|
|
# Job-summary line so reviewers and agents can see which
|
|
# languages actually ran without scraping logs.
|
|
{
|
|
echo "### Scope-parity matrix"
|
|
echo ""
|
|
echo "- **Entries:** $COUNT of $ALL_COUNT migrated languages"
|
|
echo "- **Reason:** $REASON"
|
|
if [ "$COUNT" -gt 0 ]; then
|
|
SLUGS=$(printf '%s' "$LANGS" | jq -r 'map(.slug) | join(", ")')
|
|
echo "- **Slugs:** $SLUGS"
|
|
fi
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
parity:
|
|
name: ${{ matrix.lang.slug }} parity
|
|
needs: discover
|
|
# Empty-matrix guard. When the filter produced no entries, the matrix
|
|
# job MUST skip — `fromJSON('[]')` in `strategy.matrix` would otherwise
|
|
# crash with "Matrix vector does not contain any values"
|
|
# (community#27096). Comparing the JSON string against `'[]'` is the
|
|
# idiomatic, documented pattern for this guard.
|
|
if: needs.discover.outputs.languages != '[]'
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 20
|
|
strategy:
|
|
# One language failing must not abort the others — we want the full
|
|
# parity matrix result on a single CI run so a reviewer sees every
|
|
# regression at once rather than one-at-a-time.
|
|
fail-fast: false
|
|
matrix:
|
|
lang: ${{ fromJSON(needs.discover.outputs.languages) }}
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
- uses: ./.github/actions/setup-gitnexus
|
|
with:
|
|
build: 'true'
|
|
|
|
# Resolver test-file existence is now validated up front in the
|
|
# `discover` job against the full MIGRATED_LANGUAGES set, so a
|
|
# missing file fails before any matrix runner spins up — and the
|
|
# invariant holds even on docs-only PRs that prune the matrix to
|
|
# empty.
|
|
|
|
- name: Resolver tests — legacy DAG (REGISTRY_PRIMARY_${{ matrix.lang.envvar }}=0)
|
|
shell: bash
|
|
working-directory: gitnexus
|
|
env:
|
|
FLAG_NAME: REGISTRY_PRIMARY_${{ matrix.lang.envvar }}
|
|
# Explicitly force the flag to `0` even though it also defaults to
|
|
# `MIGRATED_LANGUAGES.has(lang)` — once a language is in the set,
|
|
# the default flips to registry-primary, so an unset env var would
|
|
# silently re-run the same path as step #2. `env FOO=0 cmd` spawns
|
|
# `cmd` with the override scoped to just this invocation.
|
|
run: env "$FLAG_NAME=0" npx vitest run "test/integration/resolvers/${{ matrix.lang.slug }}.test.ts"
|
|
|
|
- name: Resolver tests — registry-primary (REGISTRY_PRIMARY_${{ matrix.lang.envvar }}=1)
|
|
shell: bash
|
|
working-directory: gitnexus
|
|
env:
|
|
FLAG_NAME: REGISTRY_PRIMARY_${{ matrix.lang.envvar }}
|
|
run: env "$FLAG_NAME=1" npx vitest run "test/integration/resolvers/${{ matrix.lang.slug }}.test.ts"
|