CLAUDE.md · git:20261107.d1f4f61 · 2026-11-07 · sha256 733ae7f23d3c4f3f

CLAUDE.md git:20261107.d1f4f61A

Immutable. This exact content never changes and is served at /api/v1/blob/733ae7f23d3c4f3f.

# CLAUDE.md

Working notes for AI agents contributing to this repository.

## How to find things

Search the leading keywords, then open the named file or section. Code is
authoritative when prose and source disagree. Section links point inside this
file; each row is self-contained so a retrieved fragment is enough to act on.

- **Build, test, lint, race, package, `make gate`, `make site-check`** →
  [Build, test, ship](#build-test-ship); commands live in `Makefile` and
  `.github/workflows/gate.yml`.
- **CI checks, PR merge, `gh pr merge`, release tag, commit author, CLA** →
  [Repository gates, PRs and releases](#repository-gates-prs-and-releases);
  `gate.yml`, `installers.yml`, `security.yml`, `release.yml`, `pages.yml`
  in `.github/workflows/`.
- **AGPL, commercial license, SPDX header, `CLA.md`, `TERMS.md`** →
  [Licensing](#licensing); `LICENSE`, `CLA.md`, `TERMS.md`.
- **Duplicated identifiers, Windows scheduled task, PATH marker, launchd,
  systemd, `TermsVersionCurrent`, `ProgressKind`, `EvUserMsg`,
  `consumed_at`, `EffectiveBaseURL`, `go.mod` toolchain** →
  [Cross-artifact contracts](#cross-artifact-contracts).
- **Add or change a `panda` subcommand, `panda help`, shell completion** →
  `cmd/panda/dispatch.go` and `internal/nodecli/` (the command catalog);
  a test fails a switch case that has no catalog row.
- **Adapter, agent CLI, model preference, `_harness.py` wire protocol** →
  `adapters/_harness.py`, `adapters/*.py`,
  `tests/adapter_contract_test.py`, `docs/agent-execution.md`.
- **TUI keys, mouse, `/queue`, `/inject`, re-authorization card** →
  `cmd/panda/tui_update.go`, `cmd/panda/tui_turn.go`,
  `scripts/tui-*-pty-check.py`; the cross-artifact contracts name both
  slash-command dispatch points.
- **Config keys, per-agent models, concurrency, hot reload** →
  `internal/config/`, `internal/configsync/`,
  `internal/core/execution_config.go`,
  `webui/app/src/views/execution-settings.tsx`, `docs/agent-execution.md`.
- **Database schema, migration, `ask_rounds`, `user_version`** →
  `internal/storage/migrations.go`, `internal/core/rounds.go`.
- **UI strings, five locales, raw key rendered instead of text** →
  `internal/i18n/`, `webui/app/src/i18n/` (`parity.test.ts`),
  `site/js/i18n.js` for the marketing site.
- **Marketing site page, nav, footer, canonical, sitemap, JSON-LD,
  `data-i18n`, escaped CLI placeholders, image credit, `site/media`** →
  [The marketing site](#the-marketing-site-site), `site/`,
  `scripts/check-site.py`, `make site-check`.
- **Installer, auto-start, Windows task name, PATH block** →
  `scripts/install.sh`, `scripts/install.ps1`, `internal/install/`,
  `docs/install.md`.
- **SSE trace events, decision orbit, `steer` acknowledgment** →
  `webui/panel/events.go`, `webui/panel/panel.go`,
  `webui/panel/sessions.go`,
  `webui/app/src/components/event-parser.ts`,
  `webui/app/src/views/sessions.tsx`.
- **Memory graph, graph layout, label overlap** →
  `webui/panel/graph.go`, `webui/app/src/components/graph-layout.ts`,
  `webui/app/src/components/memory-graph.tsx`.
- **"Model configured but nothing runs", injection rejected, endpoint** →
  `internal/commander/inject.go` (`EffectiveBaseURL`), `internal/doctor/`.
- **Localized docs, `CHANGELOG.md`, version examples, release list** →
  `docs/README.md`, `scripts/check-docs.sh`, root `*.md` locale twins,
  `site/changelog.html`, `site/download.html`.
- **Secrets, redaction, sandbox, network allowlist** → `internal/security/`.
- **Repository directory map** → [Layout](#layout) and
  [internal/ by concern](#internal-by-concern).

## What this is

OpenPanda (Open Personal Adaptive Node-based Distributed Assistant) is a
peer-to-peer orchestrator that runs on heterogeneous machines and farms
work out to whichever node is best suited. It is not itself an agent —
it coordinates the agent CLIs already on each device (Claude Code,
Codex, and friends) plus direct shell execution and human-approved
tasks.

Requires Go 1.26.10+ (the patch level is a security floor: 1.26.10 is
where GO-2026-6809, reachable through crypto/x509 in the bus TLS path, is
fixed). The build deliberately avoids CGO: even SQLite comes from
`modernc.org/sqlite`, so cross-compilation is just `GOOS/GOARCH`.

## Licensing

Dual-licensed: **AGPL-3.0-or-later** (see `LICENSE`) for open-source use,
plus a commercial license for proprietary embedding/hosting. All external
contributions are governed by `CLA.md` — new PRs require CLA assent
before merge. Use of the software is additionally governed by
`TERMS.md`/`TERMS.zh-CN.md` (warranty disclaimer, liability cap,
indemnity — wired in via AGPL §7 additional terms). Keep new files
consistent with the existing
`// SPDX-License-Identifier: AGPL-3.0-or-later` headers.

## Build, test, ship

Commands for native builds, the unit/integration suite, lint, the race
detector, TUI PTY checks, site validation, cross-compilation and release
packaging. Everything goes through the Makefile:

```bash
make build        # native binary (fmt-check + vet run first as prerequisites)
make test         # unit/integration suite (adapter-test runs first as a prerequisite)
make race         # race detector, whole tree (pre-release superset check)
make race-focused # race detector on concurrency-heavy packages only:
                  # core/commander/bus/storage/askengine/scheduler/webui-panel
make vet          # go vet
make fmt / fmt-check
make gate         # pre-merge bar: fmt-check + vet + staticcheck + vuln +
                  # build + adapter-test + test + race-focused
make gate-all     # gate + web-gate + the four TUI PTY tests (CI's local equivalent)
make staticcheck  # honnef.co/go/tools/cmd/staticcheck across ./...
make vuln         # govulncheck over the reachable import graph
make adapter-test # adapter wire contract: fake CLIs on PATH assert argv/env/
                  # progress/timeout (python3 tests/adapter_contract_test.py)
make site-check   # static invariants for site/ (i18n keys, anchors, assets,
                  # JSON-LD, version, canonical host); also a Pages pre-publish step
make tui-pty-test / tui-pty-exit-test
                  # real-pty TUI contracts: mouse ownership; exit note + failed-turn
                  # persistence (scripts/tui-*-pty-check.py, macOS/Linux only)
make tui-pty-side-test / tui-pty-sidebar-test
                  # side-channel and sidebar PTY contracts (macOS/Linux only)
make bench        # routing/codec/dedup microbenchmarks
make dev          # build + open the web console against config.yaml (loopback, auto token)
make run          # run daemon from source
make run-local    # daemon + webui sidecar via scripts/run-local.sh
make install-local
                  # sync a fresh build over ~/.local/bin and the release prefix
make measure      # steady-state RSS measurement
make icons        # regenerate the PWA icon set (scripts/genicons)
make clean        # rm -rf bin dist

# release machinery
make build-<os>-<arch>   # darwin/linux/windows x amd64/arm64
make build-lite[-linux-<arch>]
                         # reduced-footprint builds for small devices
                         # (linux amd64/arm64/armv7)
make build-webui         # webui/cmd/panel sidecar binary (runs `make web` first)
make package             # release archives into dist/
make release / release-local

# single test
go test -run TestName ./internal/<pkg>/...
```

The web console needs node/npm only when rebuilding it (`make web`,
`make web-test`; `make web-gate` runs the whole leg as CI does — one
install, then typecheck + unit tests + build). The Go binary embeds a
prebuilt copy; only a placeholder `dist/index.html` is committed.

## Layout

Directory map: CLI, runtime packages, adapters, console, marketing site,
installers, fixtures and gitignored runtime state.
[internal/ by concern](#internal-by-concern) below groups the `internal/`
packages by subsystem.

- `cmd/panda/` — the `panda` CLI. Bare invocation drops into the TUI
  REPL; subcommands cover `daemon`/`serve`, `ask`, `repl`/`chat`, `web`,
  `service` (`install`/`status`/`restart`/`uninstall` for the console's
  user-level auto-start unit), `voice`, `install`/`uninstall`, `update`/`upgrade` (self-updater:
  `update check|apply`), `doctor`, `status`, `nodes`, `pair`, `queue`,
  `task` (incl. `priority`/`move`/`artifact`/`board`/`send`
  — queue vs `--inject` live notes to a task), `plan`
  (`run`/`show`/`example`), `cancel`, `approve`/`reject`, `logs`,
  `skill` (`list`/`find`/`hub`/`add`/`reset`), `reminder`, `todo`
  (`list`/`add`/`edit`/`done`/`rm`/`move`/`start` — the human board;
  `start` enqueues a task), `detect`,
  `card` (`show`/`rescan`/`edit`/`set` plus structured
  `native|agent|manual` edits), `init`, `metrics`, `heatmap`, `audit`
  (`verify`/`entries`), `session`/`sessions` (`list`/`show`/`rm` plus
  `fork`/`tree` for thread branching), `memory`, `model`/`models`
  (multi-model registry + active-model switcher), `config` (sections
  `model`/`mcp`/`limits`/`routing`/`injection`/`approval`/`delegation`/
  `self_evolution`, comments preserved; `routing set` also edits the
  per-agent model and concurrency maps — see `docs/agent-execution.md`),
  `agents`, `project`, `mcp` (exposes
  the node's self-tools as an MCP stdio server), `rpc` (NDJSON-over-stdio
  embedding surface, experimental), `auth` (subscription OAuth,
  experimental), `self` (`status`/`queue`/`doctor`/`mint`/`propose`/
  `rebase`/`rollback`/`reset` — the opt-in self-evolution patch-set
  pipeline), `read` (markdown viewer; aliases
  `view`/`cat`/`md`/`markdown`), `version`,
  `completion` (bash/zsh/fish script generation). Global flags:
  `--config`, `--card`, `--mcp`, `--json`.
- `internal/` — all runtime code (see below).
- `adapters/` — Python scripts, one per agent CLI (claude_code, codex,
  opencode, generic, hermes, pi, antigravity, deepseek, grok, openclaw…),
  plus `_harness.py` (the shared stdin/stdout JSON protocol they all
  speak). Installed next to the binary; pure stdlib, no pip deps.
- `extensions/voice/` — the voice extension scripts (`stt.py`, `tts.py`,
  `wake.py`) shipped alongside the adapters, consumed by `panda voice`.
- `webui/app/` — Preact console (vite build, embedded via go:embed into
  `webui/panel/`; `webui/cmd/panel/` is the sidecar binary,
  `webui/push/` the Web Push/VAPID service). `views/execution-settings.tsx`
  edits per-agent models and concurrency; `components/graph-layout.ts` and
  `memory-graph.tsx` render the memory graph.
- `config.example.yaml` / `config.example.local.yaml` (repo root) —
  config schema samples. `config/` holds the committed
  `capabilities.example-*.yaml` node-card samples; personal
  `capabilities.*.yaml` and `config*.yaml` are gitignored.
- `testdata/` — `node-{a,b,c}.yaml`, `card-{a,b}.yaml`,
  `deploy-opi.yaml` and `scenarios/` fixtures; `run/`, `live/`,
  `artifacts/` and the per-node state dirs are gitignored runtime output.
- `deploy/` — packaging/deploy manifests (homebrew, macos, windows).
- `drivers/` — the reference actuator drivers (`panda-*` scripts the
  card advertises as `hardware:*` abilities — servo, mic, camera,
  notify, MCU, screen capture, GUI input; `.ps1` variants for Windows).
- `scripts/` — installers (`install.sh`/`install.ps1`), release
  packaging/signing/verify and Homebrew-tap tooling, PTY regression
  checks (`tui-*`, `watcher-*`), deploy/lab helpers, `panda_rpc.py`,
  `check-docs.sh`.
- `skills/global/` — the bundled global SKILL.md library.
- `docs/` — user guides and the public protocol spec;
  `docs/README.md` indexes the set (`agent-execution.md` in five languages,
  plus `status.md`, `install.md`, `faq.md`, `protocol.md` and their
  `.en.md` twins).
- Runtime dirs (`data/`, `memory/`, `plans/`, `projects/`, `tasks/`,
  `MEMORY.md`/`USER.md`) appear at the repo root when a dev node runs
  with `work_path="."` — gitignored node state, not source. `author/`
  and `docs/author/` are local-only for the same reason. `site/` is
  not: it is tracked and deployed by `.github/workflows/pages.yml` (see
  [The marketing site](#the-marketing-site-site)); `site/media/` holds the
  real console captures and demo recording shared with the README demo blocks.

### `internal/` by concern

- **Node & task lifecycle**: `core` (the `Core` daemon — wires store,
  transport, execution loop, delegation/retry/judge, metrics, and the
  inter-task message board in `messages.go`: slot-based sibling notes
  under `plan:<id>` / `task:<parent>` scopes; `rounds.go` persists ask
  rounds, `recovery.go` assesses whether a failed result can be replayed,
  `execution_config.go` applies live execution config), `entry`
  (classifies input into answer/tool_call/task/plan, builds prompts,
  streams output), `scheduler` (queue, node scoring `score.go`, routing
  `route.go`, multi-hop `chain.go`), `commander` (three execution tiers:
  `native` shell via `executil`, `agent` via adapters, `manual` approval
  queue; `inject.go` enriches agent context, `concurrency.go` bounds
  per-agent parallelism).
- **Wire & discovery**: `bus` (WS + sealed-UDP transports, negotiated
  per-session AEAD and binary data frames, HMAC-membership +
  Ed25519-identity hello auth, LAN beacons, DTN/punch — see
  `docs/protocol.md`), `ledger` (capability cards — what each node
  advertises), `nodeidentity`, `carddetect`, `cardmut`.
- **Safety**: `defense` (permission tiers, circuit breaker, scope-drift
  and loop detection, snapshots), `security` (sandbox, net allowlists,
  secret redaction, audit log), `guard`, `trust` (per-peer trust zones —
  mesh/guarded/open levels asserted locally on the peer's Ed25519
  identity — clamping what an outbound delegate may expose and how an
  inbound one is admitted; screen-capture tasks and their output are
  mesh-only).
- **Memory & skills**: `memory` (USER.md/MEMORY.md layers, isolation
  wall, daily logs, Dreaming consolidation engine, context injector),
  `skills` (SKILL.md progressive loading + self-evolution).
- **Data & platform**: `storage` (SQLite WAL + migrations; schema 67 adds
  the `ask_rounds` table), `ctxstore`,
  `sessions`, `projects`, `config` (plus `execution.go` for per-agent
  model and concurrency defaults), `configsync` (polls the shared config
  and database so a model change in one process reaches the others —
  works on Windows and across atomic file replacement), `log`, `util`
  (UUIDv7 etc.), `version`, `hwinfo`, `install`, `i18n` (EN/ZH/JA/ES/DE),
  `reminders`,
  `nodecli` (the `panda` command registry: the agent prompt reference, the
  `panda help` page's rows and order, and the completion vocabulary all
  come from here; a test parses `cmd/panda/dispatch.go` and fails a switch
  case without a catalog row),
  `updater`, `doctor`, `cliui`, `providers`, `pyexec`, `askengine`
  (hot-swaps the active model without restarting a turn),
  `artifact`, `mdtext`, `mcp`/`mcpserve`, `agents`, `plan`, `auth`
  (subscription-OAuth token store + PKCE flows), `self` (self-evolution:
  signed patch-set manifests/queue/worktree/archive, release-gate runner,
  staged swap + `panda.release` rollback target).

## Repository gates, PRs and releases

Read this before merging a PR, pushing `main`, cutting a `v*` tag, or
choosing a commit author. It records the branch ruleset, which workflow runs
on which event, the release order and the identity CLA checks.

- `main` is protected by a GitHub ruleset: deletion and force-push are
  disabled, and every change lands through a PR with all required checks
  green. Merge with `gh pr merge <n> --rebase`; the project does not keep
  merge commits.
- Workflow triggers are not interchangeable, so a green PR is not proof that
  every leg ran: `gate.yml` runs on PRs, pushes to `main` and manual dispatch;
  `installers.yml` only on pushes to `main` (skipping Markdown and `docs/**`);
  `security.yml` weekly and on demand; `release.yml` on `v*` tags; `pages.yml`
  on pushes to `main` touching `site/**`, `CHANGELOG.md` or
  `scripts/check-site.py`, validating the site before it uploads the artifact.
  A PR cannot surface a `lite-linux-*` packaging failure — the `lint` job
  compiles that variant with `go build -tags lite ./...` to close the gap.
- A release is ordered: push `main` → poll the Gate for that exact SHA until
  it succeeds → then push the `v*` tag. Tagging first publishes a build the
  gate never examined.
- Local and PR commits are authored as `Xustalis <fing2024@outlook.com>`;
  CLA assent is checked against the commit author. The
  `OpenPanda <openpanda@localhost>` identity exists only for git calls made
  *inside repository code* on CI runners with no configured identity — never
  use it for a local commit.

## Cross-artifact contracts

Read this before editing an installer identifier, an auto-start unit, a
terms-version fixture, a progress kind, a trace event, a user-message
delivery path, the `go` directive or a doctor check. These values are
duplicated deliberately; changing one copy alone breaks a runtime path that
a unit test may not cover.

- `OpenPandaNode` — the Windows logon task name, shared by
  `scripts/install.ps1`, `internal/install/services_windows.go`
  (`nodeTaskName`) and `docs/install.md`.
- The PATH marker block `# >>> openpanda path >>>` /
  `# <<< openpanda path <<<` is written and stripped by both the installers
  and `internal/install/path_unix.go`.
- Auto-start unit names: `com.openpanda.node` (macOS launchd label) and
  `openpanda.service` (systemd user unit).
- `config.TermsVersionCurrent` — the PTY fixtures must be at least this
  version. Derive it from `config.go` instead of hard-coding a number: a
  stale fixture makes the re-authorization card swallow keystrokes, which
  looks like a scroll regression.
- `askengine.ProgressKind` (`internal/askengine/askengine.go`) — adding a
  kind means checking its rendering in `cmd/panda/ui.go` (`progressNote`,
  plus five locale strings), `cmd/panda/tui_turn.go` (`onProgress`),
  `webui/panel/sessions.go` (`statusEventOf`, SSE) and
  `webui/app/src/views/sessions.tsx` (`statusNote`, plus web locale strings
  for kinds the console phrases locally instead of passing through).
- Delivering a user message is a three-sided contract:
  `task_messages.consumed_at` (migration 58); `EvUserMsg` /
  `EvUserMsgConsumed` from `internal/core/trace.go` in **both**
  `visibleTraceTypes` (`webui/panel/panel.go`) and `traceEventTypes`
  (`webui/panel/events.go`), with a case and five `events.*` strings in web
  `components/event-parser.ts`; and `StreamCallbacks.OnSteerConsumed`
  (`internal/askengine/askengine.go`), which `webui/panel/sessions.go`
  turns into the SSE `steer` event that upgrades a "queued" note. A
  successful channel write is not a delivery acknowledgment. The TUI leaves
  `GetSteer` and `OnSteerConsumed` nil on purpose (`cmd/panda/tui_msgs.go`):
  mid-turn input is the next turn, and the durable `/queue` and `/inject`
  commands cover cross-process steering. Those two commands need a case at
  both dispatch points in `cmd/panda/tui_update.go` (`submitSlash` and
  `submitDuringTurn`), or they fall through to a nonexistent
  `panda queue` verb.
- The `go` directive in `go.mod` is the CI toolchain version. A stdlib
  vulnerability is fixed by raising that directive; editing call sites does
  not change the compiler.
- The diagnostic surface and the execution surface share one source of
  truth: `commander.EffectiveBaseURL` decides whether a model is usable.
  Every branch that refuses to run needs a matching explanation in
  `internal/doctor` or the logs, or users only see the symptom.

## Conventions

Rules by subject: tests and pre-merge checks, localization, the marketing
site, adapters, localized docs, commits and secrets.

### Tests and pre-merge checks

- Tests sit next to the code (`foo_test.go`); `internal/core/e2e_test.go`
  is the heavyweight integration suite.
- Before opening a PR run `make gate` — it is what CI enforces — plus
  `scripts/check-docs.sh` and the TUI PTY checks. A `site/` edit also needs
  `make site-check`, and a dependency change needs `go mod tidy`. Keep both
  i18n key-parity guards green: `internal/i18n`'s `TestKeyParity` /
  `TestKeyParityReverse` and `webui/app/src/i18n/parity.test.ts` — a missing
  key renders as the raw key.

### Localization: Go app and web console

- User-facing strings go through `internal/i18n` and
  `webui/app/src/i18n/` — all five locales are maintained in parallel.

### The marketing site (site/)

- The marketing site (`site/`) is a nine-page site (`index.html`, plus
  `features.html`, `docs.html`, `download.html`, `changelog.html`,
  `status.html`, `security.html`, `community.html`, `faq.html`) plus a
  self-contained `404.html`. Seven primary pages sit in the nav; security and
  community are reached from the eight-card site map and the footer. The
  eleven chapters of the landing page are reached through `hero-index` and ⌘K,
  not the nav. Content for the standalone pages comes from `README.md`,
  `SECURITY.md`, `CONTRIBUTING.md`, `docs/faq.md`, `docs/status.md` and the
  installer scripts — do not invent capabilities or numbers. Adding a page
  means adding its nav link (or site-map card), sitemap entry, canonical /
  og:url, English keys and TOC anchors — `scripts/check-site.py` asserts all
  of them, along with content floors for the capability, security, FAQ,
  community and adapter pages.
- It is bilingual by a different mechanism than the Go app: Chinese is the
  markup (every translatable node carries `data-i18n`, `data-i18n-aria`,
  `data-i18n-placeholder` or `data-i18n-content`) and English lives in the
  flat table in `site/js/i18n.js`. New copy needs the attribute *and* an
  English entry — a key without one falls back to the Chinese already in the
  page. Language comes from `?lang=`, then
  `localStorage["openpanda.site.lang"]`, then the browser; there is no build
  step, so Pages ships the files as they are. The nav, footer and head
  language bootstrap are therefore **copied verbatim into each page**;
  `check-site.py` compares them byte for byte, so a one-page edit fails.
  `js/main.js` belongs to the landing page, `js/page.js` to the eight content
  pages.
  Chinese is the only canonical edition: no hreflang or alternate locale is
  declared, JSON-LD lives on the landing page only and carries
  language-neutral facts (no FAQPage — it would disagree with the English
  view), and social metadata stays zh_CN in both views.
  `scripts/check-site.py` guards i18n key sets in both directions, in-page
  and cross-page anchors, local assets, tab ARIA, per-page canonicals and
  shared chrome, versions against `CHANGELOG.md`, the release list in
  `changelog.html`, the target matrix in `download.html`, the sitemap and
  link safety. A new release must update `changelog.html`, `download.html`
  and every version literal, or the check goes red. Run `make site-check`
  after any `site/` edit; it also runs before the artifact upload in
  `.github/workflows/pages.yml`.
- The landing page's product section is evidence, not a mock: `site/media/`
  holds real console captures, terminal stills and a short screen recording,
  shared with the demo blocks in the five-language README set. Markup, the
  `s3.rec.*` / `s3.shot.*` / `s3.demo.*` keys in the site dictionary and
  the media files move together; `check-site.py` asserts every local asset
  exists. CLI placeholders such as `<id>` or `<version>` must be written
  `&lt;…&gt;` in both the HTML and the English dictionary — an unescaped one
  becomes an unknown element and swallows the rest of the page. The
  ESO/S. Brunier `eso0932a` backdrop is CC BY 4.0, so the shared footer keeps
  the source link, the licence link and an adaptation note.

### Adapters, localized docs, commits and secrets

- New agent adapters plug into `adapters/` and speak the `_harness.py`
  wire contract; `make adapter-test` (`tests/adapter_contract_test.py`)
  exercises it against fake CLIs on PATH and is a prerequisite of
  `make test`, so an adapter flag change is a deliberate contract edit.
  Adapters honour each CLI's native model preference; the per-agent model
  and concurrency maps are documented in `docs/agent-execution.md`.
- Top-level docs are localized too — README, CHANGELOG, CONTRIBUTING,
  CLA, CODE_OF_CONDUCT, COMMERCIAL, TERMS and SECURITY each carry
  per-locale variants (`zh-CN`/`ja`/`es`/`de`); keep them in sync.
- Commits and PR bodies carry no AI attribution: no `Co-Authored-By` bot
  trailers, no "Generated with" footers, and branch names should name the
  change. The public history is part of the project's record.
- Never log secrets; `internal/security` owns redaction — route new
  secret-handling through it rather than reinventing.