# K4 Cryptanalysis — Claude Project Knowledge Pack

> Generated 2026-05-01. Last commit: `366f7ea`. This file is the seed
> document for a Claude project that co-works on the K4 attack. Drop it
> into your project's instructions / context and a fresh Claude can pick
> up where the operator left off.
>
> **Refresh procedure:** ask Claude in the `k4/` working directory to
> "refresh the knowledge pack". Claude regenerates this file in place at
> repo root, citing the latest commit SHA at the top. Keep it under 30KB;
> trim older milestones if it grows.

---

## 1. Mission (one paragraph)

Derive the **encryption method** behind Kryptos K4 — the final unsolved
panel of Jim Sanborn's 1990 sculpture at CIA Langley. The plaintext was
photographed from accidentally-donated Smithsonian archive scraps in
September 2025 (Kobek and Byrne); Sanborn confirmed the cipher itself
remains undisclosed. The full archive sold at auction Nov 20, 2025 for
$962,500 to an anonymous bidder. K5 is confirmed to exist. The win
condition is METHOD, not plaintext.

The project is built and operated by AI agents under a single human
operator. Reasoning layer = 10 specialist Claude agents + a deterministic
Python crib validator. Compute layer = Python attack runner on a small
ARM VM. Public layer = Next.js site at kryptos.today (auth-gated for
visitors, admin console for the operator).

---

## 2. Hard ground truth (these are constants — never change them)

**Ciphertext (97 chars):**
```
OBKRUOXOGHULBSOLIFBBWFLRVQQPRNGKSSOTWTQSJQSSEKZZWATJKLUDIAWINFBNYPVTTMZFPKWGDKZXTJCDIGKUHUAUEKCAR
```

**Crib disclosures (0-indexed positions, Sanborn-released):**

| Positions | Plaintext | Released |
|---|---|---|
| 21–24 | `EAST`      | 2020-04 (publicly approved 2020-08) |
| 25–33 | `NORTHEAST` | 2020-01-29 (NYT Schwartz/Corum) |
| 63–68 | `BERLIN`    | 2010-11-20 (NYT Schwartz) |
| 69–73 | `CLOCK`     | 2014-11-20 (Sanborn announcement, 25th Berlin Wall anniversary) |

Constants live in `core/ciphertext.py` (`K4_CIPHERTEXT`, `K4_CRIBS`).

**Keyed alphabet used in K1 and K2:** `KRYPTOSABCDEFGHIJLMNQUVWXZ`.

---

## 3. Hard rules — non-negotiable (from `CLAUDE.md`)

1. **Never claim K4 is solved.** A "verified" candidate requires:
   - Passes the crib validator
   - Scores above quadgram threshold
   - Survives Devil's Advocate review
   - Re-derives correctly from spec alone
   - **All four. No exceptions.**
2. **The crib validator is the oracle, not agent reasoning.** Always call
   `core.cribs.validates(plaintext, K4_CRIBS)` — never eyeball whether
   cribs match.
3. **No fabricated keys, no fabricated citations.** "I don't have a
   confirmed source" is acceptable. Inventing one is not.
4. **Reproducibility is mandatory.** Every experiment in
   `experiments/YYYY-MM-DD-name/` with `hypothesis.md`, `run.py`,
   `results.jsonl`, `conclusion.md`. Seed every random source.
5. **Cite prior work.** The Kryptos community has 35 years of attempts —
   don't re-run what's already been ruled out.
6. **Skepticism over enthusiasm.** Default to "this isn't it" when in
   doubt.
7. **Phase 0 gate:** No attacks until `core/` passes K1/K2/K3 sanity
   checks. (Already passed.)

---

## 4. Architecture overview (one screen)

```
┌──────────────────────────────────────────────────────────────────┐
│  REASONING LAYER  ·  10 Claude specialist agents + crib validator│
│                                                                  │
│      Tabula (Orchestrator)                                       │
│        ├── Mneme   (Research Agent)                              │
│        ├── Codex   (Knowledge Curator)                           │
│        ├── Chi     (Statistical Cryptanalyst)                    │
│        ├── Scytale (Classical Cipher Specialist)                 │
│        ├── Bombe   (Modern ML Specialist)                        │
│        ├── Gnomon  (Sanborn Hint Specialist)                     │
│        ├── Null    (Devil's Advocate)                            │
│        ├── Scribe  (Repo Integrator)                             │
│        └── Sigma   (Synthesis Agent)                             │
│                                                                  │
│      Crib Validator  →  core/cribs.py  (Python function, oracle)│
│                                                                  │
└──────────────────────────────────────────────────────────────────┘
          │ heartbeat every ~5s, candidates as found
          ▼
┌──────────────────────────────────────────────────────────────────┐
│  COMPUTE LAYER                                                   │
│      Hetzner CAX11 (ARM, 2 cores, Nuremberg, k4-runner)          │
│      systemd unit `k4-attack`, Restart=always                    │
│      attacks/runner.py · ~17,000 attempts/sec                    │
│         └── attacks/strategies/<active strategy>.py              │
│         └── attacks/persistence.py → Supabase Postgres           │
│      cron `deploy/push-results.sh` every 10 min → GitHub         │
└──────────────────────────────────────────────────────────────────┘
          │ realtime publication: runner_heartbeats, attack_runs,
          │ candidates  (websocket)
          ▼
┌──────────────────────────────────────────────────────────────────┐
│  PUBLIC LAYER                                                    │
│      Vercel · Next.js 16 · kryptos.today                         │
│      Whole-site auth gate (proxy.ts) + magic-link invite flow    │
│      Routes: /login · / · /pulse · /plan · /progress ·           │
│              /architecture · /about · /admin                     │
│      Auth: /auth/<token> (magic link), /api/auth/password-login  │
│      Server-side API proxies:                                    │
│        /api/compute-metrics  → Hetzner Cloud API                 │
│        /api/commit-activity  → GitHub REST                       │
│      Analytics: GA4 (gtag.js), Measurement ID G-ZV6ZX8RXYF       │
└──────────────────────────────────────────────────────────────────┘
```

---

## 5. The 10 specialist agents (system prompts in `agents/<role>.md`)

Each agent has a hard mandate, hard inputs, hard outputs, and explicit
constraints. Read the corresponding `agents/<role>.md` before invoking.
Filenames use the role slug; codename is the user-facing label.

### 5.1 Tabula — Orchestrator (`agents/orchestrator.md`)
Project director. Selects next experiment, spawns subagents, integrates
results, declares phase gates. **Never implements ciphers.** **Never
evaluates candidates by feel** (validator is oracle).

### 5.2 Mneme — Research Agent (`agents/research_agent.md`)
Institutional memory. Prevents wasted compute on already-tried
approaches. Reads Sanborn primary statements, NSA FOIA, Histocrypt
papers, elonka.com/kryptos, kryptosfan archives. Writes drafts to
`knowledge_base/_drafts/`. **Never fabricates citations.**

### 5.3 Codex — Knowledge Curator (`agents/knowledge_curator.md`)
Research librarian. Promotes drafts only when every factual claim has a
URL or in-project reference. Maintains weekly digests at
`docs/weekly/YYYY-WW.md`. **Never generates findings.**

### 5.4 Chi — Statistical Cryptanalyst (`agents/statistical_cryptanalyst.md`)
Forensic statistician. Tells the team what K4 *cannot* be and what
residual structure must be explained. Outputs:
`experiments/statistical_baseline/dashboard.md`. **Never proposes specific
cipher solutions.** **Never reports a statistic without showing the
computation.**

### 5.5 Scytale — Classical Cipher Specialist (`agents/classical_cipher_specialist.md`)
Implements pre-computer cipher families: Vigenère family, transposition,
Hill, Playfair, ADFGVX, Gromark, homophonic, nomenclator, compositions.
Outputs in `attacks/strategies/<family>.py`. **Never hand-tunes to fit
cribs.** **Never skips K1/K2 unit tests.**

### 5.6 Bombe — Modern ML Specialist (`agents/modern_ml_specialist.md`)
Heuristic search over key spaces too large to enumerate: simulated
annealing, GA, hill climbing, MCMC, AZDecrypt-style. Required fitness
pattern: cribs are a hard gate, quadgram score on non-crib positions is
the objective. **Never trains end-to-end on cribs (overfit).** **Never
skips seed.**

### 5.7 Gnomon — Sanborn Hint Specialist (`agents/sanborn_hint_specialist.md`)
Translates Sanborn's public statements and physical sculpture features
into testable, falsifiable hypotheses. Hypothesis format: Source /
Citation / Cryptographic interpretation / Decryption procedure (testable)
/ Search space / Expected if correct / Falsifiability. **No hypothesis
without a falsifiability condition.**

### 5.8 Null — Devil's Advocate (`agents/devils_advocate.md`)
Falsifier. Reviews every entry in `results/candidates.jsonl`. Required
checks: crib-stripped quadgram score, random-baseline test (method
applied to 1000 random 97-char ciphertexts), degrees-of-freedom audit,
independent re-derivation. **Expected reject rate ≥95%** — approval at
higher rates means leniency.

### 5.9 Scribe — Repo Integrator (`agents/github_integrator.md`)
Vendors external Kryptos research repos as references. Trust matrix in
`knowledge_base/external_repos.md`. **Never trusts a repo's claimed
solution as the project's solution.**

### 5.10 Sigma — Synthesis Agent (`agents/synthesis_agent.md`)
Cross-domain pattern detection. Finds resonances across statistical,
hint, and cipher findings that no single specialist would notice.
Outputs `docs/synthesis/YYYY-WW.md`. **Synthesis without an actionable
experiment is just chatter** — every output must include at least one
concrete proposed experiment.

---

## 6. The crib validator (`core/cribs.py`)

A Python function — **NOT an agent**. The function:

```python
validates(plaintext: str, cribs: tuple[Crib, ...]) -> CribReport
```

Takes a 97-char A-Z plaintext, returns `passes: bool`, `failures`,
`matched_chars`, `total_chars`. Standard library only — no dependencies
beyond stdlib + `core.ciphertext`. Cannot be subverted by reasoning.

**Every agent and the runner must call it.** Agent reasoning is never
the oracle.

---

## 7. Phase 2 attack-family priority queue (as of 2026-05-01)

The Synthesis Agent compiled this on 2026-04-29. The 2026-04-30 period-7
attribution test demoted Phase 2A.1; current order reflects that.

| # | ID | Family | Status |
|---|---|---|---|
| 1 | 2B.1 | Weltzeituhr (Alexanderplatz) keystream | **active** (≥636M attempts, 0 candidates) |
| 2 | MORSE | Morse-panel content as keystream | queued |
| 3 | 2A.2 | K3-style 8-then-24 transposition + Vigenère | queued |
| 4 | 2B.2 | Mengenlehreuhr lamp-state keystream | queued |
| 5 | 2A.1 | Width-7 columnar transposition + Vigenère | queued (was #1; 333M attempts already burnt) |
| 6 | 2H | XOR-layered approaches | queued |
| 7 | 2C | Hill cipher variants — sizes ≥ 4×4 only | queued |
| 8 | 2D | Gromark with non-standard primers | queued |
| 9 | 2A.3 | Composite: position-dependent algorithm change | queued |
| 10 | 2F | K2-keyed K4 | deprioritized |
| 11 | 2G | Antipodes-derived | blocked (photo verification) |
| 12 | 2I | Synthesis-novel: physical-key + classical-cipher | queued |

**Re-rank reasoning** (per `experiments/2026-04-30-period-7-attribution/conclusion.md`):
The lag-7 z=+3.05 anomaly that motivated Phase 2A.1 is plaintext-side, not
encryption-side. K4's lag-7 match-rate (0.10) is +3.0σ above uniform
random but only **+1.3σ above same-length English samples** and **+0.4σ
above K3 plaintext windows**. The H3 × S1 intersection that ranked
Phase 2A.1 top-priority is substantially weakened.

The Sanborn 2025-11 promotion of Weltzeituhr over Mengenlehreuhr +
the new Sanborn-validated Morse-panel direction take the slots.

---

## 8. Completed milestones (in time order, UTC)

| Time | Milestone |
|---|---|
| 2026-04-29T02:00Z | Phase 0 — bootstrap (cipher core, validator, sanity tests) |
| 2026-04-29T04:00Z | External repos vendored (7 submodules) |
| 2026-04-29T06:00Z | Statistical baseline (N=10,000 random null) |
| 2026-04-29T18:00Z | Phase 1 — Sanborn hint constraint docs (4 hint families) |
| 2026-04-29T21:00Z | Famous false positives catalogued |
| 2026-04-29T23:00Z | Synthesis Agent → 12-family attack queue |
| 2026-04-30T02:21Z | Phase 2A.1 sweep started (width-7 columnar, ~17K att/s) |
| 2026-04-30T03:30Z | Period-7 attribution test → Phase 2A.1 motivation invalidated |
| 2026-04-30T04:07Z | Cloud activation — live-state DB realtime + Vercel |
| 2026-04-30T05:30Z | Phase 2B.1 sweep started (Weltzeituhr keystream); 0/96 first pass |
| 2026-04-30T13:00Z | Auth gate added — Supabase Auth on operational routes; per-user activity log |
| 2026-04-30T16:00Z | Multi-agent code audit + 16-issue resolution sweep |
| 2026-04-30T19:00Z | Whole-site auth gate (proxy.ts) + password-only login + admin invite flow |
| 2026-04-30T21:00Z | Magic-link auto-login at `/auth/<token>`; email-less invite (handle-only) |
| 2026-04-30T23:00Z | /login redesign as cinematic dossier (large image, big-type "can A.I. crack K4?") |
| 2026-05-01T13:30Z | LiveStrip on /login: 3 animated metrics, total attempts cumulative across all phases |
| 2026-05-01T14:00Z | Google Analytics 4 wired (Measurement ID `G-ZV6ZX8RXYF`) |
| 2026-05-01T15:00Z | Animated favicon (canvas-driven copper "K" + breathing signal-green dot) |
| 2026-05-01T15:30Z | /admin tables — sortable + searchable + refreshable, hide-user filter, names in totals/visits |

---

## 9. Recent findings — read these first

### 9.1 Period-7 attribution test (2026-04-30)
`experiments/2026-04-30-period-7-attribution/`. K4's lag-7 match-rate is
+3σ vs uniform random but only +1.3σ vs same-length English. **Lag-7 is
plaintext-side.** Phase 2A.1 demoted from #1 to #5.

### 9.2 Phase 2A.1 sweep result (negative)
Ran from 2026-04-30T02:21Z to 2026-04-30T13:40Z (with one switch). Total:
~333M attempts. 0 candidates passed cribs. Strategy:
`attacks/strategies/width7_columnar_vigenere.py`. Demoted but not deleted
— a verified-1988 city list might re-elevate Phase 2A.1 again.

### 9.3 Phase 2B.1 first sweep result (negative, conditional)
`experiments/2026-04-30-weltzeituhr-keystream/`. 96/96 candidates rejected
under provisional 1988 city list. Best quadgram-per-char: −7.72 (vs random
null −8.30). No structural near-miss. **Negative is conditional on the
provisional list** — does not falsify the Weltzeituhr-keystream class.

### 9.4 Phase 2B.1 ongoing sweep (2026-04-30 → present)
Runner has executed ≥636M attempts under `weltzeituhr_keystream` since
2026-04-30T05:30Z, no crib-passing candidates. Heartbeats every ~10 min
in git history (commit `731b09e` at 2026-05-01T15:40Z = 636,451,001).
Sweep continues until exhausted or until a verified 1988 engraving
list arrives (Open Research Ticket #1).

---

## 10. Operational state (where things actually live)

### 10.1 Compute host
- Provider: Hetzner Cloud (project ID: 14409368)
- Server: `k4-runner`, ID 128623112
- IP: 94.130.78.175
- Type: CAX11 (ARM aarch64, 2 cores, 4GB, Ubuntu 24.04)
- DC: Nuremberg (nbg1-dc3)
- SSH key: `~/.ssh/k4_runner` (operator's laptop)
- systemd unit: `k4-attack` (in `deploy/k4-attack.service`)
- Working tree: `/home/k4/k4`
- env file: `/home/k4/k4/.env` (mode 0600, owner k4)
- cron: `*/10 * * * * cd /home/k4/k4 && bash deploy/push-results.sh >> /var/log/k4-push.log 2>&1`
  - **CRITICAL:** must invoke as `bash …`, not `./…` —
    `git reset --hard` strips the execute bit. Using `bash` makes cron
    permanent regardless of file mode.

### 10.2 Live-state DB
- Provider: Supabase Pro
- Project: `kryptos-k4`, ref `lvwglapwxajsifznvtsc`
- Region: us-east-1 (North Virginia)
- URL: `https://lvwglapwxajsifznvtsc.supabase.co`
- Schema: `deploy/supabase/schema.sql` (idempotent migration)
- Cipher tables: `attack_runs`, `runner_heartbeats`, `candidates`, `rejected_samples`
- Auth/admin tables: `page_visits`, `invitee_secrets` (password_sha256 PK + password_plaintext for the operator's reveal-once UI), `rejected_feed`
- RPC: `admin_per_user_visit_totals()` — admin-gated SQL group-by; **must be
  called via the authenticated user client, not the service-role client**
  (service-role JWT carries no app_metadata so the in-function admin
  guard fails)
- Views: `current_run`, `latest_heartbeat`
- RLS: anon role gets `SELECT` only on the four cipher tables; auth/admin
  tables require `app_metadata.is_admin = true`
- Realtime publication: `runner_heartbeats`, `attack_runs`, `candidates`
- Backups: daily + 7-day Point-in-Time Recovery (Pro tier)
- Heartbeat cadence: 5 seconds (throttled in `attacks/persistence.py`)

### 10.3 Public web
- Provider: Vercel (account `fredlumieres-projects`)
- Project: `kryptos.today`
- Domain apex: `kryptos.today` (registered at GoDaddy)
- Source: `web/` directory of the GitHub repo
- Framework: **Next.js 16** (App Router, Turbopack) + Tailwind 4
  + React 19 + Bun (lockfile is `bun.lock` — do NOT add `package-lock.json`)
- **Vercel CLI must run from repo root** (`/Users/fredericlumiere/k4`),
  not from `web/`. The `.vercel/project.json` lives at repo root.
- Auto-deploys on every push to `main`
- Auth gate: `web/proxy.ts` (Next.js 16 replaces `middleware.ts` with `proxy.ts`).
  Allowlists `/login`, `/api/auth/*`, `/auth/*`. Sets `x-user-email` and
  `x-user-is-admin` headers. Fails CLOSED on missing env.
- Public + gated routes:
  - `/login` — auth gate landing (cinematic dossier with LiveStrip,
    Wikipedia link, Signal deeplink)
  - `/auth/<token>` — magic-link auto-login (GET → SHA-256 lookup →
    signInWithPassword → 302 to `/`)
  - `/` — live cipher grid + ProcessingStrip + RoadmapPreview + Terminal
  - `/pulse` — three live panels: Supabase / compute / commits
  - `/plan` — 12-family queue + completed timeline + ETA
  - `/progress` — research findings log (markdown-driven)
  - `/architecture` — system overview + lightbox SVG diagram
  - `/about` — facts-only project description
  - `/log` — live attack feed (auth-gated)
  - `/admin` — operator-only console (invitees, per-user totals
    drilldown, recent visits; sortable+searchable+refreshable; hide-user
    localStorage filter; magic-link copy buttons)
- Server APIs:
  - `/api/auth/password-login` — SHA-256 lookup → signInWithPassword;
    220ms timing equalisation; origin-guard; Vercel WAF rate-limited
    to 10 req/min/IP
  - `/api/admin/create-user` — service-role invite flow (synthesizes
    `inv-<8hex>@invitees.kryptos.today` if email is blank)
  - `/api/admin/clear-visits` — bulk delete (per-user or all)
  - `/api/visits/track` + `/api/visits/leave` — per-page-visit logging
    (server-side `left_at` to prevent client forgery)
  - `/api/compute-metrics` — server proxy to Hetzner Cloud API
  - `/api/commit-activity` — server proxy to GitHub commits
- Analytics: Google Analytics 4 via gtag.js (`next/script`,
  `strategy="afterInteractive"`); Measurement ID
  `NEXT_PUBLIC_GA_MEASUREMENT_ID = G-ZV6ZX8RXYF`. **GA Home tab lags
  24–48h after first install; use the Realtime tab to verify live
  traffic.**

### 10.4 Code archive
- Provider: GitHub
- Repo: `Fredlumiere/k4` (private)
- Default branch: `main`
- Write deploy key on the compute host

### 10.5 Cron flow
1. Runner writes `results/live_state.json` continuously.
2. Every 10 min, cron runs `deploy/push-results.sh`:
   - Snapshot `results/live_state.json`, `rejected.jsonl`, `candidates.jsonl`
   - `git fetch + reset --hard origin/main` (clean slate)
   - Restore snapshot
   - Merge live state into `web/public/data/snapshot.json` via `jq`
   - Commit, push to GitHub
3. Vercel auto-builds on push.
4. Runner detects code-rev change within 60s, exits, systemd reloads on
   new code.

---

## 11. Repository map

```
core/                          ← cipher constants, validator, scoring
  ciphertext.py               (K4_CIPHERTEXT, K4_CRIBS, KRYPTOS_ALPHABET)
  cribs.py                    (validates() — the oracle)
  scoring.py                  (IoC, χ², quadgram)
  scoring_data/quadgrams.txt  (389,373-entry English corpus)
  sanity_checks.py            (K1/K2/K3 verification)
  test_core.py                (17 pytest tests)

agents/                        ← 10 specialist system prompts (markdown)
  orchestrator.md             (codename: Tabula)
  research_agent.md           (Mneme)
  knowledge_curator.md        (Codex)
  statistical_cryptanalyst.md (Chi)
  classical_cipher_specialist.md (Scytale)
  modern_ml_specialist.md     (Bombe)
  sanborn_hint_specialist.md  (Gnomon)
  devils_advocate.md          (Null)
  github_integrator.md        (Scribe — display name: "Repo Integrator")
  synthesis_agent.md          (Sigma)

attacks/
  runner.py                   (continuous Phase 2 sweep loop)
  persistence.py              (Supabase mirror, exception-safe)
  strategies/
    width7_columnar_vigenere.py     (Phase 2A.1 — exhausted)
    weltzeituhr_keystream.py        (Phase 2B.1 — active)

experiments/
  statistical_baseline/                          (Phase 0 deliverable)
  2026-04-29-hint-stat-intersections/            (Phase 1 synthesis)
  2026-04-30-period-7-attribution/               (invalidates 2A.1)
  2026-04-30-weltzeituhr-keystream/              (2B.1 first sweep)

knowledge_base/
  prior_work.md
  sanborn_statements.md
  scheidt_statements.md
  timeline.md
  external_repos.md           (trust matrix for vendored repos)
  famous_false_positives.md   (mattklepp, slightofmind369, etc.)
  external/<repo>.md          (per-repo briefs)
  _drafts/                    (Research Agent / SHS staging)

external_repos/               ← git submodules
  k4testing/                  (Bean — high trust)
  kryptos-doranchak/          (Doranchak — high trust)
  K4nundrum/                  (glthr — medium)
  mproffitt-kryptos/          (medium)
  patrickkellogg-kryptos/     (low)
  azdecrypt/                  (gold-standard heuristic search tool)
  case_studies/               (mattklepp, slightofmind369 — failed claims)

deploy/
  k4-attack.service           (systemd unit for the runner)
  setup.sh                    (one-shot host bootstrap; cron via bash)
  push-results.sh             (cron snapshot+reset+restore+push)
  RECOVERY.md                 (DR runbook)
  supabase/schema.sql         (Postgres migration — idempotent)

results/
  live_state.json             (overwritten by runner)
  candidates.jsonl            (validated candidates — empty so far)
  rejected.jsonl              (sampled rejections — capped log)

web/                          ← Next.js 16 App Router site
  AGENTS.md                   ← reminder: Next.js 16 has breaking changes
                                from training data; read node_modules/next/dist/docs
  proxy.ts                    (whole-site auth gate; replaces middleware.ts in Next 16)
  app/
    layout.tsx                (root layout; mounts PageVisitTracker, AnimatedFavicon, GA4)
    page.tsx                  (home — /)
    pulse/                    (/pulse + 3 panels + API routes)
    plan/                     (/plan, data.ts, milestones.ts, RichText.tsx)
    progress/                 (markdown-driven log + search)
    architecture/             (SVG diagram + lightbox)
    about/
    log/                      (live attack feed, auth-gated)
    login/                    (cinematic dossier landing + LoginForm + LiveStrip)
    auth/[token]/             (magic-link auto-login)
    admin/                    (operator console)
      page.tsx                (server component; parallel data fetch)
      components/
        InviteForm.tsx
        InviteeList.tsx       (sortable+searchable; magic-link copy buttons)
        PerUserTotalsDrilldown.tsx (sortable+searchable; hide+drilldown+delete)
        AdminVisitsTable.tsx  (sortable+searchable; respects hidden-users)
        ClearActivityButton.tsx
        tableControls.tsx     (shared useTableControls hook + SortHeader,
                              TableSearchInput, HiddenUsersBanner)
        useHiddenUsers.ts     (localStorage-backed hidden-set, cross-tab synced)
    components/               (Nav, SiteHeader, StatusBar, ProcessingStrip,
                              CipherGrid, Findings, Terminal,
                              AnimatedFavicon, PageVisitTracker, etc.)
    api/
      auth/password-login/route.ts   (SHA-256 → signInWithPassword)
      admin/create-user/route.ts     (synthesizes inv-XXXX@invitees emails)
      admin/clear-visits/route.ts
      visits/track/route.ts
      visits/leave/route.ts
      compute-metrics/route.ts
      commit-activity/route.ts
    lib/useLiveState.ts       (Supabase realtime hook)
    types.ts
    globals.css
  lib/
    supabase/{server,client,admin}.ts (createClient variants;
                                       admin uses SUPABASE_SECRET_KEY)
    crypto.ts                 (sha256 helper)
    format.ts                 (formatTs, formatRelative, formatDuration)
    origin-guard.ts           (CSRF protection; falls through to Referer
                              for sendBeacon which omits Origin)
  data/                       (markdown sources for build-data)
    progress.md
    focus.md
  scripts/build-data.ts       (build-time data composer)
  public/data/                (snapshot.json, progress.json — built artifacts)

docs/
  methodology.md              ← 4-of-4 verification, scoring thresholds
  orchestration.md            ← phase flow, gates, agent invocation order
  glossary.md                 ← cipher + Kryptos terminology

CLAUDE.md                     ← project's claude-code instructions
README.md
SETUP.md
requirements.txt
K4-Project-Knowledge-Pack.md  ← THIS FILE (gitignored at repo root)
```

---

## 12. Open research tickets (Source TBD)

These are blocking concrete attacks. Each is a Sanborn Hint Specialist
or Knowledge Curator task.

1. **1988-era Weltzeituhr engraving list** — gates Phase 2B.1 second pass
   with verified data. Sources to chase: DDR Museum photo archives,
   Bundesarchiv, Berlin tourism board 1988 publications, the
   Berlin-Mitte district archives.
2. **Morse panel canonical content** — gates Phase 2B.1's neighbor
   (MORSE keystream). Current list (LUCID MEMORY, SHADOW FORCES,
   VIRTUALLY INVISIBLE, DIGETAL INTERPRETATION, etc.) is community-sourced.
3. **Antipodes K4 region** — does it differ character-by-character from
   Kryptos K4? Photograph verification required before any 2G attack.
4. **1992 NSA "interval 7 roughness" provenance** — Bean 2018 cites it
   but the primary NSA document hasn't been re-derived under a method
   that controls for English-baseline lag-7. The period-7 attribution
   test (2026-04-30) means this citation should be re-verified before
   reuse.
5. **German Guesser 2022 specifics** — referenced as "started to look
   right" but specifics are not public.

---

## 13. Verification standard (the four-of-four — quoted verbatim from `CLAUDE.md`)

> Never claim K4 is solved. A "verified" candidate requires: passes the
> crib validator, scores above quadgram threshold, survives Devil's
> Advocate review, re-derives correctly from spec alone. All four. No
> exceptions.

The four gates, formalized in `docs/methodology.md`:

1. **Crib gate** — `cribs.validates(candidate).passes` returns True.
2. **Statistical gate** — quadgram score per char on non-crib positions
   exceeds threshold T (initial T = -10.0; calibrate against K1/K2).
3. **Red-team gate** — Devil's Advocate verdict: "Promote to verified."
4. **Re-derivation gate** — A different agent re-implements the method
   from spec alone and produces the same plaintext.

---

## 14. Recent commits (last 36h, web work + cipher heartbeats summary)

Web layer (every 10 min the runner pushes a heartbeat commit; those
are elided here):

```
366f7ea admin: refresh button on each table (router.refresh + useTransition)
ac3ab44 admin: sortable + searchable tables, hide-user filter, names in totals/visits
e5afe94 favicon: animated copper-K with pulsing live dot, JS-canvas
38e48c6 analytics: wire Google Analytics 4 (G-ZV6ZX8RXYF) into the root layout
c3ece24 login: live strip 'total attempts' now sums across ALL phases
30eebdf login: live-stats strip — 3 animated metrics above the dossier image
29b0252 login: Signal block now opens Signal app on click
bbc0825 login: link 'Kryptos' to its Wikipedia entry in the explainer
69cc030 login: drop pseudo-official 'classified' / 'OP-CLEARED' / 'operator file'
a63f524 login: redesign as a classified dossier — image as exhibit, auth as checkpoint
459a36d login: replace hero image with the Project K4 infographic
e332dbd login: replace exhibit-caption with project explainer
a904ebc admin: fix 'network error' on successful invite (React 19 e.currentTarget)
716b1f8 admin: invite without email — name/handle field, magic link as the share artifact
9d5e146 auth: magic-link auto-login at /auth/<token>
57acb64 admin: call admin_per_user_visit_totals via authenticated client, not service-role
5cae4e5 admin: add Location column to Invited Museums + city field on invite form
8bee0b7 audit: resolve 11 findings from the multi-agent audit + 2 UI feedback items
5fe9b07 audit follow-up: close #18, #20, #22, #27 (the remaining open audit items)
73fb1c3 admin: trash icon per row to bulk-delete one user's visits
d2f5423 admin: lock-icon secure stamp + logout in header; flush-activity button
7330b68 auth: gate the WHOLE site behind login (not just operational routes)
3179242 admin: store password plaintext + show with copy button on /admin
90ea9ca auth: password-only login (no email field)
cf98749 admin: per-user totals drill into per-path breakdown
91e559e admin: museum-invite flow — generate account + reveal-once password
cf59321 auth: gate operational routes behind Supabase Auth, log per-user activity
bafc27c web + db: /log live attack feed, invariant SiteHeader, retention pruning
f5f4f1f brand: codename the 10 specialist agents (Tabula..Sigma)
b54cd8d cipher: switch runner to Phase 2B.1 (Weltzeituhr keystream)
c74c4cd experiment: period-7 attribution test invalidates Phase 2A.1 motivation
89eaaaa infra: Supabase realtime persistence + DR runbook + auto-restart
```

Cipher runner heartbeats since 2026-04-30T18:00Z: Phase 2B.1
weltzeituhr_keystream every ~10 min, latest at 636,451,001 attempts
(commit `731b09e`, 2026-05-01T15:40Z). 0 candidates passed cribs.

---

## 15. Credentials inventory (names only — values live in env files)

**On operator's laptop** (`/Users/fredericlumiere/k4/.env`):
- `SUPABASE_URL`
- `SUPABASE_PUBLISHABLE_KEY`  (anon JWT)
- `SUPABASE_SECRET_KEY`       (service role)
- `HCLOUD_TOKEN`              (Hetzner Cloud API, read scope)
- `HCLOUD_SERVER_ID`          (128623112)

**On Hetzner host** (`/home/k4/k4/.env`):
- `SUPABASE_URL`
- `SUPABASE_PUBLISHABLE_KEY`
- `SUPABASE_SECRET_KEY`

**On Vercel** (project `kryptos.today` env vars, production):
- `NEXT_PUBLIC_SUPABASE_URL`              (browser-safe)
- `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY`  (browser-safe; legacy JWT — see note)
- `NEXT_PUBLIC_GA_MEASUREMENT_ID`         (browser-safe; `G-ZV6ZX8RXYF`)
- `SUPABASE_SECRET_KEY`                   (server-only — service-role JWT;
                                          marked `--sensitive`)
- `HCLOUD_TOKEN`                          (server-only)
- `HCLOUD_SERVER_ID`                      (server-only)
- `GITHUB_TOKEN`                          (server-only — PAT, used by /api/commit-activity)
- `GITHUB_REPO`                           (server-only — `Fredlumiere/k4`)

**In macOS Keychain** (operator):
- Supabase CLI access token (PAT) — used to apply migrations via Management API

**Deferred rotations** (operator decided to defer — should self-check
on 2026-05-14):
- Supabase PAT in keychain (set during 2026-04-30 work; visible in shell history)
- GitHub `gho_*` PAT (broad scope; should be replaced with fine-grained
  PAT scoped to `Fredlumiere/k4` with `Contents: Read-only`, 1y expiry)

**Note on the new `sb_publishable_*` key format:** Supabase issued a
`sb_publishable_*` style publishable key for this project, but PostgREST
on this project rejects it as "Invalid API key". The legacy JWT anon key
(`eyJhbGci...`) works and is what's deployed.

---

## 16. Public site URL leaks (network-layer, not UI)

The website's UI uses generic function names (compute host, live-state
DB, edge web, code archive). But network-layer signals still reveal
vendors:

- Realtime websocket connects to `wss://lvwglapwxajsifznvtsc.supabase.co`
  — visible in browser dev tools' network tab.
- Vercel HTTP responses include `Server: Vercel` header.
- `https://www.googletagmanager.com/gtag/js?id=G-ZV6ZX8RXYF` script tag
  in HTML reveals GA4 + the Measurement ID.

To fully hide these would require a CNAME at `realtime.kryptos.today` →
Supabase realtime endpoint, plus edge-config header stripping (Enterprise
tier on Vercel).

---

## 17. How to use this doc with a Claude project

When you set up your Claude project, paste this whole doc into the
project knowledge / instructions. Key affordances:

1. **A fresh Claude can read sections 1–4 and immediately understand
   what the project is.**
2. **Sections 5–7 give the agent roster, validator semantics, and
   current attack queue** — the operating state you'd otherwise have
   to re-discover.
3. **Section 9 is the most important "what changed recently" section.**
   Always read it before suggesting strategy.
4. **Section 10.3 is the public-web cheat sheet** — auth gate, routes,
   admin console, GA wiring. Read before touching `web/`.
5. **Section 12 (open research tickets) is where you can direct work.**
   Each ticket is a concrete primary-source hunt.
6. **Section 13 is the bright line.** No agent can claim K4 solved
   without all four of those gates.

**Operational pattern:**
- The runner is on Hetzner. SSH in via `~/.ssh/k4_runner` as root if you
  need to debug it (IP in section 10.1).
- To switch attack strategy: edit `attacks/runner.py` to import a
  different strategy class, push to GitHub. Cron pulls within 10 min,
  runner exits on rev change, systemd reloads.
- To check current runner state: `curl https://kryptos.today/data/snapshot.json | jq .live`
- To inspect Supabase live state directly: query `runner_heartbeats`
  with the publishable key + the Supabase REST API.
- To run an experiment: `experiments/YYYY-MM-DD-name/{hypothesis.md,run.py,results.jsonl,conclusion.md}`.
  Always seed RNG. Always end with conclusion.md that explicitly states
  what was falsified and what wasn't.

**Web-layer gotchas (learned the hard way):**
- Next.js 16 uses `proxy.ts` instead of `middleware.ts`. Read
  `web/AGENTS.md`; the docs in `node_modules/next/dist/docs/` are
  authoritative.
- React 19 nulls `e.currentTarget` after `await` in event handlers.
  Capture it synchronously: `const formEl = e.currentTarget;` BEFORE
  awaiting.
- Vercel CLI must run from repo root, not `web/` — `.vercel/project.json`
  is at root.
- Project uses Bun (`bun.lock`). Don't accidentally add
  `package-lock.json`.
- Server-side RPC calls that check `auth.jwt() -> 'app_metadata'` fail
  for service-role JWTs (which carry no app_metadata). Use
  `auth.users.raw_app_meta_data` lookup via `auth.uid()` instead, OR
  call the RPC via the authenticated user client.
- GA4 "No data received" on the Home tab is normal for 24–48h after
  install. Use the Realtime tab to verify.

**Red flags that should slow you down:**
- A candidate that "looks like English" without passing the validator.
- A method that requires the cribs in its derivation but isn't tested
  for the cribs as a hard gate (mattklepp anti-pattern).
- An agent making a citation without a URL.
- Any claim of "this looks right" without quadgram score on non-crib
  positions.
- Re-running a parameter range that `knowledge_base/prior_work.md`
  already lists as exhausted.

---

## 18. What this doc does NOT contain

- Solution claims of any kind.
- Speculation about K4's plaintext beyond the four cribs.
- Any specific cipher hypothesis the operator has expressed personal
  belief in.
- API keys or credentials in cleartext.
- The 1988 Weltzeituhr engraving list (still Source TBD).
- Personal information about invitees from the admin console.

---

*End of pack. Last regenerated 2026-05-01 (commit `366f7ea`). To
regenerate: ask Claude in the k4 working directory to refresh this doc.*
