Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
**/src/generated/**/*.json text eol=lf
docs/architecture/evidence/*.json text eol=lf

# Canonical replay fixtures are compared byte-for-byte with the LF-only codec.
platform/minecraft/src/test/resources/ca/teamdman/sfm/client/history/replay/*.json text eol=lf

# This checked-in parser fixture deliberately exercises CRLF coordinates while
# keeping the repository blob normalized for whitespace review.
platform/cli/sfm-propagate-changes/tests/java_analysis/scenarios/definition_at_position_unicode_crlf/source/**/*.java text eol=crlf
93 changes: 93 additions & 0 deletions .github/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# SFM 1.19.2 verification

`workflows/ci.yml` verifies the checked-out 1.19.2 source with the repository's
Rust toolchain. Its three jobs run independently:

| Job | Passing evidence |
| --- | --- |
| Container software graphics and isolation | Xvfb/Mesa llvmpipe with the asserted container restrictions |
| Compile, test and package | Current-source CLI, all Java source sets, JUnit success and a distributable mod JAR |
| Isolated Minecraft client | A fresh offline puppet run with three title captures and eight world captures |

The delivery output is a downloadable Actions artifact named
`sfm-1.19.2-<commit>`. Build, graphics and game evidence have separate artifacts,
including diagnostics from failed jobs. Retention is 14 days. The workflow uses
read-only repository permissions and needs no mod publishing or Discord secrets.

`workflows/windows-ci.yml` runs the same canonical compile, JUnit and JAR checks
on Windows. Its mod artifact is named `sfm-1.19.2-windows-<commit>`.
`workflows/vox-diagnostic.yml` is a separate dependency investigation, triggered
only by its diagnostic branch or a manual run. It does not supply artifacts to
the SFM build.

## Observed experiment results

The [final Linux/Docker PR run](https://github.com/TeamDman/SuperFactoryManager/actions/runs/35481820090)
passed all three jobs at source `d7e22e73e`, tested as PR merge revision
`77e42edb0469ae9ca71b19b5b677c12cf245e79e`. Linux passed 2,075 Java tests with
zero failures, one Windows-only skip and six existing opt-in tests aborted by
their prerequisites. Its packaged mod contains the new Vox JAR with the exact
expected hash. The fresh offline container completed both puppets and all 11
PNG files decoded successfully. The [Windows PR run](https://github.com/TeamDman/SuperFactoryManager/actions/runs/35481820091)
also passed compilation, 2,076 Java tests and packaging, with zero failures and
the same six opt-in tests aborted by their prerequisites. Both platforms embed
identical Vox JAR bytes; the complete mod archives are not byte-identical.

| Evidence | Result |
| --- | --- |
| [First feature-branch run](https://github.com/TeamDman/SuperFactoryManager/actions/runs/35460447959) | Push trigger, Linux CLI and restricted software graphics passed; dependency preparation failed |
| [Second Linux/Docker run](https://github.com/TeamDman/SuperFactoryManager/actions/runs/35461041230) | UTF-8 container compilation fixed; both builds stopped in the pinned Vox Java test; graphics passed again |
| [Focused Vox diagnostic](https://github.com/TeamDman/SuperFactoryManager/actions/runs/35461627486) | Captured `message for unknown channel 1:1` before the connection closes |
| [Vox candidate comparison](https://github.com/TeamDman/SuperFactoryManager/actions/runs/35462115154) | Candidate passed the original test and 19 reduced cases; unchanged baseline also passed on this run, confirming the full-test failure is timing-sensitive |
| [Reduced Linux reproduction](https://github.com/TeamDman/SuperFactoryManager/actions/runs/35462297128) | Identical credit passes before sender Close and reproduces the exact unknown-channel failure after Close in unchanged pinned code |
| [First Windows build](https://github.com/TeamDman/SuperFactoryManager/actions/runs/35461257226) | Vox suite and deterministic JAR passed; a global Java-options banner incorrectly failed the dependency check |
| [Third Linux/Docker run](https://github.com/TeamDman/SuperFactoryManager/actions/runs/35462847488) | Linux compiled SFM then found Windows-specific JUnit fixtures; Docker built the mod and completed both puppets, but the verifier read CLI progress instead of the raw game log |
| [Second Windows run](https://github.com/TeamDman/SuperFactoryManager/actions/runs/35462847520) | Mod compilation passed; two canonical replay tests found Git's CRLF conversion of their byte-exact JSON fixture |
| [Final Linux/Docker PR run](https://github.com/TeamDman/SuperFactoryManager/actions/runs/35481820090) | All jobs passed: native compilation/JUnit/package, software graphics and the fresh restricted offline game |
| [Final Windows PR run](https://github.com/TeamDman/SuperFactoryManager/actions/runs/35481820091) | Compilation, all enabled Java tests, packaging and dependency checks passed; verified mod uploaded |

With the user's authorization, SFM now pins `org.facet:vox-java:0.10.0-rc.5`
to Facet revision `4a079ac1c8a8bb8a914811ef55945bc1d9a9fef3`, published on
`teamy/vox-java-late-credit` in [Facet PR #2](https://github.com/TeamDman/facet/pull/2).
Its full canonical package recipe passed locally, including 19 regression cases,
with content hash `blake3:2be34a7d38bbd4a455d2a933c856c9462630f47a`.
All other project dependencies remain unchanged. Source tests and artifact hashes
are enforced. The [diagnostic](../containers/sfm/vox-diagnostic/README.md)
retains the original pinned reproduction and candidate comparison.

## Worktrees and feature branches

A worktree is a local checkout. GitHub receives its branch and commits through
an ordinary push; the local directory does not affect Actions.

This experiment triggers on pushes to `1.19.2` and
`ci/1.19.2-container-puppet`, and on pull requests targeting `1.19.2`. The
initial experiment triggered successfully at `f7dc28338` before any merge into
the default branch. GitHub's PR checkout is a merge revision; the workflow
creates a local `sfm-ci-checkout` branch at that same revision so the SFM CLI's
worktree selector can find it.

Use a push for the first experiment. The manual workflow button depends on
workflow discovery on the default branch; a feature-only workflow does not need
that button to receive push events.
[GitHub workflow events](https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows),
[manual runs](https://docs.github.com/en/actions/how-tos/manage-workflow-runs/manually-run-a-workflow).

```bash
git push origin ci/1.19.2-container-puppet
gh run list --repo TeamDman/SuperFactoryManager --branch ci/1.19.2-container-puppet
gh run view <run-id> --repo TeamDman/SuperFactoryManager
gh run download <run-id> --repo TeamDman/SuperFactoryManager --dir build/ci-download
```

## Reproduce the container experiment

See [the container guide](../containers/sfm/README.md) for exact Docker commands,
artifact checks and the Discord/Kubernetes deployment boundaries. The first
graphics run established software OpenGL 4.5 under the restrictions; the final
PR run also verified the complete game with networking disabled.

Implementation progress and observed blockers are recorded in
[the experiment plan](../docs/tasks/ci%20and%20container%20puppet%20experiment%20plan.md).
Only 1.19.2 is in this workflow's acceptance scope. Add version-specific
validation before propagating the workflow to other Minecraft branches.
247 changes: 247 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
name: SFM 1.19.2 verification

on:
push:
branches:
- '1.19.2'
- 'ci/1.19.2-container-puppet'
pull_request:
branches:
- '1.19.2'
workflow_dispatch:

permissions:
contents: read

concurrency:
group: sfm-1.19.2-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

defaults:
run:
shell: bash

jobs:
graphics-probe:
name: Container software graphics and isolation
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- name: Check out the event revision
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Build the graphics probe without Minecraft dependencies
timeout-minutes: 12
run: |
mkdir -p build/graphics-probe
docker build --progress plain --target graphics-probe \
--file containers/sfm/Dockerfile --tag sfm-graphics:local . \
2>&1 | tee build/graphics-probe/image-build.log

- name: Test fixture evidence validation
run: |
python3 -B -m unittest discover -s containers/sfm -p test_verify.py -v
bash containers/sfm/test_smoke.sh
bash containers/sfm/test_junit_diagnostics.sh

- name: Verify software OpenGL inside the restricted container
timeout-minutes: 3
run: |
container=$(docker create --network none --read-only --user 10001:10001 \
--cap-drop ALL --security-opt no-new-privileges:true \
--pids-limit 128 --memory 1g --memory-swap 1g --cpus 2 --init \
--tmpfs /tmp:rw,exec,nosuid,nodev,size=128m,mode=1777 \
--tmpfs /home/sfm:rw,nosuid,nodev,size=32m,uid=10001,gid=10001,mode=700 \
sfm-graphics:local)
cleanup() {
status=$?
trap - EXIT
docker logs "$container" > build/graphics-probe/console.log 2>&1 || true
docker inspect "$container" > build/graphics-probe/docker-inspect.json || true
docker rm -f -v "$container" >/dev/null || true
exit "$status"
}
trap cleanup EXIT
timeout --signal=TERM --kill-after=10s 2m docker start --attach "$container"
test "$(docker inspect --format '{{.State.ExitCode}}' "$container")" = 0

- name: Upload graphics and isolation evidence
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: sfm-graphics-evidence-${{ github.sha }}
path: build/graphics-probe/
if-no-files-found: warn
retention-days: 14

build:
name: Compile, test and package (Linux)
runs-on: ubuntu-24.04
timeout-minutes: 140
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: '1'
RUSTUP_TOOLCHAIN: '1.96.0'
steps:
- name: Check out the event revision
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Select the checked-out revision for the SFM CLI
run: |
# PR checkouts contain GitHub's merge revision. Keep that exact tree;
# the CLI discovers named worktrees and skips a detached HEAD.
git switch --create sfm-ci-checkout
mkdir -p build/ci
echo "SFM_PROPAGATE_CHANGES_HOME=$RUNNER_TEMP/sfm-home" >> "$GITHUB_ENV"
echo "SFM_PROPAGATE_CHANGES_CACHE=$RUNNER_TEMP/sfm-cache" >> "$GITHUB_ENV"
git rev-parse HEAD > build/ci/source-revision.txt
grep -Eq '^minecraft_version[[:space:]]*=[[:space:]]*1\.19\.2[[:space:]]*$' \
platform/minecraft/gradle.properties

- name: Set up Java 17
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
with:
distribution: temurin
java-version: '17'

- name: Install the compiler for the frozen Vox artifact
timeout-minutes: 10
run: |
# Vox's locked manifest embeds this exact JDK vendor/version. The mod
# and game continue to use current Java 17 via explicit --java-home.
bash containers/sfm/install-jbr.sh "$RUNNER_TEMP/vox-build-jdk"
echo "SFM_VOX_BUILD_JDK=$RUNNER_TEMP/vox-build-jdk" >> "$GITHUB_ENV"

- name: Set up the Rust compiler
timeout-minutes: 10
run: |
rustup toolchain install 1.96.0 --profile minimal
rustc --version
cargo --version

- name: Build the CLI from locked sources
timeout-minutes: 25
run: |
cargo build --locked --release \
--manifest-path platform/cli/sfm-propagate-changes/Cargo.toml \
2>&1 | tee build/ci/cargo-build.log
cli="$GITHUB_WORKSPACE/platform/cli/sfm-propagate-changes/target/release/sfm-propagate-changes"
"$cli" --version | tee build/ci/cli-version.txt
sha256sum "$cli" > build/ci/cli-sha256.txt
"$cli" repo-root set "$GITHUB_WORKSPACE"
echo "SFM_CI_CLI=$cli" >> "$GITHUB_ENV"

- name: Test portable dependency paths on Linux
timeout-minutes: 15
run: |
cargo test --locked --release --lib \
--manifest-path platform/cli/sfm-propagate-changes/Cargo.toml \
json_path::tests 2>&1 | tee build/ci/portable-path-tests.log

- name: Compile all Java source sets
timeout-minutes: 35
run: |
JAVA_HOME="$SFM_VOX_BUILD_JDK" \
"$SFM_CI_CLI" --log-filter info --log-file build/ci/compile.ndjson \
run compile --branch sfm-ci-checkout --java-home "$JAVA_HOME" \
--require-portable-artifacts --plan-json build/ci/compile-plan.json \
2>&1 | tee build/ci/compile.log

- name: Run Java unit tests
timeout-minutes: 15
run: |
diagnostics_pid=''
stop_diagnostics() {
local status=$?
trap - EXIT
if [[ -n "$diagnostics_pid" ]]; then
if jobs -pr | grep -Fxq -- "$diagnostics_pid"; then
kill "$diagnostics_pid" 2>/dev/null || true
fi
wait "$diagnostics_pid" 2>/dev/null || true
fi
exit "$status"
}
trap stop_diagnostics EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
bash containers/sfm/junit-diagnostics.sh build/ci/junit-diagnostics \
> build/ci/junit-diagnostics-watchdog.log 2>&1 &
diagnostics_pid=$!
"$SFM_CI_CLI" --log-filter info --log-file build/ci/test.ndjson \
test run --branch sfm-ci-checkout --java-home "$JAVA_HOME" \
--require-portable-artifacts \
2>&1 | tee build/ci/test.log

- name: Package the distributable mod
timeout-minutes: 15
run: |
"$SFM_CI_CLI" --log-filter info --log-file build/ci/package.ndjson \
jar build --branch sfm-ci-checkout --java-home "$JAVA_HOME" \
--require-portable-artifacts --plan-json build/ci/package-plan.json \
2>&1 | tee build/ci/package.log

- name: Verify frozen dependency declarations
if: always()
run: |
git diff --exit-code -- '**/Cargo.toml' '**/Cargo.lock' \
platform/minecraft/sfm-toolchain.lock.json

- name: Upload the verified mod
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: sfm-1.19.2-${{ github.sha }}
path: platform/minecraft/build/libs/*-rust.jar
if-no-files-found: error
retention-days: 14

- name: Upload build and test diagnostics
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: sfm-build-diagnostics-${{ github.sha }}
path: |
build/ci/
platform/minecraft/build/sfm-toolchain/**/*.log
platform/minecraft/build/sfm-toolchain/**/*.args
platform/minecraft/build/sfm-toolchain/run/**/*.json
if-no-files-found: warn
retention-days: 14

container-smoke:
name: Isolated Minecraft client (Mesa and Xvfb)
runs-on: ubuntu-24.04
timeout-minutes: 120
steps:
- name: Check out the event revision
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Build the prepared game image
timeout-minutes: 70
run: |
mkdir -p build/container-ci
docker build --progress plain \
--build-arg "SFM_SOURCE_REVISION=$(git rev-parse HEAD)" \
--file containers/sfm/Dockerfile --tag sfm-ci:local . \
2>&1 | tee build/container-ci/image-build.log

- name: Capture screenshots in an offline restricted container
timeout-minutes: 40
run: bash containers/sfm/smoke.sh sfm-ci:local build/container-smoke

- name: Upload container evidence
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: sfm-container-evidence-${{ github.sha }}
path: |
build/container-ci/
build/container-smoke/
if-no-files-found: warn
retention-days: 14
Loading
Loading