diff --git a/content/colophon.md b/content/colophon.md index 5d42b65..fe05474 100644 --- a/content/colophon.md +++ b/content/colophon.md @@ -1,7 +1,6 @@ --- title: Colophon date: 2026-03-21 -modified: 2026-04-27 status: "Durable" confidence: 93 tags: [meta] diff --git a/content/essays/networking-stack/index.md b/content/essays/networking-stack/index.md index 6658b3a..8a57751 100644 --- a/content/essays/networking-stack/index.md +++ b/content/essays/networking-stack/index.md @@ -13,7 +13,6 @@ importance: 1 scope: personal novelty: conventional practicality: moderate -confidence-history: --- A fuller write-up follows. In the meantime, see the [projects index](/cv/projects/). diff --git a/content/essays/neuropose/index.md b/content/essays/neuropose/index.md index 6de474f..07a4cb7 100644 --- a/content/essays/neuropose/index.md +++ b/content/essays/neuropose/index.md @@ -18,7 +18,6 @@ evidence: 4 scope: broad novelty: innovative practicality: high -confidence-history: --- A fuller write-up follows with the clinical-implications manuscript. In the meantime, see the [projects index](/cv/projects/). diff --git a/content/essays/scaling_outage.md b/content/essays/scaling_outage.md index 73bf7ec..fe3df96 100644 --- a/content/essays/scaling_outage.md +++ b/content/essays/scaling_outage.md @@ -1,23 +1,20 @@ --- title: "Speculative Reluctance" -date: 2026-04-15 # required; used for ordering, feed, and display -abstract: > # optional; shown in the metadata block and link previews +date: 2026-04-15 +abstract: > AI labs are likely deliberately reluctant to scale because they are aware that any imminient shift to locally run models as the norm would render their compute redundant. We take Anthropic as a principal case study to validate this hypothesis. -tags: # optional; see Tags section +tags: - ai - tech - speculative - open - -# Epistemic profile — all optional; the entire section is hidden unless `status` is set -status: "Draft" # Draft | Working model | Durable | Refined | Superseded | Deprecated -confidence: 55 # 0–100 integer (%) -importance: 3 # 1–5 integer (rendered as filled/empty dots ●●●○○) -evidence: 1 # 1–5 integer (same) -scope: broad # personal | local | average | broad | civilizational -novelty: moderate # conventional | moderate | idiosyncratic | innovative -practicality: high # abstract | low | moderate | high | exceptional -confidence-history: # list of integers; trend arrow derived from last two entries +status: "Draft" +confidence: 55 +importance: 3 +evidence: 1 +scope: broad +novelty: moderate +practicality: high --- Running a lab that develops frontier LLMs is somewhat like playing a game that, by all measurable metrics external, you are bound to lose. The amount of compute required to train a frontier LLM is unbelievably expensive. The expense of inference is even more astronomical. OpenAI claims at the time of this writing to have somewhere between 900 Million and 1 Billion active users, all of whom require some amount of inference cost, and some small subset of whom consume an enormous amount of compute - to use their words, this is ["commercial scale."](https://openai.com/index/accelerating-the-next-phase-ai/). This isn't to mention the immense amount of competition - there are many major players in the United States alone contributing models that push the boundaries. OpenAI may have been the first, but Anthropic, Google, Meta, xAI, and, yes, even Amazon and Bytedance are following right along. diff --git a/content/essays/specification-dilemma/index.md b/content/essays/specification-dilemma/index.md index b371999..3339a3f 100644 --- a/content/essays/specification-dilemma/index.md +++ b/content/essays/specification-dilemma/index.md @@ -19,7 +19,6 @@ evidence: 5 scope: civilizational novelty: innovative practicality: moderate -confidence-history: --- There are at least two distinct ways to reduce the search space over which AGI^[The definition of "Artificial General Intelligence", or whether such a definition exists, is contentious. My use of the term is not intended to endorse any proposed timeline for AGI, nor to suggest that it is inevitable. It is rather to provide calibration through a hypothetical goal that clearly justifies pursuit.] will have to operate. The first involves a harmonious interaction of agent and human, not transactional in origin, not fully autonomous nor fully human-driven, but rather collaborative in nature - the agent augments the capacity of the human, just as any other good tool for thought does, by working within the scope of something well specified and ideated upon. This is not to say that the agent cannot have a place in such planning, but rather that the human is ultimately the driver of the actions and tasks, defining the scope of what is to be done in as much detail as possible without being the one to actually do it. diff --git a/content/essays/weenix/index.md b/content/essays/weenix/index.md index 13ac55c..b82f836 100644 --- a/content/essays/weenix/index.md +++ b/content/essays/weenix/index.md @@ -14,7 +14,6 @@ importance: 1 scope: local novelty: moderate practicality: low -confidence-history: --- A fuller write-up follows. In the meantime, see the [projects index](/cv/projects/). diff --git a/static/images/canto31.jpg b/static/images/canto31.jpg index f1490d3..94a3433 100644 Binary files a/static/images/canto31.jpg and b/static/images/canto31.jpg differ diff --git a/static/site.webmanifest b/static/site.webmanifest index bf3b677..8b20b3a 100644 --- a/static/site.webmanifest +++ b/static/site.webmanifest @@ -1,6 +1,9 @@ { "name": "Levi Neuwirth", "short_name": "ln", + "description": "Personal site of Levi Neuwirth — essays, research, music, and photography.", + "start_url": "/", + "scope": "/", "icons": [ { "src": "/web-app-manifest-192x192.png", @@ -8,11 +11,23 @@ "type": "image/png", "purpose": "any" }, + { + "src": "/web-app-manifest-192x192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, { "src": "/web-app-manifest-512x512.png", "sizes": "512x512", "type": "image/png", "purpose": "any" + }, + { + "src": "/web-app-manifest-512x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" } ], "theme_color": "#16140f", diff --git a/templates/blog-post.html b/templates/blog-post.html index 1eba026..018ed6e 100644 --- a/templates/blog-post.html +++ b/templates/blog-post.html @@ -17,25 +17,28 @@ $body$ $if(backlinks)$ + $endif$ $endif$ diff --git a/tools/archive.py b/tools/archive.py index b16a1e3..603533f 100644 --- a/tools/archive.py +++ b/tools/archive.py @@ -270,7 +270,10 @@ def extract_text_pdf(pdf: Path, txt: Path) -> None: """Extract plain text from `pdf` into `txt` via pdftotext. On any failure an empty file is written so downstream steps still find it.""" try: - subprocess.run(["pdftotext", "-q", str(pdf), str(txt)], check=True) + # `--` ends option parsing so a slug starting with `-` cannot be + # mistaken for a pdftotext option. + subprocess.run(["pdftotext", "-q", "--", str(pdf), str(txt)], + check=True) except (subprocess.CalledProcessError, FileNotFoundError) as exc: err(f"{pdf.name}: pdftotext failed ({exc}); writing empty text sidecar") txt.write_text("", encoding="utf-8") @@ -292,6 +295,51 @@ def find_monolith() -> str | None: return shutil.which("monolith") +MONOLITH_VERSION_FILE = REPO_ROOT / "tools" / "monolith-version.txt" + +# Binaries already verified this run — the pin check hashes the binary +# once, not once per snapshot. +_monolith_verified: set[str] = set() + + +def _pinned_monolith_sha256() -> str | None: + """Parse the `sha256 = ` line from tools/monolith-version.txt. + Returns None when the file is missing or unparseable (the caller + warns and continues — only a *mismatch* is fatal).""" + try: + text = MONOLITH_VERSION_FILE.read_text(encoding="utf-8") + except OSError: + return None + m = re.search(r"^\s*sha256\s*=\s*([0-9a-fA-F]{64})\s*$", + text, re.MULTILINE) + return m.group(1).lower() if m else None + + +def verify_monolith(mono: str) -> None: + """Integrity gate for the snapshot tool itself: the binary that + produces committed artifacts must match the SHA-256 pinned in + tools/monolith-version.txt. A mismatch is an integrity error (print + loudly, exit non-zero, halt `make build`); a missing or unparseable + version file is a warning only.""" + if mono in _monolith_verified: + return + pinned = _pinned_monolith_sha256() + if pinned is None: + print(f"[archive] WARNING: {MONOLITH_VERSION_FILE.name} is missing " + f"or has no parseable `sha256 = …` line — monolith binary " + f"integrity NOT verified ({mono})", file=sys.stderr) + _monolith_verified.add(mono) + return + live = sha256_of(Path(mono)) + if live != pinned: + err(f"monolith binary {mono} fails SHA-256 verification " + f"(pinned {pinned}, found {live}). The snapshot tool's bytes " + f"do not match tools/monolith-version.txt — re-vendor the " + f"binary or update the pin (see that file's instructions).") + sys.exit(1) + _monolith_verified.add(mono) + + def body_noarchive(path: Path) -> bool: """True if the snapshot declares — the in-document equivalent of the X-Robots-Tag header.""" @@ -356,6 +404,7 @@ def fetch_html(url: str, dest: Path) -> bool: f"tools/bin/monolith (see tools/monolith-version.txt) or set " f"$MONOLITH_BIN; HTML snapshot skipped") return False + verify_monolith(mono) source = dest.with_suffix(dest.suffix + ".source.part") tmp = dest.with_suffix(dest.suffix + ".part") diff --git a/tools/extract-exif.py b/tools/extract-exif.py index 73f7b25..799ec04 100755 --- a/tools/extract-exif.py +++ b/tools/extract-exif.py @@ -36,6 +36,7 @@ images are logged and the rest of the walk continues. from __future__ import annotations import json +import os import shutil import subprocess import sys @@ -133,6 +134,12 @@ def _read_exif_via_exiftool(image: Path) -> dict[str, Any]: entry. Numeric values come through as numbers; text values as strings. We accept missing keys silently. """ + # exiftool does not reliably support `--` as an end-of-options + # marker, so make the path argument non-option-shaped instead: a + # relative path is prefixed with ./ so it can never start with `-`. + image_arg = str(image) + if not os.path.isabs(image_arg): + image_arg = os.path.join(os.curdir, image_arg) result = subprocess.run( [ "exiftool", @@ -156,7 +163,7 @@ def _read_exif_via_exiftool(image: Path) -> dict[str, Any]: "-ImageWidth", "-ImageHeight", "-n", # numeric output for shutter/aperture/GPS/dimensions - str(image), + image_arg, ], capture_output=True, text=True, diff --git a/tools/hooks/pre-commit-marks.sh b/tools/hooks/pre-commit-marks.sh index ce1335a..7eb3465 100755 --- a/tools/hooks/pre-commit-marks.sh +++ b/tools/hooks/pre-commit-marks.sh @@ -20,9 +20,11 @@ set -u # Newly-added .md files under content/essays/ in this commit. +# `--name-status` output is TAB-separated (statuspath); split on the +# tab so paths containing spaces survive intact. mapfile -t added < <( git diff --cached --name-status --diff-filter=A -- 'content/essays/*.md' \ - | awk '{ print $2 }' + | cut -f2- ) if [[ ${#added[@]} -eq 0 ]]; then @@ -47,8 +49,10 @@ for path in "${added[@]}"; do # Best-effort frontmatter probe: does any line in the YAML head # block start with `status:`? Avoids a YAML dependency in the # hook, which has to run before the build environment is sourced. - if awk '/^---$/{f++; next} f==1 && /^status:[[:space:]]*[^[:space:]]/{print; exit}' \ - -- "$path" \ + # Probe the STAGED blob (`git show :path`), not the working tree — + # the commit contains the index content, which may differ. + if git show ":$path" 2>/dev/null \ + | awk '/^---$/{f++; next} f==1 && /^status:[[:space:]]*[^[:space:]]/{print; exit}' \ | grep -q .; then has_status=1 fi diff --git a/tools/import-photo.sh b/tools/import-photo.sh index 45ad38b..8bcdaea 100755 --- a/tools/import-photo.sh +++ b/tools/import-photo.sh @@ -148,7 +148,14 @@ fi echo "import-photo: stripping EXIF from delivered file..." magick mogrify -strip "$TARGET" \ - || { echo "import-photo: magick mogrify -strip failed for $TARGET (EXIF NOT stripped)" >&2; exit 1; } + || { + # The copy under content/ still carries full EXIF (GPS, serial + # numbers); the Makefile's `git add content/` could auto-commit + # and publish it. Remove it before bailing out. + rm -f -- "$TARGET" + echo "import-photo: magick mogrify -strip failed for $TARGET (EXIF NOT stripped); deleted the copied target so the EXIF-laden JPEG cannot be auto-committed" >&2 + exit 1 + } # --------------------------------------------------------------------------- # Step 4: extract palette (does its own walk; idempotent on already-done photos) diff --git a/tools/preset-signing-passphrase.sh b/tools/preset-signing-passphrase.sh index 847ea04..9196505 100755 --- a/tools/preset-signing-passphrase.sh +++ b/tools/preset-signing-passphrase.sh @@ -28,7 +28,9 @@ echo -n "Signing subkey passphrase: " read -rs PASSPHRASE echo -echo -n "$PASSPHRASE" | GNUPGHOME="$GNUPGHOME" "$GPG_PRESET" --homedir "$GNUPGHOME" --preset "$KEYGRIP" +# printf, not `echo -n`: a passphrase starting with -e/-n/-E would be +# eaten as an echo option. +printf '%s' "$PASSPHRASE" | GNUPGHOME="$GNUPGHOME" "$GPG_PRESET" --homedir "$GNUPGHOME" --preset "$KEYGRIP" echo "Passphrase cached for keygrip $KEYGRIP (24 h TTL)." echo "Test: GNUPGHOME=$GNUPGHOME gpg --homedir $GNUPGHOME --batch --detach-sign --armor --output /dev/null /dev/null" diff --git a/tools/stamp-build-time.py b/tools/stamp-build-time.py index be8c9f5..c06d3eb 100755 --- a/tools/stamp-build-time.py +++ b/tools/stamp-build-time.py @@ -49,8 +49,19 @@ def stamp_file(path: str, replacement_bytes: bytes) -> bool: data, ) if count and new_data != data: - with open(path, "wb") as f: - f.write(new_data) + # Write to a sibling temp file and os.replace so an interrupt + # mid-write never leaves a truncated deployed HTML file. + tmp = path + ".stamp-tmp" + try: + with open(tmp, "wb") as f: + f.write(new_data) + os.replace(tmp, path) + except BaseException: + try: + os.unlink(tmp) + except FileNotFoundError: + pass + raise return True return False