American spelling is the default and British forms turn up in generated
prose. Catch the common endings and the irregulars, with two exceptions:
project vocabulary that the client's documents, ticket titles and codebase
all spell the British way, such as "enrolment" on APSCA, and existing code
identifiers, which are never renamed to fix spelling.
The rules after it shift by one.
The merge request conventions and the coworker list only apply to Ksense
work, but they load in every project including personal ones. Name the
folder that work lives in so an agent reads those sections as conditional
rather than as always in force.
Jira and Confluence reached Claude Code through two claude.ai connectors,
which are configured per account rather than per machine and had been added
twice. Atlassian hosts its own MCP server, so it belongs in this list with
the others and a new machine picks it up from the installer.
It carries no token. The server runs its own OAuth and Claude Code signs in
through /mcp on first use.
Claude Code and Codex start their shells without SSH_AUTH_SOCK. id_ed25519
has a passphrase, so batch ssh from a harness had no way to unlock it and
failed with "Permission denied (publickey)" against server.gib, junior.gib
and git.gbrown.org. The key and the per-host blocks were right the whole
time, which made it read as a key problem and cost several rounds of
checking keys that were never wrong.
The desktop keyring agent at $XDG_RUNTIME_DIR/keyring/ssh already holds the
key unlocked for the interactive session. A Host * block with IdentityAgent
points every host at that socket, so a shell that inherits no agent gets the
same one the terminal uses.
Dragging attachments into customfield_10142 replaces whatever text is
sitting there, so the list the skill wrote was destroyed by the upload it
was describing. Naming each artifact is already the job of the Test Cases
proof cells, which reference it by filename, and step 10 tells me which
files to upload.
Only the Story rule changes. On a Bug that field carries the test-case
table rather than prose.
Anything a person other than me reads now gets an unslop pattern pass
before it ships: MR descriptions, ticket fields, deliverables. The rules
that matter most are the ones a generic pass misses. Write as me in first
person, never name me or use a stand-in, and never flag one of my own
decisions as a deviation, because that reads as my own AI tattling on me
and costs the PM time investigating a non-issue.
Punctuation is stricter than unslop's own pattern: no em dashes, en
dashes, semicolons or arrow glyphs. Generated output is never rewritten,
only regenerated.
Also records Josh as the tech lead on apsca_next, whose dev reviews carry
draft code and schema and are a starting point rather than a finished spec.
An MCP server is a URL plus a bearer token, and the token is why this is a
stage rather than a manifest line. Panama is public, so the tokens cannot
live in it, and neither runtime keeps its server list in a file worth
symlinking: Codex writes them into config.toml beside dozens of unrelated
settings, and Claude Code into ~/.claude.json. link-mcp registers them
through the runtime's own CLI instead.
user/agents/mcp/servers is tracked and names which variable carries each
token. user/agents/mcp/env holds the tokens and is ignored. A new machine
gets the servers by dropping its own env file beside the tracked one.
Only Claude Code is handled, and only where the interview enabled personal
content. Rewriting a section of somebody's live Codex TOML is a worse
failure mode than leaving two lines to paste once.
The scan was keyed off this item becoming visible, but on a machine with no
backlight `visible` stays false until a scan has already found a DDC
display, so it never scanned at all. It now keys off the quick settings
panel opening, which still keeps the second-long I2C probe off shell
startup.
This panel's DP link is marginal. 4500x3000@60 at 10bpc is around 24 Gbps,
right at the edge of DP 1.4 HBR3 and reliant on DSC, so every modeset
retrains the link and blanks the screen. 8bpc keeps headroom.
Steam games are the other trigger. Everything Hyprland does only for a real
fullscreen window (direct scanout, VRR, tearing, auto HDR) forces that
retrain, so steam_app windows get fullscreen_state "1 2": maximized
internally while the game believes it is fullscreen, which is what
borderless windowed looks like from the game's side.
Recorded alongside the related Panama settings already at 0,
directScanoutPolicy and vrrPolicy.
The picker paints an opaque frozen frame edge to edge, so the blur and dim
it inherited from the shared qs-overlay rule were compositor work on pixels
nobody sees. It now has its own layer rule.
no_anim is the load-bearing part. Capture.qml unmaps the picker and runs
grim 90ms later, and the default 200ms layersOut fade put the half-faded
picker, dimming and all, into every screenshot.
Quickshell's exclusiveZone setter flips exclusionMode back to Normal as a
side effect, so a window declaring both is at the mercy of which property
the QML engine applies last. The 2026-09-14 Qt update changed that order
and every full-screen overlay slid under the bar.
Each overlay now declares ExclusionMode.Ignore alone. The capture picker
was the visible failure: it started under the bar, the full-output freeze
frame was squeezed into a shorter box, and every selection landed one
bar-height off in the real capture.
tests/quickshell/exclusion-idiom-contract fails any file that pairs them.
eza 0.23.5 gives --icons an optional WHEN value, so a trailing --icons
swallowed the path argument and `ls docs` failed with "invalid value
'docs' for '--icons'". Using --icons=auto makes both aliases independent
of flag order.
Claude-Session: https://claude.ai/code/session_017trjCxkXTdv7Z5ePfGWYtW
The repository audit made any Terra that is not Panama's own pinned form a
trust-root failure, and status 78 then stopped every stage before it ran. A
machine that installed Terra the way Terra documents it -- terra-release's own
repo file, a metalink, the key at its stock path -- was classified hostile and
had no way back, because install_terra_repository refused to touch a machine
terra-release had already reached. A gate with no door.
The trust root is the signing key, and that key is byte-for-byte the
fingerprint this repository reviewed and pinned, with every signature check
already on. So verify the fingerprint and adopt the configuration into the
pinned form instead of refusing it. Adoption needs no network and no DNF, it
runs before any other transaction in the stage, and it is repeatable, which it
has to be: terra-release owns that file and restores it on update.
Adoption stays narrow. The pinned fingerprint must match both the reviewed key
and the key the machine actually verifies against, the gpgkey must be a local
file under the system trust directory, and the endpoint must be one Terra
itself serves -- so the reviewed baseurl or the reviewed metalink host, now
pinned as TERRA_METALINK_BASEURL. An unknown key, a redirected baseurl, a
second enabled Terra, or a disabled signature check is still a hard refusal.
A refusal also stops less than it did. It suppresses the stages that open DNF
and the migrations, which may run a transaction of their own, and the run still
exits 78. It no longer stops link-dotfiles, link-skills or link-user, which
read no repository and install no package. Exiting before them is what left
this laptop with a stale ~/.claude/skills and no shipped skill reachable.
Also stub ensure_flathub_remote in the extras contract, which has been failing
since that call was added to install_extra_category without one.
Claude-Session: https://claude.ai/code/session_01PeTrG9dGY89UWuhGm4Pr1s
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
.bashrc is back to its one job: export the Panama paths and source
what it finds. The personal-env block moves into config/bash/shell —
first, because the tmux guard below it reads that file — and the
cargo source that was duplicated between the two files lives only in
shell now.
The real fix is behind that tidying: both Home Assistant helpers
defaulted to the IN-REPO config/bash/env, and the writer rebuilt it
with only its own three lines — which read as "my env vars vanished"
to the person who thought that file was hand-maintained. Both now
prefer ~/.config/panama/env, the migrated home outside the checkout,
with the repo path kept only as a read fallback for unmigrated
machines. The stale legacy copy on this machine is retired; the
working credentials were merged into the migrated file first, after
probing both sets against the live Home Assistant.
Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8