Tooling, manifest, and content polish

- import-photo.sh deletes the copied JPEG when EXIF stripping fails, so
  the auto-commit can never publish GPS/serial metadata (AUDIT §4.11)
- pre-commit-marks hook: tab-aware path parsing, probes the staged blob
  rather than the working tree (§4.11)
- preset-signing-passphrase uses printf; stamp-build-time writes via
  temp + os.replace; archive.py passes -- to pdftotext and verifies the
  vendored monolith binary against its recorded sha256 (mismatch is
  fatal, consistent with the tool's integrity contract); extract-exif
  ./-prefixes relative paths (§4.11)
- blog-post.html: id="similar-links"/"backlinks" each appear once;
  rendered output unchanged (§6.4)
- site.webmanifest: start_url/scope/description added, maskable icon
  purpose restored alongside any (§9.3)
- Frontmatter cleanup: scaffold comments out of scaling_outage,
  dangling null confidence-history keys removed (populated ones kept),
  dead modified: key dropped from colophon (§6.4)
- canto31.jpg: 4.0 MB -> 1.9 MB (2400px, q80, grayscale — the source
  is a monochrome Doré engraving, so single-channel is colorimetrically
  lossless); webp sidecar regenerated (§6.4, prior-audit §6.1)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Levi Neuwirth 2026-06-10 11:13:34 -04:00
parent 56afdb867a
commit 9f61ce5949
15 changed files with 127 additions and 37 deletions

View File

@ -1,7 +1,6 @@
---
title: Colophon
date: 2026-03-21
modified: 2026-04-27
status: "Durable"
confidence: 93
tags: [meta]

View File

@ -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/).

View File

@ -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/).

View File

@ -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 # 0100 integer (%)
importance: 3 # 15 integer (rendered as filled/empty dots ●●●○○)
evidence: 1 # 15 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.

View File

@ -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.

View File

@ -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/).

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 MiB

After

Width:  |  Height:  |  Size: 1.8 MiB

View File

@ -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",

View File

@ -17,24 +17,27 @@
$body$
$if(backlinks)$
<footer class="page-meta-footer">
$else$
$if(similar-links)$
<footer class="page-meta-footer">
$endif$
$endif$
$if(backlinks)$
<div class="meta-footer-full meta-footer-backlinks" id="backlinks">
<h3>Backlinks</h3>
$backlinks$
</div>
$endif$
$if(similar-links)$
<div class="meta-footer-full meta-footer-similar" id="similar-links">
<h3>Related</h3>
$similar-links$
</div>
$endif$
$if(backlinks)$
</footer>
$else$
$if(similar-links)$
<footer class="page-meta-footer">
<div class="meta-footer-full meta-footer-similar" id="similar-links">
<h3>Related</h3>
$similar-links$
</div>
</footer>
$endif$
$endif$

View File

@ -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 = <hex>` 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 <meta name=robots ... noarchive> —
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")

View File

@ -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,

View File

@ -20,9 +20,11 @@
set -u
# Newly-added .md files under content/essays/ in this commit.
# `--name-status` output is TAB-separated (status<TAB>path); 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

View File

@ -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)

View File

@ -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"

View File

@ -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:
# 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