Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
211 commits
Select commit Hold shift + click to select a range
2e3722f
chore: import mission-control + mission-authoring onto the upstream f…
HlalanathiMashimbye Jun 20, 2026
6724e64
Fixes AB#250: operator login and admin access on the hub (#2)
HlalanathiMashimbye Jun 23, 2026
9b642a4
Fixes AB#249: serve the yard satellite on port 3001 (#3)
HlalanathiMashimbye Jun 23, 2026
3d3012d
Fixes AB#251: minimal operator mission API in the hub (#4)
HlalanathiMashimbye Jun 23, 2026
3bab0c9
Fixes AB#254: align hub Blockly with the yard's (#5)
HlalanathiMashimbye Jun 23, 2026
3aae4ca
Fixes AB#248: retire the mission-control Next app (#6)
HlalanathiMashimbye Jun 23, 2026
1296dd9
fix: make yard/satellite deps install cross-platform for local dev (#7)
HlalanathiMashimbye Jun 23, 2026
31dda57
Fixes AB#262: retire challenges and leaderboard (#8)
HlalanathiMashimbye Jun 24, 2026
84ba60e
Fixes AB#261: YouTube-style mission feed (#9)
HlalanathiMashimbye Jun 24, 2026
2f28e05
Fixes AB#252: Operator page lists not-run missions with code (#10)
HlalanathiMashimbye Jun 24, 2026
7a73a8d
Fixes AB#253: attach a YouTube video to a mission (#11)
HlalanathiMashimbye Jun 24, 2026
56badbf
feat: search on the landing feed, and remove em dashes across the hub…
HlalanathiMashimbye Jun 24, 2026
d2932fe
Fixes AB#255: persist Blockly state on submission (#13)
HlalanathiMashimbye Jun 24, 2026
accc071
fix: allow the rover servo/sensor/LED API so Blockly missions validat…
HlalanathiMashimbye Jun 24, 2026
68018d8
Fixes AB#256: Copy blocks in the operator page (#15)
HlalanathiMashimbye Jun 24, 2026
c790c20
Fixes AB#257: import blocks paste box in the yard editor (#14)
HlalanathiMashimbye Jun 24, 2026
b7f458d
feat: polish the mission discovery feed and detail page (#17)
HlalanathiMashimbye Jun 25, 2026
3a8ea45
chore: rename mission-authoring to mission-control and strip bloat (#18)
HlalanathiMashimbye Jun 25, 2026
85c328c
chore: remove explicit-any type-safety errors + gate lint in CI (#19)
HlalanathiMashimbye Jun 25, 2026
1dff2cc
chore: remove the dead standalone simulator (#20)
HlalanathiMashimbye Jun 25, 2026
3ae6b24
chore: remove dead components, hooks, and a service (#21)
HlalanathiMashimbye Jun 25, 2026
e308da5
feat: render the 2D sim as a video client-side (fixes Bug 2) (#22)
HlalanathiMashimbye Jun 25, 2026
8f15e8c
feat: sim-vs-real video tabs on the mission detail page (#23)
HlalanathiMashimbye Jun 25, 2026
3f6a951
feat: redesign the mission detail page and make run selection a dropd…
HlalanathiMashimbye Jun 25, 2026
17c0958
fix: fit the mission detail page to the viewport (no page scroll) (#25)
HlalanathiMashimbye Jun 25, 2026
1e534d4
feat: show Blockly blocks on the mission preview (Blocks|Python toggl…
HlalanathiMashimbye Jun 25, 2026
91d6665
feat: human mission names (no number) + move the email ask to post-su…
HlalanathiMashimbye Jun 25, 2026
b2fbcf0
feat: manual control is a tappable block palette (was arrow D-pad) (#28)
HlalanathiMashimbye Jun 25, 2026
6d2a58d
fix: restyle manual blocks to look like the real Blockly blocks (#29)
HlalanathiMashimbye Jun 25, 2026
876ad97
feat: elevate the Mars theme (Blockly colour tokens, clay helpers, ca…
HlalanathiMashimbye Jun 27, 2026
ef8854b
feat: rebuild the landing feed as a viewport-fit, filterable mission …
HlalanathiMashimbye Jun 27, 2026
725ca33
feat: beautify the navbar (segmented nav pill, chunky brand, exact 64px)
HlalanathiMashimbye Jun 27, 2026
fac2684
feat: rebuild the operator console as a pipeline dashboard
HlalanathiMashimbye Jun 27, 2026
bbf3e4a
fix: operator login redirect race (decide from the token claim)
HlalanathiMashimbye Jun 27, 2026
de31343
feat: overhaul the 2D simulator with one shared renderer
HlalanathiMashimbye Jun 27, 2026
6077d18
fix: manual-control bugs (teleport, reset, idle loop, stale video)
HlalanathiMashimbye Jun 27, 2026
18af112
feat: align the Python editor and parser to the real rover API
HlalanathiMashimbye Jun 27, 2026
c79f304
feat: polish remaining screens and enlarge the Blockly blocks
HlalanathiMashimbye Jun 27, 2026
386b000
docs: add the UI overhaul brief
HlalanathiMashimbye Jun 27, 2026
3b966e3
chore: clean up mission-control for external review
HlalanathiMashimbye Jul 2, 2026
5c7251c
chore: remove deprecated UI overhaul brief and roadmap backlog files
HlalanathiMashimbye Jul 2, 2026
1422d7b
test: fix fixture type errors surfaced by a full tsc pass
HlalanathiMashimbye Jul 15, 2026
30aacc2
feat: move the operator console from mission-control to the yard sate…
HlalanathiMashimbye Jul 15, 2026
d5f8fdf
chore: make dev servers reclaim their ports and auto-load satellite env
HlalanathiMashimbye Jul 15, 2026
cd8fdeb
refactor: remove video capture from the rover simulator
HlalanathiMashimbye Jul 15, 2026
8ce4ff7
feat: give the yard satellite a login-first operator hub
HlalanathiMashimbye Jul 15, 2026
be7ca69
feat: add favourites system with localStorage persistence
CLXKON001 Jul 15, 2026
f5c3139
feat(yard): shared nav + CSS across satellite pages, and mission rerun
HlalanathiMashimbye Jul 15, 2026
49ccd15
Streamline dev startup output
HlalanathiMashimbye Jul 16, 2026
b22d83f
Restore Yard satellite UI changes
HlalanathiMashimbye Jul 16, 2026
0a3ba0c
Merge favourites page changes into main
CLXKON001 Jul 16, 2026
955e7d2
fix(dev-launcher): improve npm command handling and child process ter…
kamolinks Jul 16, 2026
9d6ac3e
fix(dev-launcher): enhance Windows command execution for npm scripts
kamolinks Jul 16, 2026
6e70ada
fix(operator-console): enhance error handling for token verification …
HlalanathiMashimbye Jul 16, 2026
77ecdd1
Merge origin/main into main
HlalanathiMashimbye Jul 16, 2026
2811989
fix(satellite): auto-install Python deps so operator login works on a…
HlalanathiMashimbye Jul 16, 2026
b00027c
fix: make the run video resilient to YouTube bot blocks
HlalanathiMashimbye Jul 17, 2026
0a7002f
feat: add deployment guide
HlalanathiMashimbye Jul 17, 2026
6b643cf
feat: containerize mission-control and add Cloud Run deploy workflows
HlalanathiMashimbye Jul 17, 2026
4016228
feat: Terraform for mission-control hosting on bt-impact-academy
HlalanathiMashimbye Jul 17, 2026
7b843a7
feat: OPERATOR_AUTH=off opens the yard console without sign-in
HlalanathiMashimbye Jul 18, 2026
0187952
Installed Firebase for Impact Web
kamolinks Jul 20, 2026
57ae258
Search
Kelani12 Jul 20, 2026
93665bc
Merge branch 'main' of https://github.com/HlalanathiMashimbye/4tronix…
Kelani12 Jul 20, 2026
22d8f99
Load efficiency
Kelani12 Jul 20, 2026
3ce19a3
Included Firestore Rules
kamolinks Jul 20, 2026
be52b5f
Merge branch 'main' of https://github.com/HlalanathiMashimbye/4tronix…
kamolinks Jul 20, 2026
a86da40
Created Firestore Index Jason
kamolinks Jul 20, 2026
6dc9759
Edited Firebase Json file
kamolinks Jul 20, 2026
c00c12a
Page loading efficiency and performance improvements
Kelani12 Jul 20, 2026
3459111
Cache-Control: have persisting missions even after connection is lost.
Kelani12 Jul 21, 2026
7392f92
Home UI: Updated home page layout and styling
Kelani12 Jul 21, 2026
6d38937
Updated GitIgnore
kamolinks Jul 21, 2026
d11365a
Final Cache changes
Kelani12 Jul 25, 2026
9a72327
Error Message: update error messages
Kelani12 Jul 25, 2026
4636f5c
YouTube auto-link: fix Firestore query + poll resilience (#31)
HlalanathiMashimbye Jul 25, 2026
9b08080
docs: document YouTube env vars and add the missing satellite .env.ex…
HlalanathiMashimbye Jul 25, 2026
b4deb38
UI Changes
Kelani12 Jul 25, 2026
1310e7a
Home UIChanged
Kelani12 Jul 25, 2026
45a5bf8
fix: load the satellite's own .env at startup (#33)
HlalanathiMashimbye Jul 25, 2026
0e1578f
homeuichanges
Kelani12 Jul 25, 2026
42a2f5e
final
Kelani12 Jul 25, 2026
72efc3e
docs: add YouTube OAuth client placeholders to .env.example (#34)
HlalanathiMashimbye Jul 25, 2026
162be3d
Yard satellite: caching, adaptive polling, and UI polish (#35)
HlalanathiMashimbye Jul 25, 2026
08cd432
Operator friendly mission console
Kelani12 Jul 25, 2026
59a94c5
UI changes
Kelani12 Jul 25, 2026
f3ef34d
Status and validations
Kelani12 Jul 25, 2026
4c40c6e
Merge branch 'Network'
Kelani12 Jul 25, 2026
de7e771
feat(mission-control): add email notifications for mission status cha…
CLXKON001 Jul 25, 2026
194e701
Revert "feat(mission-control): add email notifications for mission st…
CLXKON001 Jul 25, 2026
0e4d0ec
feat(mission-control): add email notifications for mission status cha…
CLXKON001 Jul 25, 2026
a3e72d8
docs: plan for mission locking and offline sync (#37)
HlalanathiMashimbye Jul 25, 2026
2e6b8ce
Updated email notifications and mission name generation
CLXKON001 Jul 26, 2026
8b1f84c
feat(mission-control): email notifications for mission status changes
CLXKON001 Jul 26, 2026
cbe24ee
fix(mission-control): remove duplicate email send path in MissionService
CLXKON001 Jul 26, 2026
ca2d0f5
Merge branch 'feat/merge-email-notifications'
CLXKON001 Jul 26, 2026
82c48b1
fix(mission-control): fix admin Firestore exists() check breaking PAT…
CLXKON001 Jul 26, 2026
6723bc2
chore(mission-control): default dev script to webpack
CLXKON001 Jul 26, 2026
e0f7914
feat(mission-control): submit missions through POST /api/missions
CLXKON001 Jul 26, 2026
e9f5341
feat(mission-control): add POST /api/missions/[id]/notify
CLXKON001 Jul 26, 2026
96fa062
feat(operator-console): notify mission-control after status changes
CLXKON001 Jul 26, 2026
a310f22
fix(operator-console): run mission-control notification on a backgrou…
CLXKON001 Jul 26, 2026
5839c50
Added locking fields to firestore write path
kamolinks Jul 26, 2026
a1887f3
Implemented Locking Functions
kamolinks Jul 26, 2026
7b15677
Added lease timer
kamolinks Jul 26, 2026
f4bc4e6
Implemented sql mission storage
kamolinks Jul 26, 2026
b0dabf6
added outbox table
kamolinks Jul 26, 2026
5dcc40a
Changed send_to_rover endpoint
kamolinks Jul 26, 2026
8495bbd
Changed other 3 endpoints
kamolinks Jul 26, 2026
9c4649f
Added write and enqueue to mission_store
kamolinks Jul 26, 2026
e8fd4b5
Implemented push before pull rule
kamolinks Jul 26, 2026
183106a
Pending writes
kamolinks Jul 26, 2026
011ac39
UI color changes
Kelani12 Jul 27, 2026
f617905
hOME PAGE AND LOGIN PAGE UI DESIGN
Kelani12 Jul 27, 2026
d1dd75d
fix(package-lock): remove unnecessary peer dependencies
HlalanathiMashimbye Jul 27, 2026
9482b9a
User Stiry 313: design operator page
Kelani12 Jul 27, 2026
46a8223
Operator message box
Kelani12 Jul 27, 2026
aabfc24
filters and buttons
Kelani12 Jul 27, 2026
4800219
Fixes the issue with the operator template. The changes ensure that t…
Kelani12 Jul 27, 2026
d5c6951
Added dotenv for dev satellite
kamolinks Jul 27, 2026
b755888
Merge branch 'main' into message-feedback
Kelani12 Jul 27, 2026
864a9b2
Fix: Added ignoreDeprecations to tsconfig.json to suppress deprecatio…
Kelani12 Jul 27, 2026
e98f271
installed resend package
Kelani12 Jul 27, 2026
b258508
modified package.json
Kelani12 Jul 27, 2026
76041cf
Merge pull request #40 from HlalanathiMashimbye/message-feedback
Kelani12 Jul 27, 2026
0743bb2
Missions loadtime
Kelani12 Jul 27, 2026
51de811
fix(mission-control): backfill learner email onto in-flight mission
HlalanathiMashimbye Jul 27, 2026
42aec0b
feat(mission-control): redirect email to sandbox inbox while domain i…
HlalanathiMashimbye Jul 27, 2026
3a2c44d
feat(mission-control): surface mission email outcomes in logs
HlalanathiMashimbye Jul 27, 2026
b3a79cd
Yard cache and operator queue ui changes (#41)
Kelani12 Jul 27, 2026
c9cd2b7
Operator Console: Added a new feature to the operator.html template t…
Kelani12 Jul 27, 2026
077f352
Merge pull request #42 from HlalanathiMashimbye/Operator-Console
Kelani12 Jul 27, 2026
dfbb3c9
Background sync stopped
Kelani12 Jul 27, 2026
9c3dab0
fix(yard): align satellite colours with mission-control
HlalanathiMashimbye Jul 27, 2026
f2a4a7c
Added Sync Test Explanation
kamolinks Jul 27, 2026
a26b12f
fix(satellite): complete the offline mission mirror for PR 2
CLXKON001 Jul 27, 2026
af3e6bd
Merge remote-tracking branch 'origin/main' into feat/SatelliteMission…
CLXKON001 Jul 27, 2026
67dd9ed
Updated firestore index file
kamolinks Jul 27, 2026
aa9ddec
fix(infra): co-locate Cloud Run with Firestore in europe-west1
HlalanathiMashimbye Jul 28, 2026
b299621
docs(infra): rewrite Part A to match the real project state
HlalanathiMashimbye Jul 28, 2026
90238b6
feat(infra): provision Resend and NEXT_PUBLIC_APP_URL for deployed bu…
HlalanathiMashimbye Jul 28, 2026
de89a63
fix(security): replace allow-all Firestore rules with least-privilege
HlalanathiMashimbye Jul 28, 2026
a2fe598
Merge branch 'fix/firestore-rules-lockdown' into feat/hash-learner-email
HlalanathiMashimbye Jul 28, 2026
7999ac3
feat(privacy): store a hash of the learner email on missions, never t…
HlalanathiMashimbye Jul 28, 2026
a70fce7
chore(privacy): redact addresses from unreachable learner records
HlalanathiMashimbye Jul 28, 2026
f85f7a4
Merge branch 'feat/hash-learner-email'
HlalanathiMashimbye Jul 28, 2026
6127c9c
Merge branch 'fix/yard-colour-alignment'
HlalanathiMashimbye Jul 28, 2026
3a8bc7d
Merge branch 'fix/terraform-region-and-part-a'
HlalanathiMashimbye Jul 28, 2026
8b178df
fix(operator-console): make mission locking actually work, and cover it
HlalanathiMashimbye Jul 28, 2026
9212a81
Merge branch 'feat/MissionLock'
HlalanathiMashimbye Jul 28, 2026
4bc94d4
fix(mission-control): drop the invalid duplicate ignoreDeprecations
HlalanathiMashimbye Jul 28, 2026
5c84162
Push and then pull works
kamolinks Jul 28, 2026
f47b400
Push and then pull works
kamolinks Jul 28, 2026
f13128b
Merge branch 'main' into feat/SatelliteMissionMirror
HlalanathiMashimbye Jul 28, 2026
f84f812
feat(satellite): complete PR 2 - offline mission mirror, scoped to th…
HlalanathiMashimbye Jul 28, 2026
f241ab4
Merge branch 'feat/SatelliteMissionMirror'
HlalanathiMashimbye Jul 28, 2026
0409045
feat(satellite): complete PR 3 - outbox and push-before-pull sync
HlalanathiMashimbye Jul 28, 2026
e6fe526
Merge branch 'feat/OfflineFirstSync'
HlalanathiMashimbye Jul 28, 2026
a97c9fb
chore(satellite): gitignore the local mission mirror database
HlalanathiMashimbye Jul 28, 2026
008a0e7
feat(satellite): PR 4 - recovery and conflict surfacing
HlalanathiMashimbye Jul 28, 2026
82ac1bf
Merge branch 'feat/RecoveryAndConflicts'
HlalanathiMashimbye Jul 28, 2026
a753974
fix(satellite): read the mission id from the right place in rover his…
HlalanathiMashimbye Jul 29, 2026
d658647
feat(satellite): complete missions the rover confirms it ran
HlalanathiMashimbye Jul 29, 2026
d59d6c3
feat(satellite): fall back to a plain webcam when there is no Pi camera
HlalanathiMashimbye Jul 29, 2026
30516c6
feat(satellite): operator console revamp
HlalanathiMashimbye Jul 29, 2026
bbc66bf
perf(satellite): make the sync incremental - 576,000 to ~10,000 reads…
HlalanathiMashimbye Jul 29, 2026
a79197a
perf(mission-control): stop the feed reading 125 documents to show 24
HlalanathiMashimbye Jul 29, 2026
fc7f536
refactor(mission-control): delete unreachable code
HlalanathiMashimbye Jul 29, 2026
f941cde
feat(satellite): let an operator delete a mission, with a real warning
HlalanathiMashimbye Jul 29, 2026
4a44800
Feat/camera console control (#43)
HlalanathiMashimbye Jul 29, 2026
29a9eaa
fix(blockly): cap the uplink hat at one, and make the cleanup actuall…
HlalanathiMashimbye Aug 1, 2026
37b8d1a
feat: UI refinements - light mode, fluid width, motion, and a drag di…
HlalanathiMashimbye Aug 2, 2026
e7f498a
fix: stop learner email addresses being harvestable from the public f…
HlalanathiMashimbye Aug 2, 2026
4786605
feat: fall back to Application Default Credentials in firebase-admin …
HlalanathiMashimbye Aug 2, 2026
926a0cb
chore: dev-to-prod migration and operator role-grant scripts (#47)
HlalanathiMashimbye Aug 2, 2026
f2db9ef
feat(mission-control): carry a hashed learner ref on public missions …
HlalanathiMashimbye Aug 2, 2026
ace5bc3
feat(mission-control): Paper & Ink light theme, navbar search, card r…
HlalanathiMashimbye Aug 3, 2026
8ffaa50
fix(infra): default region to africa-south1 to match state and CI (#49)
HlalanathiMashimbye Aug 3, 2026
d2c3678
feat(infra): run terraform plan on PRs touching infra/ (#38)
HlalanathiMashimbye Aug 3, 2026
147d6a0
fix(yard): make the operator console tell the truth, and give the rov…
HlalanathiMashimbye Aug 10, 2026
e3e4e5d
Add interactive code walkthrough page (#53)
HlalanathiMashimbye Aug 10, 2026
0f2c815
Reorganise root scripts and refresh docs (#54)
HlalanathiMashimbye Aug 10, 2026
d231b52
feat(yard): finish the Settings page redesign, and repair what the mo…
HlalanathiMashimbye Aug 10, 2026
7ea5675
docs: architecture diagram spec, generation prompt and design decisio…
HlalanathiMashimbye Aug 10, 2026
88d5b66
docs(yard): correct the satellite port everywhere, and write up the t…
HlalanathiMashimbye Aug 10, 2026
0ad07a1
fix(mission-control): one clear button, centred blocks, and a bell th…
HlalanathiMashimbye Aug 10, 2026
77aff0d
Fix/mission control bugs (#59)
HlalanathiMashimbye Aug 10, 2026
7ae1e32
feat(infra): host Mission Control on a personal GCP project for the d…
HlalanathiMashimbye Aug 10, 2026
c87e002
feat(mission-control): use the navbar rover as the icon and link prev…
HlalanathiMashimbye Aug 10, 2026
f8e8c28
fix(mission-control): describe what the platform actually does (#62)
HlalanathiMashimbye Aug 10, 2026
1e0c711
fix(mission-control): make the workspace pages usable on a phone (#63)
HlalanathiMashimbye Aug 10, 2026
a1199b0
feat(mission-control): confirm a mission was sent, after the email qu…
HlalanathiMashimbye Aug 11, 2026
96ef8b4
docs: name the ReadMe for Team06 (#65)
HlalanathiMashimbye Aug 11, 2026
1a84903
fix: make a fresh checkout runnable with one npm install (#66)
HlalanathiMashimbye Aug 11, 2026
5c7cc59
docs: show how to set up the Python environment (#67)
HlalanathiMashimbye Aug 11, 2026
3e0c4b7
Add updated code walkthrough v2
CLXKON001 Aug 12, 2026
5cbef9f
Merge pull request #70 from HlalanathiMashimbye/feat--updated-code-wa…
CLXKON001 Aug 12, 2026
8272026
docs: rework the code walkthrough against the rubric, as slides-for-c…
HlalanathiMashimbye Aug 12, 2026
ddbda81
fix(yard): make STOP fire on the first press, and survive being press…
HlalanathiMashimbye Aug 12, 2026
5d138c9
feat(infra): put Mission Control behind an external load balancer (#69)
wernervrens Aug 13, 2026
8ebcc6f
fix(email): deep link the mission email to the mission, from a runtim…
HlalanathiMashimbye Aug 20, 2026
483629b
infra: point Impact at the marsyard domains, HTTPS and per-env APP_UR…
HlalanathiMashimbye Aug 20, 2026
9adb3ab
fix(infra): authenticate to Firestore as the runtime SA, not a mounte…
HlalanathiMashimbye Aug 20, 2026
455ad4f
fix(email): report the notification outcome instead of discarding it …
HlalanathiMashimbye Aug 20, 2026
162ab9f
chore: remove the standalone code-walkthrough HTML (#77)
HlalanathiMashimbye Aug 24, 2026
28a79a1
fix(yard): surface runs the rover refused, and stop the review count …
HlalanathiMashimbye Aug 24, 2026
8f65e22
ci: run the browser tests, which ran nowhere (#80)
HlalanathiMashimbye Aug 24, 2026
51d1cd1
chore: phase 0 foundations for the unified operator console (#81)
HlalanathiMashimbye Aug 24, 2026
f74761d
refactor: build the yard editor's Blockly from mission-control's sour…
HlalanathiMashimbye Aug 24, 2026
7c730f0
fix(ui): read APP_ENV at request time, so staging stops calling itsel…
HlalanathiMashimbye Aug 25, 2026
800086c
feat(mission): auto-generate mission name, remove manual entry
CLXKON001 Aug 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .claude/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"version": "0.0.1",
"configurations": [
{
"name": "mission-control",
"runtimeExecutable": "npm",
"runtimeArgs": ["run", "dev", "--prefix", "mission-control"],
"port": 3000
},
{
"name": "yard-satellite",
"runtimeExecutable": "bash",
"runtimeArgs": [
"-c",
"set -a && source mission-control/.env && cd yard/satellite && python3 web_server.py"
],
"port": 3001
}
]
}
127 changes: 127 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
name: CI

# Build + test gate. Set these jobs as REQUIRED status checks on main
# (Settings → Branches → main → Require status checks to pass) so a red
# build can never be merged again.

on:
pull_request:
branches: [main]
push:
branches: [main]

# Public Firebase web config is required at build time. These are placeholders
# so `next build` is deterministic in CI; they are NOT secrets (real values live
# in each app's .env, which is git-ignored).
env:
NEXT_PUBLIC_FIREBASE_API_KEY: ci-placeholder
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN: ci-placeholder.firebaseapp.com
NEXT_PUBLIC_FIREBASE_PROJECT_ID: ci-placeholder
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET: ci-placeholder.appspot.com
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID: "0000000000"
NEXT_PUBLIC_FIREBASE_APP_ID: "1:0000000000:web:0000000000000000000000"

jobs:
mission-control:
name: mission-control (build + test)
runs-on: ubuntu-latest
defaults:
run:
working-directory: mission-control
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
cache-dependency-path: mission-control/package-lock.json
- run: npm ci
- run: npm run lint
# The yard satellite serves a compiled copy of the rover simulator so its
# monitor page can animate a fake rover with the same physics and
# renderer the learner's mission page uses. That copy is committed (the
# Pi has no Node toolchain), so this fails the build if it no longer
# matches the TypeScript it was built from.
- run: npm run check:roversim
- run: npx next build
- run: npx jest --ci

yard-rover:
name: yard rover (python tests)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Install rover deps
run: pip install -r yard/rover/requirements.txt
- name: Run rover tests
working-directory: yard/rover
run: python -m pytest -q

# The satellite is where mission locking, the offline sync worker, the
# operator console and the rover stop-control all live, and none of it was
# gated: this job did not exist, so ~200 tests covering the most
# safety-relevant code in the repo could go red without failing a build.
yard-satellite:
name: yard satellite (python tests)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Install satellite test deps
run: pip install -r yard/satellite/requirements-test.txt
- name: Run satellite tests
working-directory: yard/satellite
run: python -m pytest tests -q

# The two browser-driven files (test_blockly_codegen, test_status_page) are
# skipped by tests/conftest.py whenever playwright is absent, and
# requirements-test.txt deliberately leaves it out so a plain `pytest tests`
# works anywhere. The consequence was that they ran on nobody's machine
# unless someone installed playwright by hand: the yard editor's Blockly
# generator, the one thing a learner's program passes through before it
# reaches a rover, had no gate at all.
#
# Caught for real. Moving the block definitions into a shared module turned
# code.html's script into an ES module, module scope is not global, and all
# seven codegen tests broke. Nothing in CI would have said so.
#
# A separate job because it is the slow one: it downloads a browser (cached
# below) and needs network access for Blockly from unpkg. Keeping it out of
# yard-satellite leaves that job's ~2s feedback intact.
yard-browser:
name: yard browser tests (blockly codegen)
runs-on: ubuntu-latest
env:
# Pinned rather than floating, for the same reason terraform_version is:
# a new release should not change what CI does under the team, and the
# browser cache below is keyed on this exact value.
PLAYWRIGHT_VERSION: "1.62.0"
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Install satellite test deps
run: |
pip install -r yard/satellite/requirements-test.txt
pip install "playwright==${PLAYWRIGHT_VERSION}" pytest-playwright

# Chromium is ~95MB. Keyed on the pinned version so bumping it fetches a
# matching browser instead of silently reusing an incompatible one.
- uses: actions/cache@v4
id: pw-cache
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ env.PLAYWRIGHT_VERSION }}
- name: Install chromium
if: steps.pw-cache.outputs.cache-hit != 'true'
run: python -m playwright install --with-deps chromium

- name: Run browser tests
working-directory: yard/satellite
run: python -m pytest tests/test_blockly_codegen.py tests/test_status_page.py -q
73 changes: 73 additions & 0 deletions .github/workflows/deploy-prod.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
name: Deploy prod

# Prod promotion (Werner's deploy guide, Phase 3).
#
# Manually triggered, gated by the GitHub "production" Environment (configure
# required reviewers there: Werner and/or Gavin approve). Promotes the image
# digest CURRENTLY SERVING on staging to the prod service: the exact bytes
# that were smoke-checked, no rebuild.
#
# Rollback is the same mechanism in reverse: rerun this workflow after
# pointing staging at the previous digest, or
# gcloud run services update-traffic / gcloud run deploy --image <old digest>
#
# Variables (from Terraform outputs): GCP_WIF_PROVIDER, GCP_DEPLOY_SA,
# GCP_PROJECT_ID, GCP_REGION, STAGING_SERVICE, PROD_SERVICE, PROD_URL.
# PROD_URL is the load-balancer URL (service_urls.prod), not *.run.app.

on:
workflow_dispatch:

permissions:
contents: read
id-token: write # OIDC token for WIF

concurrency:
group: deploy-prod
cancel-in-progress: false

jobs:
promote:
name: Promote staging digest to prod
runs-on: ubuntu-latest
environment: production
steps:
- id: auth
uses: google-github-actions/auth@v2
with:
workload_identity_provider: ${{ vars.GCP_WIF_PROVIDER }}
service_account: ${{ vars.GCP_DEPLOY_SA }}

- uses: google-github-actions/setup-gcloud@v2

- name: Read the digest serving on staging
run: |
IMAGE=$(gcloud run services describe "${{ vars.STAGING_SERVICE }}" \
--project "${{ vars.GCP_PROJECT_ID }}" --region "${{ vars.GCP_REGION }}" \
--format='value(spec.template.spec.containers[0].image)')
echo "Promoting: $IMAGE"
case "$IMAGE" in
*@sha256:*) ;;
*) echo "Staging is not pinned to a digest; refusing to promote a mutable tag" >&2; exit 1 ;;
esac
echo "IMAGE=$IMAGE" >> "$GITHUB_ENV"

- name: Deploy Cloud Run prod
run: |
gcloud run deploy "${{ vars.PROD_SERVICE }}" \
--project "${{ vars.GCP_PROJECT_ID }}" \
--region "${{ vars.GCP_REGION }}" \
--image "$IMAGE" \
--quiet

- name: Smoke check
run: |
URL="${{ vars.PROD_URL }}"
if [ -z "$URL" ]; then
echo "PROD_URL GitHub variable is unset; copy terraform output service_urls.prod" >&2
exit 1
fi
echo "Prod URL: $URL"
curl -sSf -o /dev/null "$URL/"
test "$(curl -s -o /dev/null -w '%{http_code}' "$URL/operator")" = "404"
echo "Smoke check passed"
102 changes: 102 additions & 0 deletions .github/workflows/deploy-staging.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
name: Deploy staging

# CD for mission-control (Werner's deploy guide, Phase 2).
#
# Runs only after the CI workflow finishes GREEN on main: build the Docker
# image, push it to Artifact Registry tagged with the git SHA (immutable
# artifact), deploy Cloud Run staging to that exact digest, then smoke-check.
#
# Auth is Workload Identity Federation (GitHub OIDC -> deploy service
# account): no downloaded JSON keys anywhere. All environment-specific values
# come from GitHub *variables* (Settings -> Secrets and variables -> Actions
# -> Variables), which are filled from Terraform outputs once Phase 1 lands:
#
# GCP_WIF_PROVIDER projects/N/locations/global/workloadIdentityPools/github/providers/github
# GCP_DEPLOY_SA deploy SA email (push image + update Cloud Run)
# GCP_PROJECT_ID bt-impact-academy
# GCP_REGION e.g. africa-south1
# GCP_AR_REPO Artifact Registry docker repo name
# STAGING_SERVICE e.g. mission-control-staging
# STAGING_URL load-balancer URL from terraform output service_urls.staging
# (NOT the Cloud Run *.run.app URI — that 403s without allUsers)
# NEXT_PUBLIC_FIREBASE_* public web config for the staging Firebase app
# NEXT_PUBLIC_APP_URL same as STAGING_URL (baked into the image at build time)
#
# Server secrets (FIREBASE_* admin credentials) are NOT here: Terraform wires
# them from Secret Manager onto the Cloud Run service itself.

on:
workflow_run:
workflows: [CI]
types: [completed]
branches: [main]

permissions:
contents: read
id-token: write # OIDC token for WIF

concurrency:
group: deploy-staging
cancel-in-progress: false

jobs:
deploy:
name: Build, push, deploy staging
runs-on: ubuntu-latest
if: github.event.workflow_run.conclusion == 'success'
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.workflow_run.head_sha }}

- id: auth
uses: google-github-actions/auth@v2
with:
workload_identity_provider: ${{ vars.GCP_WIF_PROVIDER }}
service_account: ${{ vars.GCP_DEPLOY_SA }}

- uses: google-github-actions/setup-gcloud@v2

- name: Configure docker for Artifact Registry
run: gcloud auth configure-docker ${{ vars.GCP_REGION }}-docker.pkg.dev --quiet

- name: Build image (tag = git SHA)
working-directory: mission-control
run: |
IMAGE="${{ vars.GCP_REGION }}-docker.pkg.dev/${{ vars.GCP_PROJECT_ID }}/${{ vars.GCP_AR_REPO }}/mission-control:${{ github.event.workflow_run.head_sha }}"
echo "IMAGE=$IMAGE" >> "$GITHUB_ENV"
docker build \
--build-arg NEXT_PUBLIC_APP_URL='${{ vars.NEXT_PUBLIC_APP_URL }}' \
--build-arg NEXT_PUBLIC_FIREBASE_API_KEY='${{ vars.NEXT_PUBLIC_FIREBASE_API_KEY }}' \
--build-arg NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN='${{ vars.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN }}' \
--build-arg NEXT_PUBLIC_FIREBASE_PROJECT_ID='${{ vars.NEXT_PUBLIC_FIREBASE_PROJECT_ID }}' \
--build-arg NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET='${{ vars.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET }}' \
--build-arg NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID='${{ vars.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID }}' \
--build-arg NEXT_PUBLIC_FIREBASE_APP_ID='${{ vars.NEXT_PUBLIC_FIREBASE_APP_ID }}' \
--build-arg NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID='${{ vars.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID }}' \
-t "$IMAGE" .

- name: Push image
run: docker push "$IMAGE"

- name: Deploy Cloud Run staging (by digest)
run: |
DIGEST=$(gcloud artifacts docker images describe "$IMAGE" --format='value(image_summary.digest)')
gcloud run deploy "${{ vars.STAGING_SERVICE }}" \
--project "${{ vars.GCP_PROJECT_ID }}" \
--region "${{ vars.GCP_REGION }}" \
--image "${IMAGE%%:*}@${DIGEST}" \
--quiet

- name: Smoke check
run: |
URL="${{ vars.STAGING_URL }}"
if [ -z "$URL" ]; then
echo "STAGING_URL GitHub variable is unset; copy terraform output service_urls.staging" >&2
exit 1
fi
echo "Staging URL: $URL"
# Learner home must serve via the load balancer; the operator surface must NOT exist.
curl -sSf -o /dev/null "$URL/"
test "$(curl -s -o /dev/null -w '%{http_code}' "$URL/operator")" = "404"
echo "Smoke check passed"
Loading