Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
quality/regression/fixtures/** text eol=lf
14 changes: 13 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ jobs:
- name: Install dependencies
run: npm ci

- name: Audit production dependencies
run: npm audit --omit=dev --audit-level=high

- name: Lint
run: npm run lint

Expand All @@ -54,15 +57,21 @@ jobs:
- name: Validate D1 migrations and database integrity
run: npm run validate:d1

- name: Validate pilot source manifests
- name: Validate recursive source manifests
run: npm run validate:manifests

- name: Run isolated extraction and prompt-injection regressions
run: npm run quality:synthetic-regression

- name: Validate scheduled maintenance capacity
run: npm run validate:maintenance

- name: Run ingestion Worker tests
run: npm run test:ingestion

- name: Run Entity Materializer tests
run: npm run test:entity-materializer

- name: Run publisher Worker tests
run: npm run test:publisher

Expand All @@ -72,6 +81,9 @@ jobs:
- name: Validate ingestion Worker bundle and bindings
run: npm run check:worker:ingestion

- name: Validate Entity Materializer Worker bundle and bindings
run: npm run check:worker:entity-materializer

- name: Validate publisher Worker bundle and bindings
run: npm run check:worker:publisher

Expand Down
7 changes: 5 additions & 2 deletions .github/workflows/cloudflare-backup.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@ jobs:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: |
test -n "$CLOUDFLARE_API_TOKEN"
test -n "$CLOUDFLARE_ACCOUNT_ID"
npx tsx scripts/cloudflare/backup-preflight.ts --phase credentials

- name: Export catalog and pipeline databases
shell: bash
Expand Down Expand Up @@ -76,6 +75,10 @@ jobs:
cd "$RUNNER_TEMP"
sha256sum catalog.sql.gz pipeline.sql.gz > backup-sha256.txt

- name: Verify backup artifacts
shell: bash
run: npx tsx scripts/cloudflare/backup-preflight.ts --phase artifacts --directory "$RUNNER_TEMP"

- name: Upload daily and monthly copies
shell: bash
env:
Expand Down
17 changes: 17 additions & 0 deletions .github/workflows/data-health.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ jobs:
uses: actions/setup-node@v6
with:
node-version: 24
cache: npm

- name: Install dependencies
run: npm ci --ignore-scripts

- name: Resolve audit mode
id: mode
Expand Down Expand Up @@ -86,6 +90,13 @@ jobs:
fi
node scripts/data-health.mjs "${args[@]}"

- name: Build platform quality scorecard
id: platform_scorecard
continue-on-error: true
run: >-
npm run quality:platform-scorecard --
--output "$RUNNER_TEMP/platform-data-quality.json"

- name: Add report to workflow summary
if: always()
shell: bash
Expand All @@ -106,6 +117,7 @@ jobs:
${{ runner.temp }}/link-health.md
${{ runner.temp }}/data-health.json
${{ runner.temp }}/data-health.md
${{ runner.temp }}/platform-data-quality.json
if-no-files-found: warn
retention-days: 30

Expand Down Expand Up @@ -181,6 +193,7 @@ jobs:
env:
DATA_HEALTH_OUTCOME: ${{ steps.data_health.outcome }}
LINK_CHECK_OUTCOME: ${{ steps.link_check.outcome }}
PLATFORM_SCORECARD_OUTCOME: ${{ steps.platform_scorecard.outcome }}
run: |
failed=0
if [[ "$DATA_HEALTH_OUTCOME" == "failure" ]]; then
Expand All @@ -191,4 +204,8 @@ jobs:
echo "One or more links returned a confirmed 404 or 410."
failed=1
fi
if [[ "$PLATFORM_SCORECARD_OUTCOME" == "failure" ]]; then
echo "The platform quality scorecard could not be generated."
failed=1
fi
exit "$failed"
49 changes: 37 additions & 12 deletions .github/workflows/program-fact-refresh.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ on:
default: '1000'

permissions:
contents: write
contents: read

concurrency:
group: weekly-program-fact-refresh
Expand All @@ -30,6 +30,7 @@ jobs:
uses: actions/checkout@v6
with:
ref: main
persist-credentials: false

- name: Use Node.js 24
uses: actions/setup-node@v6
Expand Down Expand Up @@ -67,7 +68,7 @@ jobs:
--review "$RUNNER_TEMP/current-program-review.json" \
--data-dir content/data \
--output-dir content/data \
--audit "quality/international-program-review/fact-enrichment-${{ steps.date.outputs.value }}.json" \
--audit "$RUNNER_TEMP/fact-enrichment-${{ steps.date.outputs.value }}.json" \
--checked-at "${{ steps.date.outputs.value }}" \
--max-urls "$MAXIMUM_URLS" \
--minimum-domain-interval-ms 5000
Expand All @@ -80,16 +81,40 @@ jobs:
tests/unit/content-data.test.ts \
tests/unit/publication.test.ts

- name: Commit verified changes
- name: Prepare validated refresh candidate
shell: bash
run: |
if git diff --quiet -- content/data quality/international-program-review; then
echo "No verified fact changes."
exit 0
set -euo pipefail
artifact_dir="$RUNNER_TEMP/program-fact-refresh-candidate"
mkdir -p "$artifact_dir/content/data" \
"$artifact_dir/quality/international-program-review"

cp "$RUNNER_TEMP/current-program-review.json" \
"$artifact_dir/current-program-review.json"
cp "$RUNNER_TEMP/fact-enrichment-${{ steps.date.outputs.value }}.json" \
"$artifact_dir/quality/international-program-review/"
cp content/data/programs.json content/data/admission-cycles.json \
"$artifact_dir/content/data/"

git diff --binary -- \
content/data/programs.json \
content/data/admission-cycles.json \
> "$artifact_dir/catalog-changes.patch"
git diff --stat -- \
content/data/programs.json \
content/data/admission-cycles.json \
> "$artifact_dir/change-summary.txt"

if git diff --quiet -- \
content/data/programs.json \
content/data/admission-cycles.json; then
echo 'No verified fact changes.' >> "$artifact_dir/change-summary.txt"
fi
git config user.name "studyinchina-data-bot"
git config user.email "studyinchina-data-bot@users.noreply.github.com"
git add content/data/programs.json content/data/admission-cycles.json \
"quality/international-program-review/fact-enrichment-${{ steps.date.outputs.value }}.json"
git commit -m "data: refresh official program facts ${{ steps.date.outputs.value }}"
git push origin HEAD:main

- name: Upload validated refresh candidate
uses: actions/upload-artifact@v6
with:
name: program-fact-refresh-${{ steps.date.outputs.value }}
path: ${{ runner.temp }}/program-fact-refresh-candidate
if-no-files-found: error
retention-days: 35
65 changes: 65 additions & 0 deletions .github/workflows/source-manifest-cohort-candidates.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: Build Source Manifest Candidate Cohort

on:
workflow_dispatch:
inputs:
checked_at:
description: Evidence check date in YYYY-MM-DD format
required: true
type: string

permissions:
contents: read

concurrency:
group: source-manifest-cohort-candidates
cancel-in-progress: false

jobs:
build-candidate-artifact:
runs-on: ubuntu-latest
timeout-minutes: 20
env:
CHECKED_AT: ${{ inputs.checked_at }}
steps:
- name: Checkout repository
uses: actions/checkout@v6

- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: 24
cache: npm

- name: Install dependencies
run: npm ci

- name: Validate locked official target registry
run: npm run validate:double-first-class

- name: Build review-only candidate artifact
shell: bash
env:
ARTIFACT_DIRECTORY: ${{ runner.temp }}/source-manifest-cohort-candidates
run: |
set -euo pipefail
npm run pipeline:build-source-manifest-candidates -- \
--checked-at "$CHECKED_AT" \
--artifact-output "$ARTIFACT_DIRECTORY"

- name: Verify checksums and publication safety
shell: bash
env:
ARTIFACT_DIRECTORY: ${{ runner.temp }}/source-manifest-cohort-candidates
run: |
set -euo pipefail
npm run pipeline:verify-source-manifest-candidates -- "$ARTIFACT_DIRECTORY"
git diff --exit-code -- content/source-manifests

- name: Upload review-only candidate bundle
uses: actions/upload-artifact@v6
with:
name: source-manifest-candidates-${{ github.run_id }}
path: ${{ runner.temp }}/source-manifest-cohort-candidates
if-no-files-found: error
retention-days: 14
45 changes: 39 additions & 6 deletions .github/workflows/vercel-production-alias.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,41 @@ jobs:
name: Point studyinchina.vercel.app to the successful main deployment
if: >-
github.event.deployment_status.state == 'success' &&
github.event.deployment.environment == 'Production' &&
github.event.deployment.ref == 'main'
github.event.deployment.environment == 'Production'
runs-on: ubuntu-latest
timeout-minutes: 10
env:
DEPLOYMENT_SHA: ${{ github.event.deployment.sha }}
DEPLOYMENT_URL: ${{ github.event.deployment_status.environment_url }}
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}

steps:
- name: Check out current main
uses: actions/checkout@v6
with:
ref: main
fetch-depth: 1
persist-credentials: false

- name: Verify deployment commit is current main
id: main
shell: bash
run: |
set -euo pipefail
main_sha="$(git rev-parse HEAD)"
if ! [[ "${DEPLOYMENT_SHA}" =~ ^[0-9a-f]{40}$ ]]; then
echo "Unexpected deployment SHA: ${DEPLOYMENT_SHA}" >&2
exit 1
fi
if [[ "${DEPLOYMENT_SHA}" == "${main_sha}" ]]; then
echo 'matches=true' >> "$GITHUB_OUTPUT"
exit 0
fi
echo 'matches=false' >> "$GITHUB_OUTPUT"
echo "::notice::Deployment ${DEPLOYMENT_SHA} is not current main ${main_sha}; the stable alias will not be changed."

- name: Detect alias credential
if: steps.main.outputs.matches == 'true'
id: credential
shell: bash
run: |
Expand All @@ -37,7 +62,9 @@ jobs:
fi

- name: Validate deployment URL
if: steps.credential.outputs.configured == 'true'
if: >-
steps.main.outputs.matches == 'true' &&
steps.credential.outputs.configured == 'true'
shell: bash
run: |
set -euo pipefail
Expand All @@ -47,13 +74,17 @@ jobs:
fi

- name: Use Node.js 24
if: steps.credential.outputs.configured == 'true'
if: >-
steps.main.outputs.matches == 'true' &&
steps.credential.outputs.configured == 'true'
uses: actions/setup-node@v6
with:
node-version: 24

- name: Promote stable production alias
if: steps.credential.outputs.configured == 'true'
if: >-
steps.main.outputs.matches == 'true' &&
steps.credential.outputs.configured == 'true'
shell: bash
run: |
set -euo pipefail
Expand All @@ -64,7 +95,9 @@ jobs:
--token "${VERCEL_TOKEN}"

- name: Verify public release API
if: steps.credential.outputs.configured == 'true'
if: >-
steps.main.outputs.matches == 'true' &&
steps.credential.outputs.configured == 'true'
shell: bash
run: |
set -euo pipefail
Expand Down
Loading
Loading