Skip to content

CI hardening: version lockstep, secretless publish, intake feedback (arch uplift)#31

Merged
BCook98 merged 3 commits into
mainfrom
arch-uplift/games
Jun 10, 2026
Merged

CI hardening: version lockstep, secretless publish, intake feedback (arch uplift)#31
BCook98 merged 3 commits into
mainfrom
arch-uplift/games

Conversation

@BCook98

@BCook98 BCook98 commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Hardens the catalog CI pipeline against the version-skew, supply-chain, and silent-failure issues found by the 2026-06-09 architecture review (PR'd from arch-uplift/games; companions: kit#arch-uplift/kit, and a larger shellcade integration PR to follow).

What's here

G1 — CI verifies with the engine the games actually use (43b08d6)

  • KIT_VERSION 2.1.1 → 2.3.0 in validate.yml + publish.yml (2.3.0 is the newest release that actually ships shellcade-kit binaries; v2.4–v2.7 are bare module tags).
  • New version-lockstep guard in validate.yml: parses the embedded kit version from shellcade-kit version and compares (sort -V) against each changed game's go.mod/Cargo.toml kit pin — fails with the exact KIT_VERSION-bump instruction instead of silently under-checking games that use newer kit features.
  • Fork-controlled directory names no longer spliced into shell via ${{ }} — every consuming step now uses env: DIRS (the repo's own documented rule, applied uniformly).
  • SCHEMA.md player bounds corrected to 1..1024 with a note that smoke scripts drive at most 8 seats.

G2 — publish pipeline: no secrets near game code, no silent never-go-live (39ef38e)

  • publish.yml split into two jobs: a build job with zero secrets (permissions: contents: read, persist-credentials: false) that compiles changed games at the release profile, runs shellcade-kit check on the actual -opt=2 artifact (previously the published artifact was never checked), and uploads workflow artifacts; and a release job that downloads artifacts, cuts the GitHub release, and POSTs to /admin/game:publish — executing zero community code. Community build.rs/proc-macros can no longer read SHELLCADE_ADMIN_SECRET or a write-scoped token.
  • The arcade's intake response is now parsed: verified:false emits ::error with the verbatim reason, upserts a comment on the merged PR (authorize.yml's marker-keyed pattern), and fails the run. Fail-soft remains only for transport-level failures.

G3 — supply-chain + content hygiene (2cfba3f)

  • Nightly govulncheck (Go games) + cargo-audit (Rust games) workflow; dependabot for Actions.
  • CONTENT_POLICY.md with a report/takedown path, linked from the README.

Operator actions after merge

  1. Rotate SHELLCADE_ADMIN_SECRET — it has been exposed to community build scripts in the pre-split pipeline.
  2. When a shellcade-kit binary for kit 2.7.x is released, bump KIT_VERSION — the lockstep guard will flag any PR that needs it sooner.

Part of the architecture-uplift program; finding details in the review report (internal).

🤖 Generated with Claude Code

BCook98 and others added 3 commits June 10, 2026 01:50
…and SCHEMA player bounds

CI correctness uplift for the games repo (architecture review findings,
Appendix B):

- Bump KIT_VERSION 2.1.1 -> 2.3.0 in validate.yml and publish.yml — the
  newest shellcade-kit release that actually ships binaries (verified via
  gh release list; no v2.7.x binary exists yet, that operator action is
  tracked separately in the uplift plan).
- Add a "kit version lockstep" step to validate.yml: parse the embedded
  kit module version from `shellcade-kit version` and compare it (sort -V)
  against each changed game's go.mod require / Cargo.toml git tag pin,
  failing the PR with an exact KIT_VERSION bump instruction when a game
  requires a newer kit than the pinned conformance engine. This makes the
  hand-maintained "bump together" comment a mechanical gate.
- Stop splicing the fork-controlled changed-dirs output into run blocks
  via ${{ }}: every consuming step in validate.yml (3 steps + the new
  guard) and publish.yml (contents:write + ADMIN_SECRET step) now passes
  it as env DIRS so it is shell data, per authorize.yml's documented rule.
- Defense in depth: validate_game_dir.py now checks the full
  games/<username>/<name> path shape, including the owner segment
  ([a-z0-9-]{1,32}), anchored on trailing segments so the documented
  local "$(pwd)" invocation keeps working.
- SCHEMA.md: fix the player-bounds contradiction (1..8 -> the enforced
  1..1024) and document the 8-seat smoke-script cap in the smoke.yaml row.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…surface verified:false

Security + feedback-loop uplift for publish.yml (architecture review
findings, Appendix B: admin secret in build env [HIGH]; conformance-gate
skew / fail-soft publish [HIGH]; invisible post-merge refusals [MED]):

- Split publish.yml into two jobs, mirroring the validate/smoke-preview
  isolation pattern. The build job compiles community game code (cargo
  build.rs / proc-macros run arbitrary code) with NO secrets, read-only
  permissions, and `persist-credentials: false`; it builds the -opt=2
  release profile, runs `shellcade-kit check` on that actual release
  artifact (sha256-verified kit fetch copied from validate.yml), logs
  the digest, and uploads the wasm as a workflow artifact. Job-level
  isolation, not step-level: a malicious build script can leave a
  background process reading later steps' environments.
- The release job (needs: build) holds GITHUB_TOKEN + ADMIN_SECRET and
  runs zero game code: it downloads the artifacts, recomputes the
  sha256 from the bytes it actually attaches, derives vN, cuts the
  GitHub release, and POSTs the tag to /admin/game:publish. The
  changed-dirs list crosses jobs via a build-job output captured BEFORE
  any game code executes, so it is not build-script-influenceable.
- Parse the game:publish response (status and body captured separately,
  authorize.yml pattern). 200 + verified:false is now LOUD: ::error
  with the slug/tag/verbatim reason, a marker-keyed comment upserted on
  the merged PR (cleared in place on a later clean pass), and a failed
  run. Transport-shaped outcomes (curl failure, non-200, empty or
  non-JSON body, missing secret) keep today's fail-soft path.
- validate.yml: also build + `shellcade-kit check` the -opt=2 release
  profile pre-merge for TinyGo games (Rust already validates --release),
  closing the dev-profile-only PR gate the review flagged.

OPERATOR ACTION when this lands: rotate SHELLCADE_ADMIN_SECRET — the
old publish job exposed it to community build scripts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Close the two hygiene gaps from the architecture review (gap areas 3 and 4):

- .github/workflows/vulncheck.yml: nightly whole-catalog dependency vuln
  scan — govulncheck (symbol-level) over every Go game module and
  cargo-audit over every Rust game, versions pinned like the existing
  toolchain pins. Each game is a standalone module, so the tree is walked
  per game.
- .github/dependabot.yml: weekly bumps for workflow actions only; gomod and
  cargo are deliberately excluded because game directories are author-owned
  namespaces a bot PR may not touch.
- CONTENT_POLICY.md: the content bar, a public (issue) and private (GitHub
  security reporting) report path, and the takedown sequence — operator
  pulls the game offline immediately, catalog removal follows by maintainer
  PR — plus appeals and namespace consequences. Linked from the README
  rules.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@BCook98
BCook98 merged commit c06be4b into main Jun 10, 2026
2 checks passed
@BCook98
BCook98 deleted the arch-uplift/games branch June 10, 2026 05:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant