OpenAI ships an official Linux RPM now, so the community wrapper goes away:
`panama app chatgpt-desktop` built codex-desktop from the upstream macOS disk
image and ran a local rebuild daemon to keep it current, and the official
package comes from a repository that upgrades with everything else. The app
file, the help example and the dock's pinned id all move over, and a migration
replaces the build on machines that already have it -- official package on
before the community one comes off, so a failure part-way still leaves an app.
The install itself does not follow upstream's instructions. Those are "download
this RPM and install it", and the RPM's own root scriptlet is what writes the
repository file and drops the signing key into /etc/pki/rpm-gpg -- so root runs
an unverified download and then learns from it what to trust. That is the shape
the repository audit forbids: no network response is executed as root without a
verified digest or signature first.
OpenAI publishes no key and no fingerprint anywhere an install could fetch and
check them, so the key is pinned here instead. setup/keys/ carries it and says
where it came from, including the honest part -- this is trust established on
first use and then held, not trust verified against the publisher. setup/lib/
chatgpt-package verifies that copy's fingerprint, installs it, and writes the
repository with gpgcheck and repo_gpgcheck on before anything is installed, so
dnf checks the metadata signature and the package signature itself. It is byte
for byte the repository the scriptlet would have written, so nothing churns
afterwards, and every later upgrade goes through the same key. Both callers use
it; a verification failure skips ChatGPT rather than installing it anyway.
The contract proves the pinned key is the key the library names, that a
missing, unreadable or mismatched key writes nothing at all, that what is
written actually turns the checks on, and that neither caller hands root a
downloaded RPM.
Claude-Session: https://claude.ai/code/session_017zzbtfnMLoYrB8WesqANFY
The manifest is written in byte order, but both the runner and the manifest
contract discovered contracts with a bare `sort` and compared them with bash's
`<` -- and both of those follow LC_COLLATE. Under en_US.UTF-8 the collation
folds punctuation away, so `calendar_agenda_bridge_test.py` sorts before
`calendar-agenda-helper-contract` instead of after it, and eight pairs that
differ only by `-` against `_` come back out of order.
The effect was that `tests/setup/contract-manifest-contract` failed on this
machine, and `panama test` refused to run at all, with eight identical "paths
are not lexicographically sorted" findings and nothing naming which paths. A
gate whose answer depends on the machine's LANG is not a gate, so the sort and
the comparison are both pinned to byte order. LC_ALL rather than LC_COLLATE,
because an exported LC_ALL outranks it and would have put the bug back.
Claude-Session: https://claude.ai/code/session_017zzbtfnMLoYrB8WesqANFY
CLAUDE_CONFIG_DIR points the CLI at a different config home, so `klaude`
runs Claude Code against ~/.klaude -- its own settings, auth and history --
without disturbing the default one this machine already uses.
Claude-Session: https://claude.ai/code/session_017zzbtfnMLoYrB8WesqANFY
Popover's container is a plain Item and never measures its children, so
the three-dots menu on a notification card opened as a 240x28 sliver
with its one row clipped out of existence. TrayMenu and AgentUsagePanel
both carry the antidote — implicit sizes on the Popover itself, taken
from the content column — and now the notification settings menu does
too, with rows filling the window width so the hover reaches the edges.
Claude-Session: https://claude.ai/code/session_01W8icivxZcmFTR2g6DkYhjT
Third recurrence of the stale-mount bug: the ExecStartPre cleanup ran on
clean ground and the portal still came up mountless, sitting active while
every fresh flatpak sandbox failed to build. Add an ExecStartPost that
polls for the fuse mount and fails the start after five seconds without
one, so a mountless portal shows up failed in the journal and the next
bus activation retries from the cleanup instead of lingering broken.
Claude-Session: https://claude.ai/code/session_01CTL7G5u3iDf1NCM6rTyCES
A machine's role is now the interview's first question and the one answer
Panama records. Servers get the same shell minus the screen: core packages,
nvm, Bun, Claude Code and Codex (desktops get Codex too), linger, rootless
ports from 80, firewalld, the nginx-bridge network, and a nightly image
updater that replaced watchtower for cause.
server/containers/ carries junior's 23 compose services -- secrets moved to
per-machine .env files that never enter this public repo, every transformed
compose proven to render byte-identical to what is live. 'panama server'
enables, disables and relinks them; nothing here restarts a running service.
'boot --server' walks a fresh VPS from its root login to a normal install.
Five new contracts pin the secrets rule, the catalog's shape, panama-server's
behavior, the role plumbing, and the dotfile classification.
Claude-Session: https://claude.ai/code/session_01NU5JGiN3JfzqrLQB6wmJ1E
All three already delegated in places — ticket calls diagnosing-bugs and
run, review-ticket calls codebase-design — so this extends a habit rather
than starting one.
unslop is the load-bearing part, at four points: create-ticket before the
Jira write, review-ticket's check list where a bare punctuation sweep
stood, and ticket on both mr.md and the Jira fields. Every one is scoped
to pattern detection and skips the "Adding soul" section, which is blog
voice and would fight the rigid house formats. The mr.md hook exempts the
pasted pre-MR handoff, generated output the skill already forbids
editing, and the field hook exempts proof cells. Conrad and Henry read
all of this, and prose that reads as machine generated costs the work its
credibility before anyone weighs what it says.
grilling comes in as a classification gate rather than blanket ceremony.
ticket now names the work bounded or architectural before drafting
plan.md and escalates only on architectural, where a wrong assumption is
expensive to find in Phase 2; bounded stories go straight through as
before. create-ticket escalates only when the epic docs have not settled
scope, which is where invented scope gets in.
tdd is gated the same way: test-first for procedures, derivations,
permission checks, migrations and state machines, and explicitly not for
UI wiring or copy changes, where it would produce the smoke-test padding
House style rejects. That closes a loop already half built, since
review-ticket proposes automated-test subtasks — those subtasks now name
the behavior to demonstrate, and ticket writes them first.
codebase-design gets the same call in ticket's planning step that
review-ticket already makes.
No steps were renumbered, so ticket's Phase 2 cross-references still
resolve.
Claude-Session: https://claude.ai/code/session_01DFgQVLdzLwu56gU8m4gvCS