architecture
Table of Contents
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
| project | trigger | what it does |
|---|---|---|
| bryanchasko-com-lint | codepipeline stage | npx biome ci --diagnostic-level=error . — tabs, double quotes, CSS format |
| bryanchasko-com-deploy | codepipeline stage | hugo build, s3 sync (two-pass), cloudfront invalidation on changed paths |
| bryanchasko-com-terraform | manual | infrastructure 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:
- first pass: upload new/changed files with appropriate cache headers
- 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:
| prefix | app |
|---|---|
| / (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
| account | what 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