Chapter 31Lesson 04~390 minutes

Production Capstone: Build, Test, Secure, Publish, and Optimize a Multi-Module JVM Platform: Failure Injection, Troubleshooting, and Recovery Drill

Break the capstone deliberately and recover from preserved evidence. Drill toolchain mismatch, dependency and supply-chain drift, false-green test wiring, stale state, publication identity failure, and credential-boundary mistakes without destroying useful diagnostics or normal user caches.

Failure injectionDiagnosticsRecoveryIncident evidenceLeast destructive fix

Learning objectives

  • Run controlled failure drills with predictions and preserved evidence.
  • Use the same diagnostic sequence across toolchain, dependency, test, cache, publication, and credential domains.
  • Treat checksum/repository-origin mismatches as incidents instead of bypassing verification.
  • Distinguish rebuild, cache restoration, and already-built artifact restoration by trust guarantee.
  • Record root cause, corrective action, verification evidence, and preventive control for every drill.

Drill safety contract. Work only in a disposable copy. Save every file before editing it, keep fake credentials obviously fake, preserve failure logs before repair, and delete only project-local cache/output directories. Never disable verification globally or clear normal user caches to make a symptom disappear.

1. One diagnostic sequence for every failure

Use the same order so investigation does not become guesswork:

1. Preserve concise failure output and the exact command.
2. Confirm Wrapper / Maven-or-Gradle / JDK identity.
3. Inspect settings, build scripts, toolchain and dependency declarations.
4. Inspect dependency graph and task/lifecycle graph.
5. Inspect repository, verification metadata, caches and filesystem outputs.
6. Inspect test/compiler/plugin/publication evidence.
7. Apply the least destructive correction.
8. Re-run the controlled failing path, then the required clean verification.
9. Record root cause, detection signal, corrective action, verification and prevention.

2. Drill A — wrong target/toolchain contract

Prediction: changing Java release to an impossible value should fail compilation before publication. Preserve the valid file first:

cp core/build.gradle.kts evidence/core-build.before-toolchain-drill.kts
python - <<'PY'
from pathlib import Path
p=Path('core/build.gradle.kts')
s=p.read_text()
p.write_text(s.replace('options.release.set(17)','options.release.set(99)'))
PY
set +e
./gradlew :core:clean :core:compileJava 2>&1 | tee evidence/drill-a-toolchain.log
status=${PIPESTATUS[0]}
set -e
test "$status" -ne 0
cp evidence/core-build.before-toolchain-drill.kts core/build.gradle.kts
./gradlew :core:clean :core:compileJava
javap -verbose core/build/classes/java/main/dev/academy/capstone/MessageNormalizer.class \
  | grep 'major version' | tee evidence/drill-a-repaired-major.txt

Root cause: declared compiler target is outside the selected toolchain’s supported release set. Preventive control: version/toolchain policy plus bytecode-major verification. Do not “fix” this by changing the runtime contract without product approval.

3. Drill B — dependency checksum mismatch / supply-chain incident

Prediction: if the reviewed Commons Lang checksum is altered, dependency verification should reject the artifact even though its coordinate/version still match. Preserve the metadata first:

cp gradle/verification-metadata.xml evidence/verification.before-drill.xml
python - <<'PY'
from pathlib import Path
p=Path('gradle/verification-metadata.xml')
s=p.read_text()
needle='org.apache.commons'
pos=s.find(needle)
if pos < 0:
    raise SystemExit('Commons group not found; inspect metadata before drill')
# Alter only the first sha256 value after the known group marker.
sha_pos=s.find('value="', pos)
if sha_pos < 0:
    raise SystemExit('checksum value not found')
start=sha_pos+7
old=s[start:start+64]
s=s[:start]+('0' if old[0] != '0' else '1')+old[1:]+s[start+64:]
p.write_text(s)
PY
set +e
./gradlew --refresh-dependencies :core:compileJava 2>&1 | tee evidence/drill-b-verification.log
status=${PIPESTATUS[0]}
set -e
test "$status" -ne 0

# Investigate; do NOT regenerate metadata to force green.
diff -u evidence/verification.before-drill.xml gradle/verification-metadata.xml || true
cp evidence/verification.before-drill.xml gradle/verification-metadata.xml
./gradlew --refresh-dependencies :core:compileJava

Incident response: preserve requested coordinate, repository configuration, verification failure, cached artifact hash, and upstream change evidence. A checksum mismatch can mean a local drill, a changed artifact, repository corruption, or tampering. The correction is restoring independently reviewed policy—not bypassing verification.

4. Drill C — false-green test lifecycle

Prediction: disconnecting integrationTest from check lets check finish without the intended integration suite. That is a build-model failure even if exit code is zero:

cp app/build.gradle.kts evidence/app-build.before-test-drill.kts
python - <<'PY'
from pathlib import Path
p=Path('app/build.gradle.kts')
s=p.read_text()
old='tasks.check {\n    dependsOn(testing.suites.named("integrationTest"))\n}'
if old not in s:
    raise SystemExit('expected check wiring not found')
p.write_text(s.replace(old,'// DRILL: integrationTest intentionally disconnected from check'))
PY
rm -rf app/build/test-results/integrationTest
./gradlew :app:clean :app:check --info | tee evidence/drill-c-false-green.log
if test -d app/build/test-results/integrationTest; then
  echo 'Unexpected: integration report exists; inspect task graph' >&2
  exit 1
fi
./gradlew :app:check --dry-run | tee evidence/drill-c-dry-run.txt
cp evidence/app-build.before-test-drill.kts app/build.gradle.kts
./gradlew :app:clean :app:check
test -d app/build/test-results/integrationTest

Root cause: lifecycle/task wiring omitted required verification. Preventive control: CI must assert report/suite presence or task-graph evidence, not only check exit status.

5. Drill D — distinguish stale workspace/cache state from source truth

Prediction: warm derived state can change task outcomes (UP-TO-DATE/FROM-CACHE) without changing accepted artifact semantics. Compare warm and isolated no-cache executions; do not clear global caches:

./gradlew :core:jar --build-cache --info | tee evidence/drill-d-warm.log
mkdir -p .drill-gradle-fresh
GRADLE_USER_HOME="$PWD/.drill-gradle-fresh" \
  ./gradlew --no-build-cache :core:clean :core:jar --info \
  | tee evidence/drill-d-fresh.log
sha256sum core/build/libs/core-1.0.0.jar | tee evidence/drill-d-core.sha256

If warm and clean behavior disagree functionally, inspect declared task inputs/outputs and cache provenance. Deleting ~/.gradle would erase evidence and is not a root-cause analysis.

6. Drill E — staged artifact checksum mismatch blocks promotion

Prediction: mutating the staged JAR after acceptance must fail promotion against the preserved accepted checksum:

jar="staging-repo/dev/academy/capstone/core/1.0.0/core-1.0.0.jar"
cp "$jar" evidence/core-1.0.0.accepted.jar
accepted="$(sha256sum evidence/core-1.0.0.accepted.jar | awk '{print $1}')"
printf 'harmless-drill-byte' >> "$jar"
actual="$(sha256sum "$jar" | awk '{print $1}')"
printf 'accepted=%s\nactual=%s\n' "$accepted" "$actual" | tee evidence/drill-e-mismatch.txt
if test "$accepted" = "$actual"; then
  echo 'drill failed: checksum unexpectedly equal' >&2
  exit 1
fi
# Promotion is blocked. Restore the preserved accepted artifact, not a rebuild.
cp evidence/core-1.0.0.accepted.jar "$jar"
test "$(sha256sum "$jar" | awk '{print $1}')" = "$accepted"

This recovery intentionally uses the already-built accepted artifact. Rebuilding from source is a different action: it can prove reproducibility, but it does not preserve the identity of an already-approved candidate unless the resulting checksum is independently matched.

7. Drill F — fake credential containment

Prediction: a fake credential committed into project configuration should be detected by the source/evidence scan and removed. Use only an unmistakably non-secret value:

cat > evidence/fake-credential-drill.properties <<'EOF'
repoUser=drill-user
repoPassword=FAKE_DO_NOT_USE_12345
EOF

grep -RIn 'FAKE_DO_NOT_USE_12345' . 
rm evidence/fake-credential-drill.properties
if grep -RIn 'FAKE_DO_NOT_USE_12345' .; then
  echo 'fake credential still present' >&2
  exit 1
fi

In a real incident, removal from the working tree is insufficient: revoke/rotate the credential, purge it from inappropriate history/log/artifact stores according to policy, determine exposure scope, and review authorization. This drill only demonstrates containment mechanics without creating a real secret.

8. Rebuild, cache restore, and artifact restore are three different recovery actions

Recovery action Starting trust What it gives you What it cannot claim alone
Rebuild from source Trusted source + tools + dependencies Fresh artifact and test evidence; reproducibility comparison Same identity as a previously approved artifact unless checksum matches.
Restore build/cache entry Trusted cache key + trusted writer + complete task inputs Faster reproduction of derived outputs Independent source rebuild or immutable promotion evidence.
Restore already-built accepted artifact Previously recorded artifact checksum/provenance Exact candidate identity for promotion/recovery Fresh compilation/test execution; relies on prior evidence bundle.

9. Incident record template for every drill

incident:
  id: DRILL-X
  prediction: "What should fail and why?"
  exact_command: "..."
  root_cause: "..."
  detection_signal: "log/report/checksum/task graph"
  preserved_evidence:
    - "..."
  corrective_action: "least destructive change"
  verification:
    - "controlled retry"
    - "clean required gate"
  time_to_recovery_estimate: "record observed/estimated duration"
  preventive_control: "policy/test/automation/ownership improvement"
  owner: "role, not an individual secret"

The time-to-recovery field is measured or estimated by the learner; this static lesson does not invent a timing number for an environment it has not executed.

10. Recovery acceptance checklist

Question Must be true before closing drill
Evidence preserved? Original failure log/artifact/model diff still exists.
Identity confirmed? Wrapper/build tool/JDK match intended baseline.
Root cause specific? Not merely “cache problem” or “Gradle problem”; exact model/state cause named.
Fix minimal? No unrelated verification disabled, no global cache destruction.
Controlled retry passes? The exact failing path now behaves as predicted.
Clean gate passes? Relevant clean check/clean consumer/reproducibility gate rerun.
Preventive control named? A policy, automated assertion, ownership, or test reduces recurrence.

Knowledge check

Why is regenerating verification metadata the wrong first response to a checksum mismatch?

Can check exit zero while the build is false green?

Why restore the accepted JAR in Drill E instead of rebuilding immediately?

Why avoid deleting normal ~/.gradle during diagnostics?

What extra action is mandatory for a real leaked credential?

What closes a failure drill?

Official references and version notes

Version-sensitive statements were rechecked against primary documentation on 2026-08-24. Mandatory work remains local/free. Hosted CI, commercial build analytics, production repository managers, remote caches, and real signing/credential systems are optional integration boundaries only.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.