Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
34c9a2b
feat(snow,rain): ICEFIELD snow + Tempest rain starter kits, engine su…
jonit-dev Oct 2, 2026
d727783
fix(playtest): retry a dropped adb transport before failing Android c…
jonit-dev Oct 2, 2026
d43e001
fix(create-threenative): doctor accepts a patch difference inside one…
jonit-dev Oct 2, 2026
6c8858d
fix(create-threenative): the rain verifier imports its TS source by i…
jonit-dev Oct 2, 2026
416ffd7
docs(release): record the published 0.3.4 cohort and what it took
jonit-dev Oct 2, 2026
924b92f
feat(playtest): gate captured frame tone (PRD-341) (#392)
jonit-dev Oct 2, 2026
304b2f4
docs: collect visual-quality execution batch and gap PRDs (#391)
jonit-dev Oct 2, 2026
569fdb2
feat(core): FluidParticles3D, GPU particle water (PRD-476) (#389)
jonit-dev Oct 2, 2026
42fa306
perf(core): PRD-475 open-world frame cuts, partial — walking p95 move…
jonit-dev Oct 2, 2026
840d353
feat(shooter): qualify bounded receiver decals with atlas and fade [V…
jonit-dev Oct 2, 2026
2f47bfc
docs(prd): PRD-476 Fluid Lab particle water on the GPU at 60 fps
jonit-dev Oct 2, 2026
d7d1a5c
docs(create-threenative): FabCLI manual-login fallback when the WebVi…
jonit-dev Oct 2, 2026
8d9ec8c
docs(prd): add PRD for native overlay utility styling with Tailwind a…
jonit-dev Oct 2, 2026
596d979
docs(prd): PRD-480 Linux CI runs on the owner machine
jonit-dev Oct 2, 2026
b66dffa
docs(prd): PRD-480 removes the duplicate full-board run
jonit-dev Oct 2, 2026
f86bb83
docs(prd): PRD-480 proof marker on the AGENTS box
jonit-dev Oct 2, 2026
b865853
docs(prd): PRD-481 CI does each piece of work once, PRD-482 local age…
jonit-dev Oct 2, 2026
ef5ac1e
docs(prd): PRD-481 names the change-scope spec and a proof per box
jonit-dev Oct 2, 2026
38c8ba0
docs(prd): CI PRD execution order; close PRD-379, archive PRD-303, dr…
jonit-dev Oct 2, 2026
e10b766
docs(prd): CI execution order works as a goal; runner token done, for…
jonit-dev Oct 2, 2026
af7e253
fix(tooling): pre-push lint and docs stages green on clean develop
jonit-dev Oct 2, 2026
114e268
ci(prd-481): CI does each piece of work once (#405)
jonit-dev Oct 3, 2026
3fd7129
ci(prd-380): a pull request never starves the runner pool (#406)
jonit-dev Oct 3, 2026
5490c3e
ci(prd-482): the local agent loop costs only what it catches (#403)
jonit-dev Oct 3, 2026
2ed9b08
docs(prd): CI execution order state, Astra review, no workflow file p…
jonit-dev Oct 3, 2026
d99a628
ci(prd-480): Linux CI runs on the owner machine (#404)
jonit-dev Oct 3, 2026
57504d8
ci(prd-480): one-command runner setup (pnpm ci:runners:install), oper…
jonit-dev Oct 3, 2026
dd4111d
ci: template legs run hosted so a board uses both pools at once (#411)
jonit-dev Oct 3, 2026
f16bd0c
ci: PRD-380 phase 2 + CI follow-ups (janitor merge groups, per-job re…
jonit-dev Oct 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
22 changes: 0 additions & 22 deletions .claude/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,6 @@
}
]
}
],
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/ponytail-context.mjs\"",
"timeout": 5
}
]
}
],
"SubagentStart": [
{
"hooks": [
{
"type": "command",
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/ponytail-context.mjs\"",
"timeout": 5
}
]
}
]
}
}
29 changes: 24 additions & 5 deletions .github/actions/workspace-dist/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ inputs:
workspace product.
required: false
default: "true"
shared-artifact:
description: >-
Name of this run's uploaded workspace to take the compiled bundles from, instead of restoring
them from the cache or building them again. Empty for the job that produces the workspace.
Consumers set it because they all `needs:` that producer: an unfiltered `needs: scope` starts
them in parallel with it, so every one of them looked for a key nobody had saved yet and
compiled the workspace itself (measured at 71-95s each, nine times a run, PRD-481). Such a
consumer must keep `save-bundles: "false"` — there is no key of its own to publish.
required: false
default: ""

runs:
using: composite
Expand All @@ -32,6 +42,14 @@ runs:
run: |
echo "node=$(node --version)" >> "$GITHUB_OUTPUT"
echo "started=$(date +%s)" >> "$GITHUB_OUTPUT"
# The producer's own path is below: restore the key, or build it when the key misses. Every
# consumer takes the upload instead, which is this run's exact bytes for this commit rather than
# whichever earlier run last published this key.
- name: Take the compiled workspace from this run's producer
if: inputs.shared-artifact != ''
uses: actions/download-artifact@v4
with:
name: ${{ inputs.shared-artifact }}
# The key names everything the bundles are built from and nothing else. `src/**`, the package
# manifests, the tsup configs, the per-package generators that write files into the bundle, and
# the script that decides which packages get built at all. Templates are deliberately absent:
Expand All @@ -43,6 +61,7 @@ runs:
# rebuild nobody needed.
- name: Restore the compiled workspace
id: dist
if: inputs.shared-artifact == ''
uses: actions/cache/restore@v4
env:
# Rebuilding these small bundles is cheaper than the default ten-minute segment wait.
Expand All @@ -62,13 +81,13 @@ runs:
echo "workspace dist restored for these sources; skipping the build"
node packages/core/scripts/bundle-engine-mcp.mjs
- name: Build missing workspace bundles
if: steps.dist.outputs.cache-hit != 'true'
if: steps.dist.outputs.cache-hit != 'true' && inputs.shared-artifact == ''
shell: bash
run: |
set -euo pipefail
pnpm tsx scripts/workspace-packages.ts build
- name: Pack current workspace files
if: inputs.pack-archives != 'false'
if: inputs.pack-archives != 'false' && inputs.shared-artifact == ''
shell: bash
run: |
set -euo pipefail
Expand Down Expand Up @@ -159,9 +178,9 @@ runs:
if: always()
shell: bash
env:
DIST_CACHE_HIT: ${{ steps.dist.outputs.cache-hit }}
DIST_SOURCE: ${{ inputs.shared-artifact != '' && format('artifact `{0}`', inputs.shared-artifact) || format('cache hit `{0}`', steps.dist.outputs.cache-hit || 'unavailable') }}
DIST_SAVER: ${{ inputs.save-bundles == 'true' && 'yes' || 'no' }}
DIST_STARTED: ${{ steps.toolchain.outputs.started }}
run: |
printf 'Workspace bundle cache hit: `%s`; publish: `%s`; restore/build/repack/validation execution: `%ss`. This is product reuse, not a test verdict.\n' \
"${DIST_CACHE_HIT:-unavailable}" "${DIST_SAVER:-unavailable}" "$(( $(date +%s) - ${DIST_STARTED:-$(date +%s)} ))" >> "$GITHUB_STEP_SUMMARY"
printf 'Workspace bundles from: %s; publish: `%s`; restore/build/repack/validation execution: `%ss`. This is product reuse, not a test verdict.\n' \
"${DIST_SOURCE:-unavailable}" "${DIST_SAVER:-unavailable}" "$(( $(date +%s) - ${DIST_STARTED:-$(date +%s)} ))" >> "$GITHUB_STEP_SUMMARY"
107 changes: 107 additions & 0 deletions .github/workflows/ci-janitor.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
name: CI janitor

# A merged or closed pull request's runs prove nothing: no verdict can change, no artifact gets
# read, and the head ref keeps holding runners until each one finishes on its own clock. That
# cost another 5.0k runner-minutes over 2026-09-18 to 10-02 (PRD-380). Cancelling the head ref's
# in-flight runs — and the merge group's, which GitHub starts for a queued pull request and then
# leaves running after it leaves the queue — is the only cleanup a repository can do for itself, and
# `pull_request: closed` reaches it within a minute of the merge.
on:
pull_request:
types: [closed]
# A failed CI run whose every failure is a known environmental flake reruns only its failed jobs,
# once. Runs from the default branch's copy of this file (a workflow_run rule).
workflow_run:
workflows: [CI]
types: [completed]

# Cancelling a run is an Actions write, and this is the one workflow here that asks for one. A
# token that cannot do it reports a green job while cancelling nothing, which is the exact
# failure this workflow exists to prevent — so the grant is explicit rather than inherited.
permissions:
actions: write

jobs:
retry-flakes:
if: >-
github.event_name == 'workflow_run' &&
github.event.workflow_run.conclusion == 'failure' &&
github.event.workflow_run.run_attempt == 1
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Rerun the failed jobs once when every failure is a known flake
env:
GH_TOKEN: ${{ github.token }}
RUN_ID: ${{ github.event.workflow_run.id }}
# Environmental signatures seen on 2026-10-02/03: SwiftShader device loss, a bridge call
# outliving its budget, an unreadable WebGPU adapter, a dev server not up yet, a 5xx on a
# download, and a runner shut down under its job. Anything else is a real failure.
FLAKE: TN_PLAYTEST_SOFTWARE_DEVICE_LOST|Bridge operation '[a-z]+' exceeded|TN_PLAYTEST_CAPTURE_PROVENANCE_MISSING|TN_GOLDEN_PATH_DEV_UNAVAILABLE|ERR_CONNECTION_REFUSED|HTTP 50[0-9]|runner has received a shutdown signal
run: |
set -euo pipefail
# Aggregates (ci-required, golden-path, run-summary) only echo another job's failure.
failed="$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/$RUN_ID/jobs?filter=latest&per_page=100" --paginate \
--jq '.jobs[] | select(.conclusion == "failure") | select(.name | test("^(ci-required|golden-path|run-summary)$") | not) | .id')"
[ -n "$failed" ] || { echo "No primary job failed; nothing to retry."; exit 0; }
for id in $failed; do
if ! gh api "repos/$GITHUB_REPOSITORY/actions/jobs/$id/logs" 2>/dev/null | grep -qE "$FLAKE"; then
echo "Job $id failed for a reason that is not a known flake; leaving the run red."
exit 0
fi
done
gh run rerun "$RUN_ID" --failed --repo "$GITHUB_REPOSITORY"
echo "Reran the failed jobs of run $RUN_ID once: every failure matched a known flake." >> "$GITHUB_STEP_SUMMARY"

cancel:
# A fork's runs live in another repository, and its runs cannot be cancelled from here. Trying
# would put a red check on every fork PR close, so the guard is the whole job.
# A promotion PR's head is `develop`: its runs are develop's own CI, never a lane to sweep.
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.head.ref != 'develop' && github.event.pull_request.head.ref != 'main'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
# The head ref is branch text chosen by whoever opened the PR. Interpolated into `run:` it
# is a command injection into a job holding `actions: write`, so it arrives through `env:`
# and every other value below is quoted.
- name: Cancel the head ref's and the merge group's queued and in-progress runs
env:
GH_TOKEN: ${{ github.token }}
HEAD_REF: ${{ github.event.pull_request.head.ref }}
BASE_REF: ${{ github.event.pull_request.base.ref }}
PR_NUMBER: ${{ github.event.pull_request.number }}
SELF_RUN_ID: ${{ github.run_id }}
run: |
set -euo pipefail
# A merge-group run reports its branch as `gh-readonly-queue/<base>/pr-<number>-<sha>`.
# GitHub drops the pull request from the queue on close but never cancels the run it has
# already started, so that run holds runners for a verdict nothing will read: on
# 2026-10-02 run 37093098790 kept nine self-hosted jobs busy for #404 after it merged.
# Built from the event's own base ref and number, so it reaches that group and no other.
group_prefix="gh-readonly-queue/${BASE_REF}/pr-${PR_NUMBER}-"
# Collected before the loop, so a failed lookup fails this step instead of quietly
# cancelling nothing under `set -e`.
runs="$(gh run list --repo "$GITHUB_REPOSITORY" --branch "$HEAD_REF" \
--json databaseId,status,headBranch \
--jq '.[] | select(.status == "queued" or .status == "in_progress") | [.databaseId, .headBranch] | @tsv'
gh run list --repo "$GITHUB_REPOSITORY" --json databaseId,status,headBranch --limit 100 \
| jq -r --arg prefix "$group_prefix" '.[] | select(.status == "queued" or .status == "in_progress") | select(.headBranch | startswith($prefix)) | [.databaseId, .headBranch] | @tsv'
)"
cancelled=0
while IFS=$'\t' read -r run_id branch; do
# A fork's branch may legitimately share this repository's ref name; never reach
# past the event's own repository for something to cancel.
[ -n "$run_id" ] || continue
[ "$run_id" != "$SELF_RUN_ID" ] || continue
# A run that completed between the list and the cancel is the normal race, not a
# failure: the point of the cancel is already satisfied.
# force-cancel, not `gh run cancel`: a plain cancel can hang on a job still queued for a
# self-hosted label and keep the run's concurrency slot (run 37098559814).
if gh api -X POST "repos/$GITHUB_REPOSITORY/actions/runs/$run_id/force-cancel" >/dev/null; then
cancelled=$((cancelled + 1))
printf 'Cancelled run %s on %s.\n' "$run_id" "$branch"
else
printf 'Run %s finished before the cancel; nothing to stop.\n' "$run_id"
fi
done <<< "$runs"
printf 'Janitor finished for %s: %s run(s) cancelled.\n' "$HEAD_REF" "$cancelled"
Loading
Loading