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
2 changes: 1 addition & 1 deletion .github/CI-AUDIT.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ This document summarizes the **current inventory** and **stabilization measures*
|------|---------|-----------|
| [`workflows/ci.yml`](workflows/ci.yml) | `push`/`pull_request` **main**, tags, `workflow_dispatch` | `security` → `quality` (matrix) → `build` \| `e2e` \| `storybook` \| `mutation` → `lighthouse` \| `vrt` → `deploy` (main) |
| [`workflows/tauri-build.yml`](workflows/tauri-build.yml) | `workflow_dispatch`, tags `v*` | Desktop bundles (Linux/Win/macOS) |
| [`workflows/prune-deployments.yml`](workflows/prune-deployments.yml) | `workflow_run` (after CI/CD), weekly cron, `workflow_dispatch` | Prune ALL environments (Production/Preview/github-pages); keeps latest 3 per env |
| [`workflows/prune-deployments.yml`](workflows/prune-deployments.yml) | daily cron 03:00 UTC and `workflow_dispatch`, both only on `main`; no `workflow_run` | Reconciles GitHub Pages deployment records only (newest three plus the proven current and rollback publication and the evidence holds); read-only while the destructive gate is closed, see [`docs/PAGES-DEPLOYMENT-RECORD-PRUNING.md`](../docs/PAGES-DEPLOYMENT-RECORD-PRUNING.md) |
| [`workflows/storybook-debug.yml`](workflows/storybook-debug.yml) | `workflow_dispatch` only | Cloud-first Storybook debug: configurable workers/retries, PWDEBUG output |
| [`workflows/scorecard.yml`](workflows/scorecard.yml) | weekly cron, `workflow_dispatch` | OpenSSF Scorecard → GitHub Code Scanning SARIF (publish_results=false; api.scorecard.dev timed out on push runs) |
| [`workflows/codeql.yml`](workflows/codeql.yml) | `push`/`pull_request` main, weekly cron | CodeQL JS/TS SAST |
Expand Down
4 changes: 4 additions & 0 deletions .github/pages-pruning-holds.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"rollback": [],
"evidence": [6995722425, 6984593438, 6983983689, 6969003237, 6965361606, 6961183719]
}
104 changes: 26 additions & 78 deletions .github/workflows/prune-deployments.yml
Original file line number Diff line number Diff line change
@@ -1,96 +1,44 @@
name: Prune old GitHub deployment records

on:
workflow_run:
workflows: [CI / CD]
types: [completed]
schedule:
# Weekly cleanup on Sundays at 03:00 UTC
- cron: '0 3 * * 0'
# Daily reconciliation includes the Sunday 03:00 UTC backlog pass.
- cron: '0 3 * * *'
workflow_dispatch:
Comment thread
qnbs marked this conversation as resolved.

# QNBS-v3: top-level read-only; deployments:write scoped to job (CodeQL Token-Permissions).
# QNBS-v3: top-level read-only; the job holds no write scope while the destructive gate in scripts/pages-pruning/policy.mjs is closed (CodeQL Token-Permissions).
permissions:
contents: read

concurrency:
group: github-pages-record-pruning
cancel-in-progress: false
Comment thread
qnbs marked this conversation as resolved.

jobs:
prune:
name: Keep latest 3 GitHub deployment records
name: Reconcile GitHub Pages records only
if: github.repository == 'qnbs/WorldScript-Studio' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
deployments: write
contents: read
actions: read
pages: read
deployments: read
steps:
- name: Delete stale GitHub deployment records — keep latest 3 per environment
# QNBS-v3: v9.0.0 uses node24 — v7 was node20 (deprecated June 2026)
- name: Check out immutable trusted main execution tree
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.sha }}
persist-credentials: false
- name: Plan or reconcile bounded Pages records
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
retries: 0
script: |
const KEEP = 3;

// QNBS-v3: GitHub API only; Vercel artifacts require the protected #627 procedure.
// QNBS-v3: Paginate all GitHub deployment records to prevent retention drift.
const allDeployments = await github.paginate(
github.rest.repos.listDeployments,
{
owner: context.repo.owner,
repo: context.repo.repo,
per_page: 100,
},
);

// Group by environment, sort each group newest-first, keep the first KEEP
const byEnv = {};
for (const dep of allDeployments) {
const env = dep.environment || 'unknown';
(byEnv[env] ??= []).push(dep);
}

let totalDeleted = 0;
let totalFailed = 0;

for (const [env, deps] of Object.entries(byEnv)) {
const sorted = deps.sort((a, b) => b.id - a.id);
const toDelete = sorted.slice(KEEP);

if (toDelete.length === 0) {
core.info(`[${env}] Nothing to prune — only ${deps.length} deployment(s).`);
continue;
}

core.info(`[${env}] ${deps.length} total → pruning ${toDelete.length}, keeping ${KEEP} latest.`);

let deleted = 0;
let failed = 0;
for (const dep of toDelete) {
// A deployment must be inactive before it can be deleted
try {
await github.rest.repos.createDeploymentStatus({
owner: context.repo.owner,
repo: context.repo.repo,
deployment_id: dep.id,
state: 'inactive',
});
} catch (e) {
core.warning(`[${env}] Could not inactivate ${dep.id}: ${e.message}`);
}

try {
await github.rest.repos.deleteDeployment({
owner: context.repo.owner,
repo: context.repo.repo,
deployment_id: dep.id,
});
deleted++;
} catch (e) {
core.warning(`[${env}] Could not delete ${dep.id}: ${e.message}`);
failed++;
}
}

core.info(`[${env}] Done: ${deleted} deleted, ${failed} failed.`);
totalDeleted += deleted;
totalFailed += failed;
}

core.info(`All environments: ${totalDeleted} deleted, ${totalFailed} failed.`);
const { readFileSync } = require('node:fs');
const { pathToFileURL } = require('node:url');
const root = process.env.GITHUB_WORKSPACE;
const { runAction } = await import(pathToFileURL(`${root}/scripts/prune-pages-deployments.mjs`).href);
const holds = JSON.parse(readFileSync(`${root}/.github/pages-pruning-holds.json`, 'utf8'));
await runAction({ github, context, core, holds, workflowRef: process.env.GITHUB_WORKFLOW_REF });
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
name one exact deployment ID revalidated against live data. AGENTS.md and the
deployment, merge and Cursor Cloud runbooks point to it. PR #1031.

- **CI / Pages:** reconcile owned deployment records safely, keeping the newest three
plus current publication, rollback and evidence holds. Daily bounded reconciliation
replaces broad CI-completion triggers; manual dispatch is read-only. Both publication
slots are proven against the triggering run's actor before any candidate is classified,
so a forged status can neither consume the rollback slot nor vouch for a record.
Failed inactivation stops deletion, and every mutation revalidates the candidate, both
slots and trusted-main authority within a measured per-run request budget.
Destructive writes stay closed in code and in the workflow token until the owner admits a
writer-fence protocol, so even an enabled schedule only reports. Issue #1028. PR #1029.

- **Security:** update quinn-proto to the OSV-fixed release 0.11.18 (RUSTSEC-2026-0338 to -0342). The crate is a transitive dependency through `tauri-plugin-http`, `reqwest` 0.12 and `quinn`; the lockfile-only update changes no manifest, feature, or toolchain pin and unblocks the required OSV scan. PR #1030.

- **R-15 Gate 4E evidence:** coordination-resource preservation checks inspect links without following them and reject nonempty files, FIFOs, sockets, and observation errors. Fixture-scoped regressions cover missing/empty resources and link variants. The ordinary-operation barrier is unchanged. Clean successor to PR #1021. PR #1026.
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<img src="https://img.shields.io/badge/TypeScript-tsgo_native_preview-3178C6?logo=typescript&logoColor=white" alt="TypeScript native preview (tsgo)">
<img src="https://img.shields.io/badge/Desktop-Tauri_2-FFC131?logo=tauri&logoColor=black" alt="Tauri 2">
<img src="https://img.shields.io/badge/i18n-19_locales-2947_keys-0EA5E9" alt="19 locales — 2947 keys">
<img src="https://img.shields.io/badge/Tests-8547%2B_%2F_637_files-22C55E" alt="8547+ tests / 637 files">
<img src="https://img.shields.io/badge/Tests-8548%2B_%2F_638_files-22C55E" alt="8548+ tests / 638 files">
<img src="https://img.shields.io/codecov/c/github/qnbs/WorldScript-Studio?logo=codecov&label=Coverage" alt="Codecov Coverage">
<img src="https://img.shields.io/github/actions/workflow/status/qnbs/WorldScript-Studio/.github/workflows/ci.yml?branch=main&logo=github&label=CI" alt="CI status">
<img src="https://img.shields.io/badge/License-MIT-22C55E" alt="MIT License">
Expand Down Expand Up @@ -809,7 +809,7 @@ The normal web PR pipeline is not the complete native release qualification surf
| DOCX | `docx` + JSZip | Word-compatible export |
| PWA | Service Worker + Web App Manifest | Offline shell/installability |
| i18n | Custom React i18n context | 2947 keys × 19 locales |
| Testing | Vitest 5.x (8547+ tests / 637 files) + Playwright | Unit/integration/E2E |
| Testing | Vitest 5.x (8548+ tests / 638 files) + Playwright | Unit/integration/E2E |
| Quality | Biome + tsgo + CodeQL/security tooling | Static and CI gates |
| Desktop | Tauri 2 | Current native shell |

Expand Down Expand Up @@ -886,7 +886,7 @@ WorldScript-Studio/
├── locales/ # Source locale trees
├── public/ # PWA assets, manifest, SW, runtime locale bundles
├── tests/
│ ├── unit/ # Vitest unit tests (the 8547+ total also includes components/ and packages/*/tests; tests/e2e is excluded)
│ ├── unit/ # Vitest unit tests (the 8548+ total also includes components/ and packages/*/tests; tests/e2e is excluded)
│ └── e2e/ # Playwright
├── docs/ # Canonical product/engineering documentation + ADRs
├── src-tauri/ # Tauri v2 desktop shell / Rust
Expand Down Expand Up @@ -1031,7 +1031,7 @@ Raw bundle-budget ceilings (KB per uncompressed asset): entry **2500 KB**, vendo

Current source-synchronized README metrics:

- **8547+ unit tests** across **637 test files**
- **8548+ unit tests** across **638 test files**
- i18n: **2947 keys × 19 locales**

CI remains authoritative for actual pass/fail and live coverage.
Expand Down
Loading
Loading