architecture

CI/CD pipeline, deployment infrastructure, and build system for bryanchasko.com

the pipeline

every push to main triggers the deployment chain. no manual deploys, no GitHub Actions (zero budget for that). CodeBuild on AWS handles everything.

push to main → CodePipeline (source) → Lint (biome) → Deploy (hugo build + s3 sync + cloudfront invalidation)

three stages, sequential, fail-fast. lint fails = nothing deploys.

codebuild projects

projecttriggerwhat it does
bryanchasko-com-lintcodepipeline stagenpx biome ci --diagnostic-level=error . — tabs, double quotes, CSS format
bryanchasko-com-deploycodepipeline stagehugo build, s3 sync (two-pass), cloudfront invalidation on changed paths
bryanchasko-com-terraformmanualinfrastructure changes (cloudfront, s3 bucket, route53)

hugo build

hugo extended builds the site. TypeScript compiles directly via js.Build — no webpack, no vite, no separate bundler.

  • assets pipeline: fingerprinted output (content hash in filename = permanent cache)
  • two build modes: development (source maps, no minify) and production (minified, no source maps)
  • external dependencies declared for babylon.js (loaded from CDN, not bundled)

deploy strategy

two-pass s3 sync:

  1. first pass: upload new/changed files with appropriate cache headers
  2. second pass: delete orphaned files that no longer exist in the build output

cache headers:

  • HTML files: Cache-Control: no-cache (always revalidate)
  • asset files (JS, CSS, images with fingerprint): Cache-Control: max-age=86400 (1 day)
  • cloudfront invalidation fires on changed paths only (not wildcard /*)

s3 prefix registry

multiple apps share one S3 bucket. collision avoidance via prefix ownership:

prefixapp
/ (root)main hugo site
/sumerian-squares/sumerian squares game assets (audio, posters, data)
/moodle-course-builder/moodle extension product page assets
/listen/podcast player
/bench/herald bench platform
/design/design system playground

s3-prefix-registry.json in the repo root is the source of truth.

versioning

sumerian-squares uses semantic-style versioning starting at v0.007. the VERSION file at repo root is the source of truth. displayed in-game at bottom-right corner.

feature branch workflow

  • no preview deploys, no branch-specific URLs
  • all work happens on feature branches
  • PRs merge to main via squash-merge
  • pipeline triggers automatically on main push
  • typical deploy time: ~3 minutes (lint: 30s, hugo build: 15s, s3 sync: 30s, invalidation propagation: ~60s)

accounts

accountwhat deploys there
aerospaceug-admin (211125425201)the site itself (s3 + cloudfront)
bryanchasko-kiro (946179428633)sumerian-hosts CDN assets (babylon.js fixtures), TTS lambda, bedrock
jitsi-video-hosting (170473530355)jitsi video infrastructure (separate pipeline)

auto-versioning pipeline

the VERSION file at repo root is the source of truth for the current build number. hugo reads it at build time via {{ readFile "VERSION" | strings.TrimSpace }} and injects the value into any template that references it

the scheme is a single monotonic counter of the form 0.XXXX — not semver. there is no major/minor/patch split because this is one game surface, so a running release counter is the honest model. the on-page badge renders v0.XXXX (<git-sha>): the human number is what you say out loud (“I see a bug on v0.1013”), the short git SHA (auto-stamped at deploy into VERSION-BUILD from CODEBUILD_RESOLVED_SOURCE_VERSION) pins the exact commit

bump rule: any deploy that changes what a player sees or does — gameplay, layout, hosts, audio, copy — increments the last digit by exactly 1. pure infra, pipeline, or docs changes that do not alter the game do not bump. the SHA always identifies the exact commit regardless, so the human number stays clean for conversation

currently used in the sumerian-squares game UI — displayed bottom-right of the game viewport at 0.55rem, 30% opacity. small enough to never interfere with gameplay, visible enough to identify which build is running when debugging or reporting issues

the bump is manual for now. developer increments the number in VERSION, commits alongside the feature work, pipeline deploys the new number. future: buildspec reads VERSION, increments the counter, commits back to main as a post-deploy step — fully automated version tracking per deploy without developer intervention

Nova Act post-deploy validation

the CodeBuild post_build phase runs Nova Act validation against the deployed site:

python3 tests/nova-act/sumerian-squares-dual-verify.py

exit code gates the deploy. non-zero = the pipeline reports failure. the validation script is not a unit test — it drives a real browser via Bedrock AgentCore’s managed Chromium instance and observes the rendered output

evidence stored at /tmp/nova-dual-verify/ during the build (screenshots, console logs, assertion results). after the run completes, evidence pushes to s3vectors for historical tracking and regression comparison

the dual-verification pattern separates programmatic checks from AI observation:

  • programmatic BabylonJS checks — deterministic assertions against engine state. scene loaded, meshes present, animation groups playing, camera positioned correctly. zero AI involvement, zero ambiguity. these catch hard crashes and loading failures
  • AI visual observation — Nova Act navigates the live page, interacts with game elements, and describes what it sees. catches visual regressions that programmatic checks cannot: mouth movements not synced, host appearing frozen despite animation groups technically running, camera framing that looks wrong to a viewer even though the numeric values are within range