diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..f33b45d --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,150 @@ +# Continuous integration. +# +# Two jobs, split by language, because they prove different things and the +# Python one is the slow half. +# +# What this actually gates, in order of how likely each is to catch something: +# +# 1. The two implementations of the game agree. `engine.py` and `engine.ts` +# score every ordered pair of words in the answer list — 4,603^2 = 21.2M +# feedback patterns — and their SHA-256 digests must match each other and +# the committed value. The page claims it VERIFIES a recorded run in your +# browser; that claim is only worth something if the browser's scorer and +# the environment's scorer cannot drift. +# 2. The reward measures something. `probe.py` plays seven deliberately +# crafted policies and asserts the reward orders them the way a person +# would, PER WEIGHTED COMPONENT — a component that is flat across every +# policy is measuring nothing and still moves the total. It also asserts +# neither good policy dominates the other, because the reward editor on +# the site is a claim about a real trade-off. +# 3. Every committed fixture replays through the engine and reproduces its +# own recorded rewards. A fixture that cannot be regenerated is a claim +# with no receipt behind it. +# 4. The word lists rebuild byte-identically from their committed sources. +# If they do not, the conformance digest is describing a different game. +# 5. The demo contract holds — see scripts/check-demos.mjs. +# 6. Four things that fail SILENTLY in production and nowhere else: +# prerendered routes carrying their own , sitemap.xml existing, +# no blob-backed worker, and the bundle budget. +# +# NO MODEL IS EVER CALLED. Rollouts are captured by hand and committed; a gate +# that cost a paid inference call would be switched off within a month. +# +# This runner is aarch64 and configured with `container.network: host`. Nothing +# here needs a service container, so the traps that cost PIG three failed runs +# do not apply — but do not add `services:` here without reading that repo's +# workflow first. + +name: ci + +on: + push: + branches: [main] + pull_request: + +jobs: + web: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: '22' + + # Corepack ships with Node and installs the exact pnpm pinned by + # `packageManager`, so CI and a laptop run the same version. The download + # prompt is disabled because a non-interactive runner cannot answer it and + # would otherwise hang until the job times out. + - name: Enable pnpm + env: + COREPACK_ENABLE_DOWNLOAD_PROMPT: '0' + run: | + corepack enable + corepack prepare --activate + test "$(pnpm -v)" = "11.21.0" || { echo "pnpm $(pnpm -v) != 11.21.0"; exit 1; } + + - name: Install + env: + CI: 'true' + run: pnpm install --frozen-lockfile + + # `pnpm build` ends in the prerender pass, which drives a real browser. + # Without this the build fails on its very last action, after everything + # else has gone green — the most expensive place to find a missing + # dependency. + - name: Install chromium + run: pnpm exec playwright install --with-deps chromium + + - run: pnpm typecheck + - run: pnpm check + - run: pnpm test + - run: pnpm build + - run: node scripts/bundle-budget.mjs + + # Crawlers do not run JavaScript. Without a real per-route HTML file every + # shared link previews as the homepage, and the failure is invisible from + # inside the app. + - name: The prerendered head is real + run: | + test -f dist/demos/wordle/index.html || { echo "no prerendered demo route"; exit 1; } + grep -q 'og:title' dist/demos/wordle/index.html || { echo "og tags missing"; exit 1; } + grep -q 'Word Five' dist/demos/wordle/index.html \ + || { echo "the demo route kept the homepage title"; exit 1; } + test -f dist/404.html || { echo "no 404.html"; exit 1; } + test -f dist/sitemap.xml || { echo "no sitemap.xml"; exit 1; } + + # The site's CSP has no `worker-src`, so it falls back to + # `default-src 'self'` and a blob-backed worker is blocked with no console + # error — in production only. Grep for construction FROM a blob, not for + # the string anywhere: React's bundle contains it in a scheme check, and a + # check that fails on a risk which is not present teaches everyone to + # ignore it. + - name: No blob-backed workers + run: | + if grep -rEo "new (Shared)?Worker\([^)]{0,80}" dist/assets/*.js \ + | grep -E "blob:|createObjectURL"; then + echo "a worker is constructed from a blob URL; production CSP blocks it silently" + exit 1 + fi + echo "ok — no blob-backed worker construction in the bundle" + + python: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install uv + run: | + curl -LsSf https://astral.sh/uv/install.sh | sh + echo "$HOME/.local/bin" >> "$GITHUB_PATH" + + - run: uv sync --all-packages + + # If these do not reproduce, every downstream number — the conformance + # digest included — is describing a different game. + - name: The word lists rebuild identically + run: | + uv run python envs/wordle_five/words/build_words.py + git diff --exit-code envs/wordle_five/words/*.json + + - run: uv run pytest envs/wordle_five/tests -q + - run: uv run python envs/probe.py + - run: uv run python envs/verify_fixtures.py + + # The other half of the cross-language gate. Node scores the same 21.2M + # pairs and must produce the digest Python committed. + - uses: actions/setup-node@v4 + with: + node-version: '22' + - name: Enable pnpm + env: + COREPACK_ENABLE_DOWNLOAD_PROMPT: '0' + run: | + corepack enable + corepack prepare --activate + - name: Install + env: + CI: 'true' + run: pnpm install --frozen-lockfile + - run: pnpm conformance diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index a5c652a..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,105 +0,0 @@ -name: ci - -on: - push: - branches: [main] - pull_request: - -concurrency: - group: ci-${{ github.ref }} - cancel-in-progress: true - -jobs: - web: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 - - uses: pnpm/action-setup@v4 - with: { version: 11.21.0 } - - uses: actions/setup-node@v5 - with: { node-version: 22, cache: pnpm } - - # Asserts the toolchain matches what package.json pins, rather than - # discovering a mismatch three steps later as an unrelated build error. - - name: preflight - run: | - test "$(pnpm -v)" = "11.21.0" || { echo "pnpm $(pnpm -v) != 11.21.0"; exit 1; } - - - run: CI=true pnpm install --frozen-lockfile - - # `pnpm build` ends in the prerender pass, which drives a real browser. - # Without this the build fails at the very last step, after everything - # else has passed, with a Playwright message about installing browsers. - - run: pnpm exec playwright install --with-deps chromium - - - run: pnpm typecheck - - run: pnpm check - - run: pnpm test - - run: pnpm build - - run: node scripts/bundle-budget.mjs - - # The prerender pass writes a real HTML file per route. Crawlers do not - # run JavaScript, so without these every shared link previews as the - # homepage — assert the baked tags actually landed. - - name: prerendered head is real - run: | - test -f dist/demos/wordle/index.html || { echo "no prerendered demo route"; exit 1; } - grep -q 'og:title' dist/demos/wordle/index.html || { echo "og tags missing"; exit 1; } - grep -qv 'PIG Demo — RL environments you can play' dist/demos/wordle/index.html \ - || { echo "demo route kept the homepage title"; exit 1; } - test -f dist/404.html || { echo "no 404.html"; exit 1; } - # sitemap.xml is written by a build step, not by Vite copying public/. - # It went missing once because the step was outside `pnpm build`, and - # nothing noticed until a 404 on the live host. - test -f dist/sitemap.xml || { echo "no sitemap.xml"; exit 1; } - - # A blob-backed worker is blocked in production and nowhere else: the - # site's CSP has no worker-src, so it falls back to default-src 'self'. - # The failure is silent — the worker simply never boots. - # - # Grep for worker construction FROM a blob, not for the string `blob:` - # anywhere. React's own bundle contains that string in a URL-scheme check, - # so the broad version fails on a risk that is not present, which teaches - # everyone to ignore it. - - name: no blob-backed workers - run: | - if grep -rEo "new (Shared)?Worker\([^)]{0,80}" dist/assets/*.js \ - | grep -E "blob:|createObjectURL"; then - echo "a worker is constructed from a blob URL; production CSP blocks it silently" - exit 1 - fi - echo "ok — no blob-backed worker construction in the bundle" - - python: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 - - uses: astral-sh/setup-uv@v6 - with: { enable-cache: true } - - - run: uv sync --all-packages - - # Rebuild the word lists from their committed sources and assert the - # output is byte-identical. If it is not, every downstream number — - # the conformance digest included — is describing a different game. - - name: word lists rebuild identically - run: | - uv run python envs/wordle_five/words/build_words.py - git diff --exit-code envs/wordle_five/words/*.json - - - run: uv run pytest envs/wordle_five/tests -q - - run: uv run python envs/probe.py - - # The cross-language gate. Both halves score all 21.2M (guess, answer) - # pairs; the digests must match each other and the committed value. - - uses: pnpm/action-setup@v4 - with: { version: 11.21.0 } - - uses: actions/setup-node@v5 - with: { node-version: 22, cache: pnpm } - - run: CI=true pnpm install --frozen-lockfile - - run: pnpm conformance - - # Every committed fixture must replay through the Python engine and - # reproduce its own recorded rewards. A fixture that cannot be - # regenerated is a claim with no receipt behind it. - - run: uv run python envs/verify_fixtures.py diff --git a/NOTICE b/NOTICE index ec80ce6..43213fc 100644 --- a/NOTICE +++ b/NOTICE @@ -43,7 +43,7 @@ https://github.com/recharts/recharts -------------------------------------------------------------------------------- PIG (Prime Intellect Growth) — Apache-2.0 -https://github.com/karti-ai/PIG-Demo is a sibling of that project. The design +https://git.karti.ai/PIG/PIG-Demo is a sibling of that project. The design token layer in src/index.css and the palette in tailwind.config.js are derived from it. Same author; the notice is here because this repository is public and Apache-2.0 section 4(d) asks for it either way. diff --git a/README.md b/README.md index 69ccbdb..e51e632 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,9 @@ scores above zero. Full accounting: [demo.primeintellectgrowth.com/honesty](https://demo.primeintellectgrowth.com/honesty) +Also served at [demo.lumbridgecorp.com](https://demo.lumbridgecorp.com); the +canonical name is the first one and every page says so in its ``. + ## Adding a demo ```bash diff --git a/index.html b/index.html index 6b752b8..fb39e1c 100644 --- a/index.html +++ b/index.html @@ -29,10 +29,10 @@

PIG Demo

These demos are interactive and need JavaScript. The substance behind them does not.

Every demo on this site ships a real verifiers environment and a set of recorded rollouts, both in the repository. You can run the environment yourself:

-
git clone https://github.com/karti-ai/PIG-Demo
+        
git clone https://git.karti.ai/PIG/PIG-Demo
 cd PIG-Demo && uv sync --all-packages
 uv run vf-eval wordle-five -n 8
-

Read the source on GitHub →

+

Read the source →

diff --git a/src/components/demo/DemoErrorBoundary.tsx b/src/components/demo/DemoErrorBoundary.tsx index 0dc22da..62c479c 100644 --- a/src/components/demo/DemoErrorBoundary.tsx +++ b/src/components/demo/DemoErrorBoundary.tsx @@ -3,7 +3,7 @@ import type { ErrorInfo, ReactNode } from 'react'; import { AlertTriangle, ExternalLink, RotateCcw } from 'lucide-react'; import { Button } from '@/components/ui/button'; -const REPO_URL = 'https://github.com/karti-ai/PIG-Demo'; +const REPO_URL = 'https://git.karti.ai/PIG/PIG-Demo'; export interface DemoErrorBoundaryProps { children: ReactNode; diff --git a/src/components/demo/DemoShell.tsx b/src/components/demo/DemoShell.tsx index befd147..32f5f84 100644 --- a/src/components/demo/DemoShell.tsx +++ b/src/components/demo/DemoShell.tsx @@ -35,7 +35,7 @@ import { RecordedBadge, TracePlayer } from './TracePlayer'; import { VerifyBadge } from './VerifyBadge'; import { formatOrDash, useIsDesktop } from './format'; -const REPO_BLOB = 'https://github.com/karti-ai/PIG-Demo/blob/main/'; +const REPO_BLOB = 'https://git.karti.ai/PIG/PIG-Demo/src/branch/main/'; /** The tab the step-detail strip opens on. Kept out of the URL when it is this. */ const DEFAULT_DETAIL_TAB = 'reasoning'; diff --git a/src/components/site/SiteFooter.tsx b/src/components/site/SiteFooter.tsx index 9d21b54..22afce3 100644 --- a/src/components/site/SiteFooter.tsx +++ b/src/components/site/SiteFooter.tsx @@ -1,5 +1,5 @@ import { Link } from 'react-router-dom'; -import { Github } from 'lucide-react'; +import { GitBranch } from 'lucide-react'; import { Separator } from '@/components/ui/separator'; import { PIG_URL, REPO_URL } from '@/components/site/links'; @@ -24,8 +24,8 @@ export function SiteFooter() { target="_blank" rel="noreferrer noopener" > -