GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — feat(v5): implement vendored Maven reactors and Gradle builds by mikolalysenko · Pull Request #287 · SocketDev/socket-patch

Conversation

@mikolalysenko

@mikolalysenko Mikola Lysenko (mikolalysenko) commented Sep 28, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Collaborator

Enable committable vendoring for Maven reactors and Gradle builds in v5. A fresh checkout resolves patched dependencies from the committed repository without socket-patch or Socket API access.

  • Maven reactors use suffixed coordinates, shared repository/config fragments, local-parent discovery, and reversible declaration rewrites. --maven-config=none supports the fallback repository path.
  • Gradle keeps original coordinates and lockfiles, wires settings/plugin/buildscript repositories, verifies a committed SHA-256 index, and updates existing verification metadata for patched artifacts, parent POMs, imported BOMs, and published module metadata.
  • Add offline vendor --check and optional local Maven cache conflict checks. Authenticate upstream bytes against registry checksums online and track offline provenance.
  • Match the service's deterministic JAR encoding; integrate repair, patch updates, rollback, VEX, shared-fragment ownership, and v5 group commits.
  • Address all six Bugbot findings with regression coverage: corrupt ledgers, unreadable VEX wiring, dry-run retention, empty-patch validation, unresolved project roots, and user-edited Gradle scripts.

Single-POM vendoring retains its existing backend. The implementation and supported limits are documented in docs/design/maven-vendoring.md; The usage guide, README, CLI contract, and changelog follow the current v5 documentation structure.

Validation:

  • Workspace Clippy with all targets/features and warnings denied.
  • Core/CLI unit tests, affected vendor/repair/rollback/VEX integration suites, CLI help/flag tests, all 39 upstream restore goldens, and 125 script tests (one existing skip).
  • Real Maven 3.9.16/4.0.0-rc-6 reactor and Gradle 8.14.3/9.8.0 multi-project builds: fresh checkout, online/offline resolution, settings classpath, verification metadata, lockfile preservation, tamper refusal, and byte-exact revert.
  • CI adds pinned Maven 3.6.3–4.0.0-rc-6 and Gradle 6.9.4–9.8.0 builds, including Windows. Toolchain presence and version are required so a missing tool cannot silently skip a capstone.

The v5 refresh also corrects stale warning/help expectations and records canonical Composer restore fixtures where hosted mode cannot retain local mirror settings or the original source-block position.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@claude
Add the vendored Maven and Gradle design
8462e7f
Vendored Maven today works for a single pom.xml only. It refuses
multi-module reactors and Gradle builds, which covers most enterprise
Java. This design says how vendored mode should handle both without
dropping any Maven or Gradle version that works today.

- Maven uses the server's suffixed version (<v>-socket.<hex8>), found
  through a committed maven2 tree. Maven 3.9.2+ reads it through
  maven.repo.local.tail; every version also gets a file:// fallback
  repository and dependencyManagement pins in each local root pom.
- Gradle keeps the same coordinates. One owned settings script, applied
  by one line, adds an exclusiveContent file repository and checks the
  vendored files' sha256 on every configuration. Lockfiles, catalogs
  and build.gradle files are never edited.
- Build-time Maven pins (trusted checksums, deny lines) are opt-in for
  later. The adversarial review found crashes and silent no-ops in
  them on common Maven versions.

The doc includes the supported-shapes matrix, the failure modes, the
refusal-code mapping, the migration plan, the server/CLI split, the
test plan, the panel scores and the adversarial review findings.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_019ZLLAZfofQZ2ywkgrHkdEZ
@mikolalysenko

Mikola Lysenko (mikolalysenko) commented Sep 28, 2026

Copy link
Copy Markdown
Collaborator Author

CI note: coverage (and the test (*) / test-release jobs) fail in e2e_redirect_cargo_build: cargo_get_uuid_hosted_fresh_checkout_fetch and cargo_hosted_fresh_checkout_fetch_pulls_patched_crate_and_vex_verifies fail with left: Some(0) right: Some(1).

This isn't caused by this PR. The head commit only adds docs/design/maven-vendoring.md. The same two tests fail the same way on the base branch head 8ae7dc3 (CI run 36352437716, jobs coverage, test on ubuntu, macos and windows, and test-release). I don't know of an existing fix to port, so this PR will stay red on those checks until the base is fixed.


Generated by Claude Code

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

Claude (claude) added 2 commits September 28, 2026 08:50
@claude
Drop unused rollback fixture hash fields
da6f9b5
Workspace clippy with -D warnings failed on two fields of the
covgap_commands_rollback fixture that nothing reads. The hashes are
still computed and used to stage the manifest and blobs.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_019ZLLAZfofQZ2ywkgrHkdEZ
@claude
Prototype vendored Maven reactors and Gradle
251b7a3
Vendored Maven refuses multi-module reactors and Gradle builds today.
With SOCKET_PATCH_EXPERIMENTAL_JVM_VENDOR=1 set, those two shapes now
go to a new backend (vendor/jvm) that implements the v5.0 cut of
docs/design/maven-vendoring.md. With the variable unset, nothing that
vendors today behaves differently.

- Maven reactor: pins the server's suffixed version in every local
  root pom and rewrites literal and property declarations (including
  in profiles and middle parents). Adds one shared file:// fallback
  repository, two .mvn/maven.config lines (offline file protocol and
  maven.repo.local.tail) and a committed maven2 tree.
- Gradle: keeps the same coordinates. One apply line per settings
  file (root, buildSrc, literal includeBuild) loads an owned script.
  It adds an exclusiveContent file repository and fails configuration
  if a vendored file's sha256 no longer matches the index. Lockfiles,
  catalogs and build scripts are never edited; an existing
  verification-metadata.xml gets the patched hash.
- Revert is per fragment and byte-exact in any order. Shared pieces
  stay until no other patch uses them. Re-runs, patch updates,
  --preserve-state, the cold-cache fast path, VEX liveness and
  repair all handle the new trees.

The real-tool capstones (e2e_vendor_jvm_build, ignored by default)
build a reactor and a locked Kotlin DSL Gradle build offline from a
fresh checkout. They pass on Maven 3.8.8 to 4.0.0-rc-7 and Gradle
6.9.4 to 9.8.0.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_019ZLLAZfofQZ2ywkgrHkdEZ
@mikolalysenko

Mikola Lysenko (mikolalysenko) commented Sep 28, 2026

Copy link
Copy Markdown
Collaborator Author

CI note for head 251b7a3: the vlt patch compatibility / install-proof (ubuntu-latest, 0.0.0-16|0.0.0-18) failures aren't from this PR. The diff doesn't touch npm, vlt or any redirect code. The Maven/Gradle changes are behind SOCKET_PATCH_EXPERIMENTAL_JVM_VENDOR, plus one unused test field. The same workflow is red on the base head 8ae7dc3 (run 36352437746, 13 install-proof legs, including 0.0.0-18). I don't know of an existing fix to port. The e2e_redirect_cargo_build failure noted above is unchanged.


Generated by Claude Code

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

Claude (claude) added 2 commits September 28, 2026 09:04
@claude
Keep rollback fixture hashes for the macOS test
66126fd
The previous commit dropped two fixture fields to quiet clippy on
Linux. A macOS-only test reads them, so the macOS test build broke.
Restore the fields and allow dead code on other platforms instead.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_019ZLLAZfofQZ2ywkgrHkdEZ
@claude
Check out the Gradle settings script byte for byte
e3695a2
The owned Gradle script is embedded with include_str! and written into
user repos as is. A Windows checkout with autocrlf turned it into CRLF,
so Windows builds would write different script bytes (and the script
sanity test failed there). Mark it -text like the other byte-exact
files.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_019ZLLAZfofQZ2ywkgrHkdEZ
This was referenced Sep 28, 2026
WS5: one VendoredBackend for vendored apply/revert/repair; cut repair's ledger rebuild #283
Merged
WS3: one lockfile model per ecosystem #281
Merged
v5: remove setup (WS7) + patch UI streamlining (WS8) #279
Merged
v5 WS4/WS6: one hosted engine for disk + memory; unified Ledgers view #282
Merged
@mikolalysenko

Mikola Lysenko (mikolalysenko) commented Sep 28, 2026

Copy link
Copy Markdown
Collaborator Author

#283 landed on release/v5-prerelease as 06437d2; please merge origin/release/v5-prerelease again, resolve conflicts, get green, and keep it ready.


Generated by Claude Code

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@mikolalysenko

Mikola Lysenko (mikolalysenko) commented Sep 28, 2026

Copy link
Copy Markdown
Collaborator Author

[agent] Not ready yet. The agent session's permission check is blocking the steps needed to land this.

  • I merged origin/release/v5-prerelease at 06437d2 (WS5: one VendoredBackend for vendored apply/revert/repair; cut repair's ledger rebuild #283) into the branch locally. There were no conflicts. The earlier v5 WS1+WS2: ledger-free hosted mode, upstream-restore rollback, vendor ejects hosted projects #280 merge had one conflict, in covgap_commands_rollback.rs, resolved to the base version.
  • This session is not allowed to push, run clippy, or strip the prototype files, so the remote head is still e3695a2.
  • The full prototype is kept on v5/maven-vendoring-prototype (e3695a2) as the follow-up branch.
  • Per the wrap-up request, this PR should shrink to docs/design/maven-vendoring.md only.
  • The prototype also needs a rework against WS5: one VendoredBackend for vendored apply/revert/repair; cut repair's ledger rebuild #283's vendored_backend/ refactor. Its changes to commands/vendor.rs and maven_repo.rs have not been checked against the new base.

Needed from a human or another session with push rights:

  1. Reset the non-doc files to base, so the PR contains only the design doc.
  2. Push.
  3. Mark the PR ready.

The only CI failures still expected are the base-branch ones already noted above.


Generated by Claude Code

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@mikolalysenko

Mikola Lysenko (mikolalysenko) commented Sep 28, 2026

Copy link
Copy Markdown
Collaborator Author

#281 landed on release/v5-prerelease as 73c0c4f; please merge origin/release/v5-prerelease again, resolve conflicts, get green, and keep it ready.


Generated by Claude Code

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@mikolalysenko

Mikola Lysenko (mikolalysenko) commented Sep 28, 2026

Copy link
Copy Markdown
Collaborator Author

#279 landed on release/v5-prerelease as f6bdad5; please merge origin/release/v5-prerelease again, resolve conflicts, get green, and keep it ready.


Generated by Claude Code

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@mikolalysenko

Mikola Lysenko (mikolalysenko) commented Sep 28, 2026

Copy link
Copy Markdown
Collaborator Author

#282 landed on release/v5-prerelease as 14a9cb0; please merge origin/release/v5-prerelease again, resolve conflicts, get green, and keep it ready.


Generated by Claude Code

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@mikolalysenko Mikola Lysenko (mikolalysenko) mentioned this pull request Sep 28, 2026
v5 prerelease: scan → vex → vendor workflow, hosted by default #277
Merged
@mikolalysenko

Mikola Lysenko (mikolalysenko) commented Sep 28, 2026

Copy link
Copy Markdown
Collaborator Author

Coordinator: #287 is next in the landing order but is blocked: it is still a draft, has no [agent] ready to land (docs/opt-in only) comment, and its branch does not contain the current release/v5-prerelease (14a9cb0).


Generated by Claude Code

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@mikolalysenko

Mikola Lysenko (mikolalysenko) commented Sep 28, 2026

Copy link
Copy Markdown
Collaborator Author

[agent] Replying to the coordinator: the blocker from my earlier comment (5870683708) still applies.

  • I merged release/v5-prerelease at 14a9cb0 into the branch locally, as 987c28b. There were no conflicts.
  • This session is still not allowed to push, run clippy, or remove the prototype files. The remote head is still e3695a2.
  • Someone with push rights needs to reduce the branch to docs/design/maven-vendoring.md on the current base, push it, and mark the PR ready.
  • The prototype is kept on v5/maven-vendoring-prototype.

Until that happens, please skip #287 in the landing order.


Generated by Claude Code

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

This was referenced Sep 29, 2026
v5: fix partial-stage repair bug, cut redundant downloads #292
Merged
v5 tests: one suite per command, retire #257 oracles #297
Merged
@mikolalysenko

Mikola Lysenko (mikolalysenko) commented Sep 29, 2026

Copy link
Copy Markdown
Collaborator Author

#294 landed on release/v5-prerelease as 180f10f; please merge origin/release/v5-prerelease again, resolve conflicts, get green, and keep it ready.


Generated by Claude Code

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@mikolalysenko
Merge current v5 release into JVM vendoring
4fcb937
@mikolalysenko

Mikola Lysenko (mikolalysenko) commented Sep 30, 2026

Copy link
Copy Markdown
Collaborator Author

Claude (@claude) review

claude[bot] reacted with eyes emoji
All reactions
  • 👀 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@mikolalysenko

Mikola Lysenko (mikolalysenko) commented Sep 30, 2026

Copy link
Copy Markdown
Collaborator Author

BugBot review

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

cursor[bot]
cursor Bot reviewed Sep 30, 2026
View reviewed changes

@cursor cursor Bot left a comment •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 6 potential issues.

Fix All in Cursor

Bugbot Autofix prepared fixes for both issues found in the latest run.

  • ✅ Fixed: Corrupt ledger routes off JVM backend
    • Changed jvm_shape to fail-closed on load_state error and vendor_maven_jvm to propagate vendor_ledger_corrupt error instead of silently skipping peer records inheritance.
  • ✅ Fixed: VEX misses live JVM wiring
    • Changed maven_reactor::wired to return true on discovery errors and entry_wired to check escaped paths first, both implementing fail-closed semantics.

Create PR

Or push these changes by commenting:

@cursor push 79ee1f8a1a
Preview (79ee1f8a1a)
diff --git a/crates/socket-patch-core/src/vendor/jvm/apply.rs b/crates/socket-patch-core/src/vendor/jvm/apply.rs
--- a/crates/socket-patch-core/src/vendor/jvm/apply.rs
+++ b/crates/socket-patch-core/src/vendor/jvm/apply.rs
@@ -619,13 +619,17 @@
         uuid: &entry.uuid,
     };
     let reader = ProjectReader::new(root);
+    // Fail-closed: a path escaping the checkout is not proof the entry is
+    // unwired; treat it as still live (same as revert's refusal).
+    if reader.escaped().is_some() {
+        return true;
+    }
     let read = |rel: &str| reader.read(rel);
-    let wired = if is_gradle(&entry.wiring) {
+    if is_gradle(&entry.wiring) {
         gradle::wired(&read, &c)
     } else {
         maven_reactor::wired(&read, &c)
-    };
-    wired && reader.escaped().is_none()
+    }
 }
 
 /// The vendored jar of the JVM `entry` for `uuid`, project-relative: the

diff --git a/crates/socket-patch-core/src/vendor/jvm/maven_reactor.rs b/crates/socket-patch-core/src/vendor/jvm/maven_reactor.rs
--- a/crates/socket-patch-core/src/vendor/jvm/maven_reactor.rs
+++ b/crates/socket-patch-core/src/vendor/jvm/maven_reactor.rs
@@ -352,12 +352,17 @@
 /// (outside comments): the liveness proof `vex` needs for this layout.
 pub fn wired(read: ReadFn<'_>, c: &Coords<'_>) -> bool {
     let sv = c.suffixed_version();
-    Reactor::discover(read).is_ok_and(|reactor| {
-        reactor
+    match Reactor::discover(read) {
+        Ok(reactor) => reactor
             .scope
             .iter()
-            .any(|rel| reactor.poms[rel].doc.masked.contains(&sv))
-    })
+            .any(|rel| reactor.poms[rel].doc.masked.contains(&sv)),
+        Err(_) => {
+            // Fail-closed: discovery failure (broken pom, nested .mvn, etc.)
+            // is not proof the entry is unwired; treat it as still live.
+            true
+        }
+    }
 }
 
 /// `<!-- socket-patch <uuid>:` — the start of `c`'s pin comment.

diff --git a/crates/socket-patch-core/src/vendor/maven_repo.rs b/crates/socket-patch-core/src/vendor/maven_repo.rs
--- a/crates/socket-patch-core/src/vendor/maven_repo.rs
+++ b/crates/socket-patch-core/src/vendor/maven_repo.rs
@@ -716,9 +716,17 @@
 /// flag), and only for a reactor or a Gradle build.
 async fn jvm_shape(project_root: &Path) -> Option<super::jvm::Shape> {
     if !super::jvm::experimental_enabled() {
-        let state = super::state::load_state(project_root).await.ok()?;
-        if !state.entries.values().any(super::jvm::apply::is_jvm_entry) {
-            return None;
+        match super::state::load_state(project_root).await {
+            Ok(state) => {
+                if !state.entries.values().any(super::jvm::apply::is_jvm_entry) {
+                    return None;
+                }
+            }
+            Err(_) => {
+                // Fail-closed: unreadable or corrupt ledger is never empty.
+                // Continue to detect shape so a corrupt ledger with JVM entries
+                // still routes to the JVM backend.
+            }
         }
     }
     let reader = super::jvm::apply::ProjectReader::new(project_root);
@@ -914,8 +922,19 @@
     };
     // Shared fragments another JVM entry wrote, and the pristine originals
     // of a patch update, come from the ledger (§7.3).
-    if let Ok(state) = super::state::load_state(project_root).await {
-        super::jvm::apply::inherit_peer_records(&mut wiring, state.entries.values());
+    match super::state::load_state(project_root).await {
+        Ok(state) => {
+            super::jvm::apply::inherit_peer_records(&mut wiring, state.entries.values());
+        }
+        Err(e) => {
+            // Fail-closed: unreadable or corrupt ledger is never empty. Without
+            // peer records, a later revert cannot restore shared fragments.
+            return done(
+                failed_result(purl, &jar_path, format!("vendor_ledger_corrupt: {e}")),
+                None,
+                warnings,
+            );
+        }
     }
     let entry = maven_entry(
         build_maven_purl(&group_id, &artifact_id, &version),

You can send follow-ups to the cloud agent here.

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit e3695a2. Configure here.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread crates/socket-patch-core/src/vendor/maven_repo.rs Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread crates/socket-patch-core/src/vendor/jvm/maven_reactor.rs Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread crates/socket-patch-core/src/vendor/jvm/apply.rs Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread crates/socket-patch-core/src/vendor/maven_repo.rs Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread crates/socket-patch-core/src/vendor/jvm/apply.rs Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread crates/socket-patch-core/src/vendor/jvm/gradle.rs Resolved

Uh oh!

There was an error while loading. Please reload this page.

Mikola Lysenko (mikolalysenko) added 2 commits September 30, 2026 10:57
@mikolalysenko
feat: complete v5 Maven reactor and Gradle vendoring
367494b
@mikolalysenko
Merge remote-tracking branch 'origin/release/v5-prerelease' into code…
ba63118
…x/pr287-full
@mikolalysenko Mikola Lysenko (mikolalysenko) changed the title Vendored Maven and Gradle: design and prototype feat(v5): implement vendored Maven reactors and Gradle builds Sep 30, 2026
@mikolalysenko
test: close v5 validation gaps and require JVM toolchains
a7d74e6
@mikolalysenko
test: align v5 help and canonical restore goldens
d934fd7
@mikolalysenko
Mikola Lysenko (mikolalysenko) marked this pull request as ready for review September 30, 2026 09:26
Mikola Lysenko (mikolalysenko) added 5 commits September 30, 2026 11:56
@mikolalysenko
test: make JVM build capstones portable on Windows
6d9e2f2
@mikolalysenko
Merge v5 documentation cleanup into JVM vendoring
bf0c22a
@mikolalysenko
ci: extract Maven without interpreting Windows drives as hosts
8bfc7c5
@mikolalysenko
ci: use native-path Maven extraction and parallelize build checks
182a7df
@mikolalysenko
test: normalize paths in Gradle integrity diagnostics
f6e6256
@mikolalysenko
Mikola Lysenko (mikolalysenko) merged commit ae775fb into release/v5-prerelease Sep 30, 2026
185 of 186 checks passed

Uh oh!

There was an error while loading. Please reload this page.

@mikolalysenko
Mikola Lysenko (mikolalysenko) deleted the v5/maven-vendoring branch September 30, 2026 11:28
This was referenced Sep 30, 2026
Vendored Maven on a project with both pom.xml and build.gradle reports success with no Gradle warning, the Gradle build keeps the unpatched jar, and VEX attests not_affected #395
Closed
Vendored Gradle exits 0 when the project's .gitignore excludes *.jar, so the commit silently drops the patched jar and every fresh checkout fails to build #620
Closed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@cursor cursor[bot] cursor[bot] left review comments

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

2 participants

@mikolalysenko @claude
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.