''', 'html.parser')
+
+ if soup.head is not None:
+ soup.head.append(banner_style)
+ if soup.body is not None:
+ soup.body.insert(0, banner_div)
+
+ index_path.write_text(str(soup), encoding='utf-8')
+ self.log("Added archived banner", "SUCCESS")
+
+ def clean_macos_artifacts(self):
+ """Remove macOS artifacts from the archive"""
+ self.log("Cleaning macOS artifacts...")
+
+ # Remove __MACOSX directories
+ for macosx_dir in self.output_dir.rglob("__MACOSX"):
+ shutil.rmtree(macosx_dir)
+ self.log(f" Removed {macosx_dir}", "SUCCESS")
+
+ # Remove .DS_Store files
+ for ds_store in self.output_dir.rglob(".DS_Store"):
+ ds_store.unlink()
+
+ # Remove ._ files
+ for dot_file in self.output_dir.rglob("._*"):
+ dot_file.unlink()
+
+ self.log("Cleaned macOS artifacts", "SUCCESS")
+
+ def create_zip_archive(self):
+ """Create cockroachdb-docs.zip excluding macOS artifacts"""
+ self.log("Creating zip archive (excluding macOS artifacts)...")
+ import subprocess
+
+ zip_path = self.output_dir.parent / "cockroachdb-docs.zip"
+ if zip_path.exists():
+ zip_path.unlink()
+
+ result = subprocess.run(
+ [
+ "zip", "-r", "-q", str(zip_path), self.output_dir.name,
+ "-x", "**/__MACOSX/*", "-x", "**/.DS_Store", "-x", "**/._*",
+ ],
+ cwd=str(self.output_dir.parent),
+ capture_output=True, text=True,
+ )
+ if result.returncode != 0:
+ self.log(f"zip failed: {result.stderr}", "ERROR")
+ return
+
+ zip_size = zip_path.stat().st_size / (1024 * 1024)
+ self.log(f"Created {zip_path.name} ({zip_size:.1f} MB)", "SUCCESS")
+
+ # Verify no macOS artifacts leaked in
+ check = subprocess.run(
+ ["unzip", "-l", str(zip_path)],
+ capture_output=True, text=True,
+ )
+ macos_count = check.stdout.count("__MACOSX")
+ if macos_count > 0:
+ self.log(f"WARNING: {macos_count} __MACOSX entries found in zip!", "WARNING")
+ else:
+ self.log("Verified: 0 __MACOSX entries in zip", "SUCCESS")
+
+ def build(self, create_zip=False):
+ """Main build process"""
+ print("\n" + "=" * 60)
+ print("COCKROACHDB FULL WEBSITE ARCHIVE CREATOR")
+ print("=" * 60)
+
+ self.log(f"Source: {self.site_dir}")
+ self.log(f"Output: {self.output_dir}")
+ self.log(f"Stable version: {self.stable_version}")
+ self.log(f"Versions: {', '.join(self.versions)}")
+
+ if not self.site_dir.exists():
+ self.log(f"Source directory not found: {self.site_dir}", "ERROR")
+ self.log("Run 'jekyll build' first to generate _site", "ERROR")
+ return False
+
+ # Build steps
+ self.clean_output_dir()
+ self.copy_asset_dirs()
+ self.copy_version_dirs()
+ self.copy_content_dirs()
+ self.copy_top_level_files()
+ self.ensure_nav_assets()
+ self.download_google_fonts()
+ self.process_all_html_files()
+ self.add_archived_banner()
+ self.clean_macos_artifacts()
+
+ if create_zip:
+ self.create_zip_archive()
+
+ # Summary
+ print("\n" + "=" * 60)
+ self.log("ARCHIVE CREATED SUCCESSFULLY!", "SUCCESS")
+ self.log(f"Output: {self.output_dir}")
+ self.log(f"Files processed: {self.processed_files}")
+ self.log(f"Errors: {self.error_count}")
+ print("=" * 60)
+ print(f"\nTo test: open {self.output_dir}/index.html in your browser")
+
+ return True
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ description="Create full offline archive of CockroachDB docs for IBM Escrow delivery.",
+ epilog="Example: python3 create_full_archive.py --add-version v26.2 --stable v26.2 --zip",
+ )
+ parser.add_argument("--site-dir", help="Path to _site/docs directory (default: src/current/_site/docs)")
+ parser.add_argument("--output-dir", help="Output directory name (default: complete_test_archive)")
+ parser.add_argument(
+ "--add-version", action="append", default=[], metavar="VERSION",
+ help="Add a version to the archive (e.g. --add-version v26.2). Can be repeated.",
+ )
+ parser.add_argument(
+ "--stable", dest="stable_version", default=None,
+ help="Set the stable version (default: highest version in the list)",
+ )
+ parser.add_argument("--zip", action="store_true", help="Create cockroachdb-docs.zip for delivery")
+ args = parser.parse_args()
+
+ # Build the version list: start from defaults, add any new versions
+ versions = list(ALL_VERSIONS)
+ for v in args.add_version:
+ if not v.startswith("v"):
+ v = f"v{v}"
+ if v not in versions:
+ versions.append(v)
+ versions.sort(key=lambda x: [int(n) for n in x.lstrip("v").split(".")])
+
+ # Determine stable version
+ stable = args.stable_version or versions[-1]
+ if not stable.startswith("v"):
+ stable = f"v{stable}"
+
+ creator = FullArchiveCreator(
+ site_dir=args.site_dir,
+ output_dir=args.output_dir,
+ stable_version=stable,
+ versions=versions,
+ )
+
+ success = creator.build(create_zip=args.zip)
+ sys.exit(0 if success else 1)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/src/current/operating.md b/src/current/operating.md
new file mode 100644
index 00000000000..0a244779b9c
--- /dev/null
+++ b/src/current/operating.md
@@ -0,0 +1,178 @@
+# IBM Escrow Archive - Operating Runbook
+
+## Overview
+
+IBM Escrow requires a quarterly zip of the CockroachDB documentation delivered within 14 days of each new version release. The deliverable is `cockroachdb-docs.zip` containing a `complete_test_archive/` directory with all supported doc versions, viewable offline in a browser.
+
+## Prerequisites
+
+- Python 3.12+ with `beautifulsoup4` and `requests` installed
+- The Jekyll site must be built (`_site/docs/` exists with the new version)
+- Internet access (script downloads Google Fonts for offline use; falls back to system fonts if offline)
+- ~2 GB free disk space
+
+## Quick Start
+
+```bash
+cd /path/to/docs/src/current
+
+# 1. Fetch the archive script
+git fetch origin feat/offline-archive-scripts
+git show "origin/feat/offline-archive-scripts:src/current/create_full_archive.py" > create_full_archive.py
+
+# 2. Run it with the new version
+python3 create_full_archive.py --add-version v26.2 --stable v26.2 --zip
+
+# 3. Deliverable is ready
+ls -lh cockroachdb-docs.zip
+```
+
+That's it. The script handles everything: copying all versions, fixing navigation for offline use, downloading fonts, cleaning macOS artifacts, and producing the final zip.
+
+## CLI Reference
+
+```
+python3 create_full_archive.py [OPTIONS]
+
+Options:
+ --add-version VERSION Add a doc version to the archive. Repeatable.
+ Example: --add-version v26.2
+ --stable VERSION Set the stable/default version.
+ Defaults to the highest version in the list.
+ --zip Produce cockroachdb-docs.zip (required for delivery).
+ --site-dir PATH Path to _site/docs/ if non-standard.
+ --output-dir PATH Output directory name (default: complete_test_archive).
+```
+
+### Examples
+
+```bash
+# Standard quarterly delivery (add new version, set as stable)
+python3 create_full_archive.py --add-version v26.2 --stable v26.2 --zip
+
+# Add multiple new versions at once
+python3 create_full_archive.py --add-version v26.2 --add-version v26.3 --stable v26.3 --zip
+
+# Rebuild with all defaults (no new version)
+python3 create_full_archive.py --zip
+```
+
+## Step-by-Step Walkthrough
+
+### 1. Ensure the site is built
+
+The script reads from `src/current/_site/docs/`. Confirm the new version directory exists:
+
+```bash
+ls _site/docs/v26.2/ | head -5
+```
+
+If not built, run `make build` or `jekyll build` from the repo root first.
+
+### 2. Fetch the archive script
+
+The script lives on the `feat/offline-archive-scripts` branch:
+
+```bash
+git fetch origin feat/offline-archive-scripts
+git show "origin/feat/offline-archive-scripts:src/current/create_full_archive.py" > create_full_archive.py
+```
+
+### 3. Run the archive creator
+
+```bash
+python3 create_full_archive.py --add-version v26.2 --stable v26.2 --zip
+```
+
+The script runs these steps automatically:
+1. Creates `complete_test_archive/` directory
+2. Copies asset directories (css, js, images, fonts, _internal)
+3. Copies all version directories (v23.1 through the new version)
+4. Copies content directories (cockroachcloud, releases, advisories, molt, stable)
+5. Copies top-level HTML files
+6. Downloads jQuery and navgoco for offline navigation
+7. Downloads and localizes Google Fonts
+8. Processes all HTML files (rewrites URLs for offline use)
+9. Adds "archived version" banner to index.html
+10. Cleans macOS artifacts (__MACOSX, .DS_Store, ._ files)
+11. Creates `cockroachdb-docs.zip` excluding macOS artifacts
+12. Verifies 0 __MACOSX entries in the zip
+
+### 4. Verify the archive
+
+```bash
+# Quick check: no macOS artifacts
+unzip -l cockroachdb-docs.zip | grep -c __MACOSX # should be 0
+
+# Open in browser
+mkdir -p /tmp/verify-archive
+unzip -q cockroachdb-docs.zip -d /tmp/verify-archive
+open /tmp/verify-archive/complete_test_archive/index.html
+```
+
+Browser verification checklist:
+- [ ] Home page loads with sidebar and archived banner
+- [ ] Sidebar navigation expands/collapses
+- [ ] New version pages load (e.g., v26.2/install-cockroachdb.html)
+- [ ] Older version pages load (e.g., v24.3/install-cockroachdb.html)
+- [ ] CSS, fonts, and images render correctly
+- [ ] No search bar or AI widgets visible (they are online-only)
+- [ ] Console (Cmd+Option+J) shows no 404 errors for archive resources
+
+Expected benign console errors (not issues):
+- `file: URLs are treated as unique security origins` -- standard browser security for local files
+- `"[object Object]" is not valid JSON` -- navgoco cookie state, sidebar still works
+- `Failed to load resource: net::ERR_INVALID_URL` -- external resource stripped for offline
+- `Cannot read properties of null (reading 'error')` -- HubSpot form loader, irrelevant offline
+
+### 5. Deliver
+
+Upload `cockroachdb-docs.zip` to the escrow delivery location.
+
+### 6. Clean up
+
+```bash
+rm -f create_full_archive.py
+rm -rf complete_test_archive/
+rm -rf /tmp/verify-archive/
+```
+
+## Archive Structure
+
+```
+cockroachdb-docs.zip
+ complete_test_archive/
+ index.html # Home page with archived banner
+ 404.html
+ search.html
+ css/ # Stylesheets + google-fonts.css
+ js/ # jQuery, navgoco, site JS
+ fonts/ # Localized Google Fonts (Poppins, Source Sans/Code Pro)
+ images/ # All site images
+ _internal/ # Sidebar HTML files per version
+ cockroachcloud/ # CockroachDB Cloud docs
+ releases/ # Release notes
+ advisories/ # Technical advisories
+ molt/ # MOLT migration docs
+ stable/ # Symlink target (= latest stable version)
+ v23.1/ # Versioned docs
+ v23.2/
+ ...
+ v26.2/ # Latest version
+```
+
+## Troubleshooting
+
+| Problem | Solution |
+|---------|----------|
+| `ModuleNotFoundError: bs4` | `pip3 install beautifulsoup4 requests` |
+| `_site/docs/ not found` | Run `make build` or `jekyll build` first |
+| Script fails with Python 3.14 syntax errors | Use Python 3.12 or 3.13 |
+| Fonts fallback to system fonts | Check internet connectivity; archive still works |
+| Zip is too small (< 1 GB) | Verify all version dirs exist in `_site/docs/` |
+
+## Version History
+
+| Delivery Date | Stable Version | Versions Included | Size |
+|---------------|---------------|-------------------|------|
+| 2026-05-22 | v26.2 | v23.1 -- v26.2 (11 versions) | 1.7 GB |
diff --git a/src/current/test_full_archive_smoke.py b/src/current/test_full_archive_smoke.py
new file mode 100644
index 00000000000..78a44f1bf6c
--- /dev/null
+++ b/src/current/test_full_archive_smoke.py
@@ -0,0 +1,176 @@
+#!/usr/bin/env python3
+"""
+Smoke test for create_full_archive.py.
+
+Creates a minimal _site/docs fixture, runs FullArchiveCreator.build(), and
+verifies key invariants without requiring a full Jekyll build or network access.
+
+Run from src/current/:
+ python3 test_full_archive_smoke.py
+"""
+import re
+import shutil
+import sys
+import tempfile
+from pathlib import Path
+from unittest.mock import patch
+
+SCRIPT_DIR = Path(__file__).parent
+STABLE_VERSION = "v26.1"
+
+# Minimal HTML page with a /docs/-prefixed href and a Google Fonts link
+FIXTURE_HTML = """\
+
+
+
+