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 title: Colophon
date: 2026-03-21 date: 2026-03-21
modified: 2026-04-27
status: "Durable" status: "Durable"
confidence: 93 confidence: 93
tags: [meta] tags: [meta]

View File

@ -13,7 +13,6 @@ importance: 1
scope: personal scope: personal
novelty: conventional novelty: conventional
practicality: moderate practicality: moderate
confidence-history:
--- ---
A fuller write-up follows. In the meantime, see the [projects index](/cv/projects/). A fuller write-up follows. In the meantime, see the [projects index](/cv/projects/).

View File

@ -18,7 +18,6 @@ evidence: 4
scope: broad scope: broad
novelty: innovative novelty: innovative
practicality: high practicality: high
confidence-history:
--- ---
A fuller write-up follows with the clinical-implications manuscript. In the meantime, see the [projects index](/cv/projects/). 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" title: "Speculative Reluctance"
date: 2026-04-15 # required; used for ordering, feed, and display date: 2026-04-15
abstract: > # optional; shown in the metadata block and link previews 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. 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 - ai
- tech - tech
- speculative - speculative
- open - open
status: "Draft"
# Epistemic profile — all optional; the entire section is hidden unless `status` is set confidence: 55
status: "Draft" # Draft | Working model | Durable | Refined | Superseded | Deprecated importance: 3
confidence: 55 # 0100 integer (%) evidence: 1
importance: 3 # 15 integer (rendered as filled/empty dots ●●●○○) scope: broad
evidence: 1 # 15 integer (same) novelty: moderate
scope: broad # personal | local | average | broad | civilizational practicality: high
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
--- ---
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. 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 scope: civilizational
novelty: innovative novelty: innovative
practicality: moderate 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. 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 scope: local
novelty: moderate novelty: moderate
practicality: low practicality: low
confidence-history:
--- ---
A fuller write-up follows. In the meantime, see the [projects index](/cv/projects/). 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", "name": "Levi Neuwirth",
"short_name": "ln", "short_name": "ln",
"description": "Personal site of Levi Neuwirth — essays, research, music, and photography.",
"start_url": "/",
"scope": "/",
"icons": [ "icons": [
{ {
"src": "/web-app-manifest-192x192.png", "src": "/web-app-manifest-192x192.png",
@ -8,11 +11,23 @@
"type": "image/png", "type": "image/png",
"purpose": "any" "purpose": "any"
}, },
{
"src": "/web-app-manifest-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{ {
"src": "/web-app-manifest-512x512.png", "src": "/web-app-manifest-512x512.png",
"sizes": "512x512", "sizes": "512x512",
"type": "image/png", "type": "image/png",
"purpose": "any" "purpose": "any"
},
{
"src": "/web-app-manifest-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
} }
], ],
"theme_color": "#16140f", "theme_color": "#16140f",

View File

@ -17,25 +17,28 @@
$body$ $body$
$if(backlinks)$ $if(backlinks)$
<footer class="page-meta-footer"> <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"> <div class="meta-footer-full meta-footer-backlinks" id="backlinks">
<h3>Backlinks</h3> <h3>Backlinks</h3>
$backlinks$ $backlinks$
</div> </div>
$if(similar-links)$ $endif$
$if(similar-links)$
<div class="meta-footer-full meta-footer-similar" id="similar-links"> <div class="meta-footer-full meta-footer-similar" id="similar-links">
<h3>Related</h3> <h3>Related</h3>
$similar-links$ $similar-links$
</div> </div>
$endif$ $endif$
$if(backlinks)$
</footer> </footer>
$else$ $else$
$if(similar-links)$ $if(similar-links)$
<footer class="page-meta-footer"> </footer>
<div class="meta-footer-full meta-footer-similar" id="similar-links"> $endif$
<h3>Related</h3>
$similar-links$
</div>
</footer>
$endif$
$endif$ $endif$
</main> </main>

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 """Extract plain text from `pdf` into `txt` via pdftotext. On any
failure an empty file is written so downstream steps still find it.""" failure an empty file is written so downstream steps still find it."""
try: 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: except (subprocess.CalledProcessError, FileNotFoundError) as exc:
err(f"{pdf.name}: pdftotext failed ({exc}); writing empty text sidecar") err(f"{pdf.name}: pdftotext failed ({exc}); writing empty text sidecar")
txt.write_text("", encoding="utf-8") txt.write_text("", encoding="utf-8")
@ -292,6 +295,51 @@ def find_monolith() -> str | None:
return shutil.which("monolith") 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: def body_noarchive(path: Path) -> bool:
"""True if the snapshot declares <meta name=robots ... noarchive> — """True if the snapshot declares <meta name=robots ... noarchive> —
the in-document equivalent of the X-Robots-Tag header.""" 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"tools/bin/monolith (see tools/monolith-version.txt) or set "
f"$MONOLITH_BIN; HTML snapshot skipped") f"$MONOLITH_BIN; HTML snapshot skipped")
return False return False
verify_monolith(mono)
source = dest.with_suffix(dest.suffix + ".source.part") source = dest.with_suffix(dest.suffix + ".source.part")
tmp = dest.with_suffix(dest.suffix + ".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 from __future__ import annotations
import json import json
import os
import shutil import shutil
import subprocess import subprocess
import sys 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 entry. Numeric values come through as numbers; text values as
strings. We accept missing keys silently. 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( result = subprocess.run(
[ [
"exiftool", "exiftool",
@ -156,7 +163,7 @@ def _read_exif_via_exiftool(image: Path) -> dict[str, Any]:
"-ImageWidth", "-ImageWidth",
"-ImageHeight", "-ImageHeight",
"-n", # numeric output for shutter/aperture/GPS/dimensions "-n", # numeric output for shutter/aperture/GPS/dimensions
str(image), image_arg,
], ],
capture_output=True, capture_output=True,
text=True, text=True,

View File

@ -20,9 +20,11 @@
set -u set -u
# Newly-added .md files under content/essays/ in this commit. # 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 < <( mapfile -t added < <(
git diff --cached --name-status --diff-filter=A -- 'content/essays/*.md' \ git diff --cached --name-status --diff-filter=A -- 'content/essays/*.md' \
| awk '{ print $2 }' | cut -f2-
) )
if [[ ${#added[@]} -eq 0 ]]; then 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 # Best-effort frontmatter probe: does any line in the YAML head
# block start with `status:`? Avoids a YAML dependency in the # block start with `status:`? Avoids a YAML dependency in the
# hook, which has to run before the build environment is sourced. # hook, which has to run before the build environment is sourced.
if awk '/^---$/{f++; next} f==1 && /^status:[[:space:]]*[^[:space:]]/{print; exit}' \ # Probe the STAGED blob (`git show :path`), not the working tree —
-- "$path" \ # 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 | grep -q .; then
has_status=1 has_status=1
fi fi

View File

@ -148,7 +148,14 @@ fi
echo "import-photo: stripping EXIF from delivered file..." echo "import-photo: stripping EXIF from delivered file..."
magick mogrify -strip "$TARGET" \ 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) # 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 read -rs PASSPHRASE
echo 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 "Passphrase cached for keygrip $KEYGRIP (24 h TTL)."
echo "Test: GNUPGHOME=$GNUPGHOME gpg --homedir $GNUPGHOME --batch --detach-sign --armor --output /dev/null /dev/null" 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, data,
) )
if count and new_data != 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
f.write(new_data) # 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 True
return False return False