Math GDD Documentation Site Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Build and deploy a searchable static site for the four product-game math/GDD packages, including interactive presentation flows and verified links to authoritative documents.

Architecture: Rspress 2 renders selected Markdown from the repository root plus thin MDX catalogue/flow pages. Plain JavaScript manifests hold navigation metadata, React components render the catalogue and flows, and Rspress performs the primary dead-link check. GitLab CI builds an immutable nginx image and deploys it to the VPS through the existing label-discovered Caddy infrastructure.

Tech Stack: Node.js 24, pnpm 11.23.0, Rspress 2.0.19, React 19.2.8, TypeScript 7.0.2, Node test runner, nginx unprivileged, Docker Compose, GitLab CI.

Spec: docs/superpowers/specs/2026-08-23-math-gdd-site-design.md

Global Constraints

  • The catalogue contains exactly four product games: Classic Fruits 3×3, Magic Vault 5×3, Book of Odin v2, and Vault Breakers: Super Score. hold-and-win-reference appears only under reusable mechanics.
  • Existing Markdown under games/, docs/ and templates/ remains authoritative; site files may link to it but must not duplicate its math tables or evidence prose.
  • All dependencies are pinned by pnpm-lock.yaml; CI uses pnpm install --frozen-lockfile.
  • The site has no CDN, remote font, remote icon or runtime API dependency.
  • Generated output is doc_build/ and is never committed.
  • The container publishes no host port, mounts no host path, joins external network web, and exposes only port 8080 to that network.
  • Deployment uses the immutable $CI_COMMIT_SHORT_SHA image tag, never main, as the running identity.
  • chow.host is exactly math-gdd; no hand-written Caddy block is added, and the derived production-host policy supplies basic auth.
  • No file under pass/, ignored raw art/video output, or .git/ enters the image.
  • Existing game math, evidence and configuration identity files are not modified except for deliberate navigation links.

File Structure

package.json                         pinned commands and dependencies
pnpm-lock.yaml                       exact dependency graph
tsconfig.json                        strict TS/MDX checking
rspress.config.ts                    routes, navigation, dead-link policy, output
index.mdx                            custom catalogue home route
flows/
  classic-fruits-3x3.mdx             Classic Fruits flow route
  magic-vault-5x3.mdx                Magic Vault flow route
  book-of-odin.mdx                   Book of Odin flow route
  vault-breakers-super-score.mdx     Vault Breakers flow route
site/
  catalog.mjs                        four-game document/navigation manifest
  flows.mjs                          typed-by-shape presentation graph data
  components/GameCatalog.tsx         home catalogue renderer
  components/GameFlow.tsx            accessible flow renderer
  components/LineIcon.tsx            local monochrome SVG icons
  theme/index.tsx                    Rspress v2 theme re-export
  theme/index.css                    control-room visual system and responsive flow CSS
  tests/catalog.test.mjs             manifest and source-file contract tests
  tests/flows.test.mjs               flow graph/link/a11y-data tests
scripts/check-built-site.mjs         built-route and generated-link validation
tests/check-built-site.test.mjs      checker fixture tests
docker/nginx.conf                    SPA/static routing and health endpoint
Dockerfile                           multi-stage Rspress build + nginx runtime
.dockerignore                        image context boundary
deploy/docker-compose.yml            one immutable static-site container
deploy/README.md                     deploy, identity, health and rollback runbook
.gitlab-ci.yml                       check → image → deploy pipeline

Task 1: Establish the Rspress build and route boundary

Files:

  • Create: package.json
  • Create: pnpm-lock.yaml
  • Create: tsconfig.json
  • Create: rspress.config.ts
  • Create: index.mdx
  • Modify: .gitignore
  • Test: site/tests/catalog.test.mjs

Interfaces:

  • Produces: pnpm docs:dev, pnpm docs:build, pnpm typecheck, pnpm test; rendered output in doc_build/.

  • Produces: Rspress routes for index.mdx, flows/**/*.mdx, games/**/*.md, selected docs/**/*.md, and templates/**/*.md.

  • Consumes: the existing Markdown tree without moving or copying it.

  • Step 1: Write the failing build-contract test

Create site/tests/catalog.test.mjs with an initial filesystem contract:

import assert from 'node:assert/strict';
import { existsSync, readFileSync } from 'node:fs';
import test from 'node:test';

test('site toolchain declares the stable commands', () => {
  assert.equal(existsSync('package.json'), true);
  const pkg = JSON.parse(readFileSync('package.json', 'utf8'));
  assert.equal(pkg.packageManager, 'pnpm@11.23.0');
  assert.equal(pkg.scripts['docs:build'], 'rspress build');
  assert.equal(pkg.scripts.typecheck, 'tsc --noEmit');
});
  • Step 2: Run the test and verify the missing scaffold fails

Run: node --test site/tests/catalog.test.mjs

Expected: FAIL because package.json does not exist.

  • Step 3: Add the minimal pinned toolchain

Create package.json with:

{
  "name": "@kiro/math-gdd-site",
  "private": true,
  "type": "module",
  "packageManager": "pnpm@11.23.0",
  "scripts": {
    "docs:dev": "rspress dev --host 0.0.0.0",
    "docs:build": "rspress build",
    "typecheck": "tsc --noEmit",
    "test": "node --test site/tests/*.test.mjs",
    "check": "pnpm typecheck && pnpm test && pnpm docs:build && node scripts/check-built-site.mjs"
  },
  "devDependencies": {
    "@rspress/core": "2.0.19",
    "@types/react": "19.2.18",
    "@types/react-dom": "19.2.4",
    "react": "19.2.8",
    "react-dom": "19.2.8",
    "typescript": "7.0.2"
  }
}

Create strict tsconfig.json using moduleResolution: "bundler", jsx: "react-jsx", noEmit: true, strict: true, and include index.mdx, flows, site, and rspress.config.ts.

Create rspress.config.ts with repository root .; output doc_build; route includes limited to index.mdx, flows/**/*.mdx, games/**/*.md, docs/**/*.md, and templates/**/*.md; route excludes site/**, node_modules/**, doc_build/**, output/**, draft-v1/**, and ggd ігри/**. Enable both dead-link and anchor checking:

markdown: {
  link: {
    checkDeadLinks: true,
    checkAnchors: true,
  },
},

Create a temporary index.mdx containing an H1 and links to the four existing game READMEs. Append node_modules/, doc_build/, and .rspress-cache/ to .gitignore.

Run corepack enable && pnpm install once to produce pnpm-lock.yaml.

  • Step 4: Verify the toolchain and route scan

Run:

node --test site/tests/catalog.test.mjs
pnpm typecheck
pnpm docs:build
test -f doc_build/index.html

Expected: all commands exit 0 and doc_build/index.html exists.

  • Step 5: Commit the build foundation
git add package.json pnpm-lock.yaml tsconfig.json rspress.config.ts index.mdx .gitignore site/tests/catalog.test.mjs
git commit -m "build(site): add Rspress documentation foundation"

Task 2: Encode the four-game catalogue and document contract

Files:

  • Create: site/catalog.mjs
  • Modify: site/tests/catalog.test.mjs
  • Modify: games/README.md

Interfaces:

  • Produces: games: readonly GameRecord[] and mechanics: readonly MechanicRecord[] from site/catalog.mjs.

  • GameRecord shape: { id, title, lifecycle, rtp, grid, evaluation, summary, documents, flowPath }.

  • documents entries: { label, sourcePath, routePath, required }.

  • Consumes: exact source paths already present under the four game folders.

  • Step 1: Expand the failing catalogue test

Add assertions that import games and mechanics, require exactly four unique game IDs, forbid hold-and-win-reference in games, require every sourcePath to exist, and require each game to have README, GDD, Math Spec and Flow links:

test('catalog separates games from reusable mechanics', async () => {
  const { games, mechanics } = await import('../catalog.mjs');
  assert.deepEqual(
    games.map(game => game.id),
    ['classic-fruits-3x3', 'magic-vault-5x3', 'book-of-odin', 'vault-breakers-super-score'],
  );
  assert.equal(games.some(game => game.id === 'hold-and-win-reference'), false);
  assert.equal(mechanics.some(item => item.id === 'hold-and-win'), true);
  for (const game of games) {
    assert.equal(new Set(game.documents.map(doc => doc.label)).has('Math Spec'), true);
    for (const doc of game.documents) {
      assert.equal(existsSync(doc.sourcePath), true, doc.sourcePath);
    }
  }
});
  • Step 2: Run the focused test and verify the missing manifest fails

Run: node --test site/tests/catalog.test.mjs

Expected: FAIL with ERR_MODULE_NOT_FOUND for site/catalog.mjs.

  • Step 3: Implement the exact catalogue

Create site/catalog.mjs with the four current games and exact document paths:

  • Classic Fruits: README, GDD, Math Spec, changelog, SEVENS presentation.
  • Magic Vault: README, GDD, Math Spec, evidence manifest, changelog, presentation flow, frontend API.
  • Book of Odin: README, GDD, v2 Math Spec, v2 evidence manifest, math change request.
  • Vault Breakers: README, GDD, Math Spec, v0.2 evidence manifest, changelog.

Use source routes such as /games/classic-fruits-3x3/README and flow routes such as /flows/classic-fruits-3x3. Represent absent changelogs or dedicated flow documents by omitting that document entry; never synthesize a path.

Update games/README.md so its product-game table lists all four games and moves hold-and-win-reference into a separate Reusable mechanics table.

  • Step 4: Prove every manifest entry resolves

Run:

node --test site/tests/catalog.test.mjs
pnpm docs:build

Expected: tests pass; Rspress reports no dead document link.

  • Step 5: Commit the catalogue contract
git add site/catalog.mjs site/tests/catalog.test.mjs games/README.md
git commit -m "feat(site): define four-game documentation catalogue"

Task 3: Build the control-room home and documentation navigation

Files:

  • Create: site/components/GameCatalog.tsx
  • Create: site/components/LineIcon.tsx
  • Create: site/theme/index.tsx
  • Create: site/theme/index.css
  • Modify: index.mdx
  • Modify: rspress.config.ts
  • Test: site/tests/catalog.test.mjs

Interfaces:

  • GameCatalog(): React.JSX.Element consumes games and mechanics from site/catalog.mjs.

  • LineIcon({ name, title }: { name: IconName; title?: string }): React.JSX.Element renders local SVG only.

  • Produces stable data-game-id attributes for catalogue integration tests and visual QA.

  • Step 1: Add failing semantic-home assertions

Extend the catalogue test to read site/components/GameCatalog.tsx and assert that the source contains <section, a game-card heading, data-game-id, and a separate reusable-mechanics section. Add a negative assertion that no <img source begins with http.

  • Step 2: Run the test and verify missing components fail

Run: node --test site/tests/catalog.test.mjs

Expected: FAIL because GameCatalog.tsx is missing.

  • Step 3: Implement the home and restrained custom theme

Create the components and use semantic article, h2, dl, and nav elements. Each game card shows lifecycle, grid, evaluation, RTP wording exactly as recorded in catalog.mjs, plus buttons for the game overview and flow. Render document links as a compact list rather than repeating document prose.

Create site/theme/index.tsx using the Rspress v2 re-export contract:

import './index.css';
export * from '@rspress/core/theme-original';

Create CSS variables for graphite surfaces, off-white text and four muted metallic accents. Include mobile breakpoints, visible :focus-visible, and a prefers-reduced-motion: reduce block. Do not eject built-in Rspress components.

Replace the temporary home with:

---
pageType: custom
title: Math & GDD
description: Current slot mathematics, evidence and presentation flows.
---

import { GameCatalog } from './site/components/GameCatalog';

<GameCatalog />

Configure globalStyles or the conventional theme path so the site uses site/theme/index.css.

  • Step 4: Verify type safety, build and rendered catalogue markers

Run:

pnpm typecheck
node --test site/tests/catalog.test.mjs
pnpm docs:build
rg -q 'data-game-id' doc_build/index.html

Expected: all commands pass and all four titles appear in generated route chunks or HTML.

  • Step 5: Perform browser QA before committing

Run pnpm docs:dev, open the printed URL, and inspect at desktop 1440×900 and mobile 390×844. Verify keyboard focus order, no horizontal scroll, readable lifecycle labels and a separate reusable-mechanics section. Capture one desktop and one mobile screenshot outside the repository or in ignored test output.

  • Step 6: Commit the catalogue UI
git add index.mdx rspress.config.ts site/components site/theme site/tests/catalog.test.mjs
git commit -m "feat(site): add math control-room catalogue"

Task 4: Add four distinct accessible presentation flows

Files:

  • Create: site/flows.mjs
  • Create: site/components/GameFlow.tsx
  • Create: flows/classic-fruits-3x3.mdx
  • Create: flows/magic-vault-5x3.mdx
  • Create: flows/book-of-odin.mdx
  • Create: flows/vault-breakers-super-score.mdx
  • Modify: site/theme/index.css
  • Test: site/tests/flows.test.mjs

Interfaces:

  • Produces: flowByGameId: Readonly<Record<GameId, FlowGraph>>.

  • FlowGraph shape: { title, sourceLinks, nodes, edges }.

  • Node shape: { id, label, detail, kind, documentRoute? } where kind is phase | decision | presentation | terminal.

  • Edge shape: { from, to, label? }.

  • GameFlow({ gameId }: { gameId: GameId }): React.JSX.Element renders one graph as an ordered semantic flow.

  • Step 1: Write graph-contract tests first

Create site/tests/flows.test.mjs asserting:

import assert from 'node:assert/strict';
import { existsSync } from 'node:fs';
import test from 'node:test';
import { flowByGameId } from '../flows.mjs';

test('every product game has a connected authoritative flow', () => {
  const ids = ['classic-fruits-3x3', 'magic-vault-5x3', 'book-of-odin', 'vault-breakers-super-score'];
  assert.deepEqual(Object.keys(flowByGameId), ids);
  for (const graph of Object.values(flowByGameId)) {
    const nodeIds = new Set(graph.nodes.map(node => node.id));
    assert.equal(nodeIds.size, graph.nodes.length);
    for (const edge of graph.edges) {
      assert.equal(nodeIds.has(edge.from), true, edge.from);
      assert.equal(nodeIds.has(edge.to), true, edge.to);
    }
    for (const source of graph.sourceLinks) assert.equal(existsSync(source.sourcePath), true);
  }
});

Add branch-specific assertions: Classic has sevens-jackpot; Magic has hold-and-win-loop; Book has both expanding-wild-loop and nested-hold-and-win; Vault has tumble-loop, multiplier-charges, free-spins, and super-scatter.

  • Step 2: Run tests and verify the missing graph fails

Run: node --test site/tests/flows.test.mjs

Expected: FAIL with ERR_MODULE_NOT_FOUND for site/flows.mjs.

  • Step 3: Implement graph data from approved documents

Encode the four diagrams exactly as specified in the design. Each unique node links to an existing game presentation document, GDD, or reusable mechanic template. Do not invent timing, audio, payouts or backend fields not stated by those sources.

  • Step 4: Implement the semantic flow renderer and pages

Render the graph inside a labelled <section> with an ordered list of nodes and explicit edge labels. A node with documentRoute is an <a>; a node without one is a non-interactive <div>. Inline monochrome icons communicate node kind, but visible text remains sufficient.

Each flows/*.mdx page uses pageType: custom, imports GameFlow, and passes one literal game ID. CSS gives each mechanic a distinct schematic shape: straight paylines, nested Hold & Win return, expanding-wild side branch, and looping cascade rail. Under 720px, all graphs become a single vertical reading order without crossed connectors.

  • Step 5: Verify graph integrity, routes and reduced motion

Run:

node --test site/tests/flows.test.mjs
pnpm typecheck
pnpm docs:build
test -f doc_build/flows/classic-fruits-3x3.html
test -f doc_build/flows/magic-vault-5x3.html
test -f doc_build/flows/book-of-odin.html
test -f doc_build/flows/vault-breakers-super-score.html
rg -q 'prefers-reduced-motion' site/theme/index.css

Expected: all checks pass.

  • Step 6: Perform visual and keyboard QA

Open all four routes at 1440×900 and 390×844. Tab through every linked node. Confirm each game visibly differs, each branch returns to the correct owning sequence, no connector overlays text, and reduced-motion mode removes tracing animation.

  • Step 7: Commit the flows
git add flows site/flows.mjs site/components/GameFlow.tsx site/theme/index.css site/tests/flows.test.mjs
git commit -m "feat(site): add interactive game presentation flows"

Task 5: Add independent built-site and container gates

Files:

  • Create: scripts/check-built-site.mjs
  • Create: tests/check-built-site.test.mjs
  • Create: docker/nginx.conf
  • Create: Dockerfile
  • Create: .dockerignore
  • Modify: package.json

Interfaces:

  • checkBuiltSite(root = 'doc_build'): string[] returns validation errors and has no side effects.

  • CLI execution exits 1 and prints each error when the returned array is non-empty.

  • Container serves /healthz and all generated routes on internal port 8080.

  • Step 1: Write red tests for the independent checker

Use mkdtempSync to create one valid and one broken miniature build. Assert that a missing local route, absent game flow route, absolute /Users/ path and external asset URL each produce a specific error. Assert that hash links and https://math-gdd.chowchowhome.duckdns.org are allowed.

  • Step 2: Run the checker test and verify it fails

Run: node --test tests/check-built-site.test.mjs

Expected: FAIL because scripts/check-built-site.mjs is missing.

  • Step 3: Implement the checker

Walk doc_build/**/*.html, extract href and src values, resolve local clean URLs to either path.html or path/index.html, require the home and four flow routes, reject file:, /Users/, /private/, pass/, and remote asset origins, and return sorted unique errors. Keep this separate from Rspress's build-time dead-link check so a generated-route regression cannot self-certify.

  • Step 4: Verify the checker red/green cases and full build

Run:

node --test tests/check-built-site.test.mjs
pnpm docs:build
node scripts/check-built-site.mjs

Expected: fixture tests pass and the real build reports zero errors.

Update package.json so the default test command now includes the independent checker suite:

"test": "node --test site/tests/*.test.mjs tests/*.test.mjs"
  • Step 5: Add the static runtime image

Use a Node 24 builder stage with Corepack and frozen pnpm install, then copy only doc_build/ into nginxinc/nginx-unprivileged:alpine. Configure nginx to listen on 8080, serve static files, return 200 ok at /healthz, and fall back from clean routes to $uri.html and $uri/index.html without masking genuine 404s.

.dockerignore must exclude .git, node_modules, doc_build, output, draft-v1, ggd ігри, SLOT_ASSETS, .playwright-mcp, .DS_Store, and ignored local caches. It must not exclude games, docs, templates, flows, site, or the build configuration.

  • Step 6: Build and probe the real image

Run:

docker build -t math-gdd:test .
docker run -d --rm --name math-gdd-test -p 127.0.0.1:18080:8080 math-gdd:test
curl -fsS http://127.0.0.1:18080/healthz
curl -fsS http://127.0.0.1:18080/
curl -fsS http://127.0.0.1:18080/flows/book-of-odin
docker stop math-gdd-test

Expected: health returns ok; home and flow return 200; the container stops cleanly.

  • Step 7: Commit validation and image files
git add package.json scripts tests Dockerfile .dockerignore docker/nginx.conf
git commit -m "build(site): validate and package static documentation"

Task 6: Add label-discovered VPS deployment and GitLab CI

Files:

  • Create: deploy/docker-compose.yml
  • Create: deploy/README.md
  • Create: .gitlab-ci.yml
  • Modify: README.md

Interfaces:

  • Compose consumes MATH_GDD_IMAGE, MATH_GDD_TAG, and CONTAINER_NAME from CI.

  • Fixed compose project: math-gdd; fixed host label: math-gdd; fixed internal port: 8080.

  • CI stages: check, image, deploy.

  • Step 1: Verify runner eligibility before writing deploy automation

Run with the authenticated GitLab CLI:

glab api projects/kiro%2Fmath-gdd/runners

Expected: at least one active runner eligible for this project. Record whether it requires tags: [tools]. If the result is empty, stop this task and assign a runner before adding CI; otherwise every job would remain pending without an actionable error.

  • Step 2: Write the compose file and validate its resolved contract

Create one service using ${MATH_GDD_IMAGE}:${MATH_GDD_TAG}, explicit ${CONTAINER_NAME}, restart: unless-stopped, external web, expose: ["8080"], and the exact five chow.* labels from the spec. Add a container healthcheck against /healthz.

Run:

MATH_GDD_IMAGE=registry.invalid/kiro/math-gdd \
MATH_GDD_TAG=deadbeef \
CONTAINER_NAME=math-gdd-1 \
docker compose -p math-gdd -f deploy/docker-compose.yml config

Expected: one service, no published ports, external web, host label math-gdd, image tag deadbeef, and container name math-gdd-1.

  • Step 3: Add the CI check and immutable image stages

Use node:24-bookworm-slim for Check and docker:28-cli for image/deploy. Check runs Corepack, frozen install and pnpm check; it stores doc_build/ for one week. Image logs into the GitLab registry, builds both $CI_COMMIT_SHORT_SHA and main, pushes both, and runs only on default-branch changes that can affect the site.

Preserve the repository's deployment rule in comments: the mutable main tag is convenience only; deploy consumes $CI_COMMIT_SHORT_SHA.

  • Step 4: Add the main-only deploy gate

Export:

export MATH_GDD_IMAGE="$CI_REGISTRY_IMAGE"
export MATH_GDD_TAG="$CI_COMMIT_SHORT_SHA"
export CONTAINER_NAME="math-gdd-1"
COMPOSE="docker compose -p math-gdd -f deploy/docker-compose.yml"

Then login, pull, up -d --remove-orphans, inspect .Image for math-gdd-1, and poll from an ephemeral Alpine container on web until both /healthz and /flows/book-of-odin answer. Print the deployed SHA and https://math-gdd.chowchowhome.duckdns.org. Do not edit Caddy or call the registry generator directly.

  • Step 5: Write the operator runbook and rollback command

Document local commands, CI stages, the up-to-60-second vhost delay, first-certificate retry, container/image identity inspection, service logs, and rollback:

export MATH_GDD_IMAGE=git.chowchowhome.duckdns.org:5050/kiro/math-gdd
printf 'Previous successful short SHA: '
read -r MATH_GDD_TAG
export MATH_GDD_TAG
export CONTAINER_NAME=math-gdd-1
docker compose -p math-gdd -f deploy/docker-compose.yml pull
docker compose -p math-gdd -f deploy/docker-compose.yml up -d --remove-orphans
docker inspect -f '{{.Image}}' math-gdd-1

The operator copies the immutable short SHA from a previously successful GitLab image job. The registry path is fixed; executable CI and the runbook contain no guessed image identity.

Update root README.md with pnpm docs:dev, pnpm check, the deployed URL and a link to deploy/README.md.

  • Step 6: Validate pipeline syntax and local deploy contract

Run:

pnpm check
docker build -t math-gdd:test .
MATH_GDD_IMAGE=math-gdd MATH_GDD_TAG=test CONTAINER_NAME=math-gdd-1 \
  docker compose -p math-gdd -f deploy/docker-compose.yml config --quiet
git diff --check

Submit .gitlab-ci.yml to GitLab CI Lint using the authenticated project endpoint and require a valid result before commit.

  • Step 7: Commit CI and deployment
git add .gitlab-ci.yml deploy README.md
git commit -m "ci(site): build and deploy Math GDD portal"

Task 7: Final review, push and live verification

Files:

  • Modify only files required by confirmed review findings.
  • Record live evidence in the merge request or deployment job; do not fabricate a repository transcript.

Interfaces:

  • Consumes all prior tasks.

  • Produces a review-approved branch, green pipeline, immutable deployed SHA and verified protected URL.

  • Step 1: Run the full local gate from a clean dependency state

Run:

pnpm install --frozen-lockfile
pnpm check
docker build -t math-gdd:test .
git diff --check

Expected: all commands exit 0.

  • Step 2: Review source-of-truth and deployment boundaries

Confirm from the diff that:

  • no game math/config/evidence number changed;

  • the catalogue contains four games;

  • every flow links to an authored source;

  • no ignored raw asset directory entered Docker context;

  • compose has no ports or bind mount;

  • deploy uses the SHA tag and fixed project name;

  • no Caddyfile or vps-infra file changed.

  • Step 3: Request independent code and deployment-safety review

The reviewer must exercise at least these mutations rather than accepting a green suite:

  • add hold-and-win-reference to the game array;
  • remove one required flow route;
  • insert a dead document link;
  • inject /Users/admin/private-path into generated HTML;
  • change deploy tag from $CI_COMMIT_SHORT_SHA to main;
  • add a host ports: mapping;
  • remove chow.host or change it from math-gdd.

Each mutation must fail a test, build, compose assertion or review gate for the claimed property.

  • Step 4: Push a feature branch and open an MR

Use a branch such as feat/math-gdd-site. Push only after the independent review returns Yes. The MR description includes the spec, local gate output, runner eligibility, image/deploy design, and explicit statement that no game math changed.

  • Step 5: Verify MR pipeline before merge

Require Check to pass on the MR. Image and Deploy remain absent because they are default-branch only. Inspect the build artifact and open its home plus all four flow pages.

  • Step 6: Merge and verify the main pipeline

After user approval to merge, require Check → Image → Deploy to succeed on one SHA. Verify:

curl -I https://math-gdd.chowchowhome.duckdns.org

Expected: authentication challenge or authenticated 200 according to the existing production basic-auth policy, never an unauthenticated public 200. Confirm the running container image matches the pipeline SHA and the registry dashboard contains the Slot Math & GDD service tile.

  • Step 7: Report exact final state

Report branch, commit SHA, pipeline URL/status, running image identity, site URL, auth behaviour, four flow-route checks, and any unresolved blocker. Do not call internal tests certification.